Skip to content

Instantly share code, notes, and snippets.

@fadi-botros
Created May 1, 2018 18:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fadi-botros/b8276bb5e2b83a63045877aeb7415f34 to your computer and use it in GitHub Desktop.
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)
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