Created
May 1, 2018 18:27
-
-
Save fadi-botros/b8276bb5e2b83a63045877aeb7415f34 to your computer and use it in GitHub Desktop.
Trying doing TechEmpower Framework Benchmark on SwiftNIO (only JSON and Plain text)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import NIO | |
import NIOHTTP1 | |
struct Message: Codable { | |
var message: String | |
} | |
let dateFormatter: DateFormatter = { | |
let ret = DateFormatter.init() | |
ret.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz" | |
return ret | |
}() | |
func headerGenerate(in ctx: ChannelHandlerContext, server: ServerHandler, contentType: String) -> EventLoopFuture<Void> { | |
return ctx.writeAndFlush(server.wrapOutboundOut( | |
.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: HTTPHeaders.init([ | |
("Content-Type", contentType), | |
("Server", "SwiftNIO"), | |
("Date", dateFormatter.string(from: Date.init())) | |
]))))) | |
} | |
func headerGenerate(in ctx: ChannelHandlerContext, server: ServerHandler, contentLength: Int, contentType: String) -> EventLoopFuture<Void> { | |
return ctx.writeAndFlush(server.wrapOutboundOut( | |
.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok, headers: HTTPHeaders.init([ | |
("Content-Type", contentType), | |
("Content-Length", "\(contentLength)"), | |
("Server", "SwiftNIO"), | |
("Date", dateFormatter.string(from: Date.init())) | |
]))))) | |
} | |
class ServerHandler: ChannelInboundHandler, ChannelOutboundHandler { | |
typealias InboundIn = HTTPServerRequestPart | |
typealias OutboundIn = Never | |
typealias OutboundOut = HTTPServerResponsePart | |
var onEnd: (ServerHandler, ChannelHandlerContext) -> () = { server, ctx in | |
// Default is, write an error | |
ctx.write(server.wrapOutboundOut( | |
.head(HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .notFound))), promise: nil) | |
writeCodable(Message.init(message: "Error"), in: ctx, server: server) | |
} | |
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) { | |
let d = self.unwrapInboundIn(data) | |
switch d { | |
case .head(let header): | |
switch(header.uri) { | |
case "/json": | |
onEnd = { server, ctx in | |
headerGenerate(in: ctx, server: server, contentType: "application/json").whenSuccess { | |
writeCodable(Message.init(message: "Hello, World!"), | |
in: ctx, server: server) | |
} | |
} | |
case "/plaintext": | |
onEnd = { server, ctx in | |
let str = "Hello, World!" | |
let count = str.utf8.count | |
headerGenerate(in: ctx, server: server, contentLength: count, contentType: "text/plain").whenSuccess { | |
var buffer = ctx.channel.allocator.buffer(capacity: count) | |
buffer.write(string: str) | |
ctx.writeAndFlush(server.wrapOutboundOut(.body(IOData.byteBuffer(buffer)))).whenSuccess { _ in | |
ctx.writeAndFlush(server.wrapOutboundOut(.end(nil))).whenSuccess { _ in } | |
} | |
} | |
} | |
default: | |
break | |
} | |
case .end(_): | |
onEnd(self, ctx) | |
default: | |
break | |
} | |
} | |
} | |
func writeCodable<T: Codable>(_ codable: T, in ctx: ChannelHandlerContext, server: ServerHandler) { | |
let data = (try? JSONEncoder.init().encode(codable)) ?? Data() | |
writeData(data, length: data.count, | |
in: ctx, server: server) | |
} | |
func writeData<T: Sequence>(_ data: T, length: Int, in ctx: ChannelHandlerContext, server: ServerHandler) where T.Element == UInt8 { | |
var byteBuffer = ctx.channel.allocator.buffer(capacity: length) | |
byteBuffer.write(bytes: data) | |
ctx.writeAndFlush(server.wrapOutboundOut(.body(.byteBuffer(byteBuffer)))).whenSuccess {_ in | |
ctx.writeAndFlush(server.wrapOutboundOut(.end(nil))).whenSuccess { _ in | |
ctx.close().whenSuccess { _ in } | |
} | |
} | |
} | |
let group = MultiThreadedEventLoopGroup(numThreads: System.coreCount) // 4) | |
let bootstrap = ServerBootstrap(group: group) | |
// Specify backlog and enable SO_REUSEADDR for the server itself | |
.serverChannelOption(ChannelOptions.backlog, value: 256) | |
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) | |
// Set the handlers that are applied to the accepted Channels | |
.childChannelInitializer { channel in | |
channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true).then { | |
channel.pipeline.add(handler: ServerHandler()) | |
} | |
} | |
// Enable TCP_NODELAY and SO_REUSEADDR for the accepted Channels | |
.childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1) | |
.childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) | |
.childChannelOption(ChannelOptions.maxMessagesPerRead, value: 1) | |
.childChannelOption(ChannelOptions.allowRemoteHalfClosure, value: true) | |
defer { | |
try! group.syncShutdownGracefully() | |
} | |
let channel = try { () -> Channel in | |
return try bootstrap.bind(host: "127.0.0.1", port: 8080).wait() | |
}() | |
guard let localAddress = channel.localAddress else { | |
fatalError("Address was unable to bind. Please check that the socket was not closed or that the address family was understood.") | |
} | |
// This will never unblock as we don't close the ServerChannel | |
try channel.closeFuture.wait() | |
print("Server closed") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment