import Foundation import NIO import NIOHTTP1 struct JSONTestResponse: Encodable { let message = "Hello, World!" } enum Constants { static let httpVersion = HTTPVersion(major: 1, minor: 1) static let serverName = "SwiftNIO" static let plainTextResponse: StaticString = "Hello, World!" static let plainTextResponseLength = plainTextResponse.utf8CodeUnitCount static let plainTextResponseLengthString = String(plainTextResponseLength) static let jsonResponseLength = try! JSONEncoder().encode(JSONTestResponse()).count static let jsonResponseLengthString = String(jsonResponseLength) } private final class HTTPHandler: ChannelInboundHandler { public typealias InboundIn = HTTPServerRequestPart public typealias OutboundOut = HTTPServerResponsePart let dateFormatter = RFC1123DateFormatter() let jsonEncoder = JSONEncoder() var plaintextBuffer: ByteBuffer! var jsonBuffer: ByteBuffer! init() { let allocator = ByteBufferAllocator() plaintextBuffer = allocator.buffer(capacity: Constants.plainTextResponseLength) plaintextBuffer.writeStaticString(Constants.plainTextResponse) jsonBuffer = allocator.buffer(capacity: Constants.jsonResponseLength) } func channelRead(context: ChannelHandlerContext, data: NIOAny) { let reqPart = self.unwrapInboundIn(data) switch reqPart { case .head(let request): switch request.uri { case "/plaintext": processPlaintext(context: context) case "/json": processJSON(context: context) default: _ = context.close() } case .body: break case .end: _ = context.write(self.wrapOutboundOut(.end(nil))) } } func channelReadComplete(context: ChannelHandlerContext) { context.flush() context.fireChannelReadComplete() } private func processPlaintext(context: ChannelHandlerContext) { let responseHead = plainTextResponseHead(contentLength: Constants.plainTextResponseLengthString) context.write(self.wrapOutboundOut(.head(responseHead)), promise: nil) context.write(self.wrapOutboundOut(.body(.byteBuffer(plaintextBuffer))), promise: nil) } private func processJSON(context: ChannelHandlerContext) { let responseHead = jsonResponseHead(contentLength: Constants.jsonResponseLengthString) context.write(self.wrapOutboundOut(.head(responseHead)), promise: nil) let responseData = try! jsonEncoder.encode(JSONTestResponse()) jsonBuffer.clear() jsonBuffer.writeBytes(responseData) context.write(self.wrapOutboundOut(.body(.byteBuffer(jsonBuffer))), promise: nil) } private func jsonResponseHead(contentLength: String) -> HTTPResponseHead { return responseHead(contentType: "application/json", contentLength: contentLength) } private func plainTextResponseHead(contentLength: String) -> HTTPResponseHead { return responseHead(contentType: "text/plain", contentLength: contentLength) } private func responseHead(contentType: String, contentLength: String) -> HTTPResponseHead { var headers = HTTPHeaders() headers.add(name: "content-type", value: contentType) headers.add(name: "content-length", value: contentLength) headers.add(name: "server", value: Constants.serverName) headers.add(name: "date", value: dateFormatter.getDate()) return HTTPResponseHead(version: Constants.httpVersion, status: .ok, headers: headers) } } let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) let bootstrap = ServerBootstrap(group: group) .serverChannelOption(ChannelOptions.backlog, value: 8192) .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_TCP), TCP_NODELAY), value: 1) .childChannelInitializer { channel in channel.pipeline.configureHTTPServerPipeline(withPipeliningAssistance: false).flatMap { channel.pipeline.addHandler(HTTPHandler()) } } .childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16) .childChannelOption(ChannelOptions.recvAllocator, value: AdaptiveRecvByteBufferAllocator()) defer { try! group.syncShutdownGracefully() } let channel = try! bootstrap.bind(host: "0.0.0.0", port: 8080).wait() try! channel.closeFuture.wait()