mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-05-14 17:02:43 +08:00
135 lines
5.8 KiB
Swift
135 lines
5.8 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Dispatch
|
|
import NIOCore
|
|
import NIOExtras
|
|
import NIOHTTP1
|
|
import NIOPosix
|
|
|
|
private final class HTTPHandler: ChannelInboundHandler {
|
|
typealias InboundIn = HTTPServerRequestPart
|
|
typealias OutboundOut = HTTPServerResponsePart
|
|
|
|
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
|
let req = self.unwrapInboundIn(data)
|
|
switch req {
|
|
case .head(let head):
|
|
guard head.version == HTTPVersion(major: 1, minor: 1) else {
|
|
context.write(
|
|
self.wrapOutboundOut(.head(HTTPResponseHead(version: head.version, status: .badRequest))),
|
|
promise: nil
|
|
)
|
|
let loopBoundContext = NIOLoopBound.init(context, eventLoop: context.eventLoop)
|
|
context.writeAndFlush(self.wrapOutboundOut(.end(nil))).whenComplete { (_: Result<(), Error>) in
|
|
loopBoundContext.value.close(promise: nil)
|
|
}
|
|
return
|
|
}
|
|
case .body:
|
|
() // ignore
|
|
case .end:
|
|
var buffer = context.channel.allocator.buffer(capacity: 128)
|
|
buffer.writeStaticString("received request; waiting 30s then finishing up request\n")
|
|
buffer.writeStaticString(
|
|
"press Ctrl+C in the server's terminal or run the following command to initiate server shutdown\n"
|
|
)
|
|
buffer.writeString(" kill -INT \(getpid())\n") // ignore-unacceptable-language
|
|
context.write(
|
|
self.wrapOutboundOut(
|
|
.head(
|
|
HTTPResponseHead(
|
|
version: HTTPVersion(major: 1, minor: 1),
|
|
status: .ok
|
|
)
|
|
)
|
|
),
|
|
promise: nil
|
|
)
|
|
context.writeAndFlush(self.wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil)
|
|
buffer.clear()
|
|
buffer.writeStaticString("done with the request now\n")
|
|
_ = context.eventLoop.assumeIsolated().scheduleTask(in: .seconds(30)) { [buffer] in
|
|
context.write(self.wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil)
|
|
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
func errorCaught(context: ChannelHandlerContext, error: Error) {
|
|
print(error)
|
|
}
|
|
}
|
|
|
|
private func runServer() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
|
|
|
|
do {
|
|
// This nested block is necessary to ensure that all the destructors for objects defined inside are called before the final call to group.syncShutdownGracefully(). A possible side effect of not doing this is a run-time error "Cannot schedule tasks on an EventLoop that has already shut down".
|
|
let quiesce = ServerQuiescingHelper(group: group)
|
|
|
|
let signalQueue = DispatchQueue(label: "io.swift-nio.NIOExtrasDemo.SignalHandlingQueue")
|
|
let signalSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: signalQueue)
|
|
let fullyShutdownPromise: EventLoopPromise<Void> = group.next().makePromise()
|
|
signalSource.setEventHandler {
|
|
signalSource.cancel()
|
|
print("\nreceived signal, initiating shutdown which should complete after the last request finished.")
|
|
|
|
quiesce.initiateShutdown(promise: fullyShutdownPromise)
|
|
}
|
|
signal(SIGINT, SIG_IGN)
|
|
signalSource.resume()
|
|
|
|
do {
|
|
|
|
let serverChannel = try ServerBootstrap(group: group)
|
|
.serverChannelOption(ChannelOptions.backlog, value: 256)
|
|
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.serverChannelInitializer { channel in
|
|
channel.eventLoop.makeCompletedFuture {
|
|
try channel.pipeline.syncOperations.addHandler(
|
|
quiesce.makeServerChannelHandler(channel: channel)
|
|
)
|
|
}
|
|
}
|
|
.childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
|
|
.childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
|
.childChannelOption(ChannelOptions.maxMessagesPerRead, value: 1)
|
|
.childChannelInitializer { channel in
|
|
channel.eventLoop.makeCompletedFuture {
|
|
let sync = channel.pipeline.syncOperations
|
|
try sync.configureHTTPServerPipeline(
|
|
withPipeliningAssistance: true,
|
|
withErrorHandling: true
|
|
)
|
|
try sync.addHandler(HTTPHandler())
|
|
}
|
|
}
|
|
.bind(host: "localhost", port: 0)
|
|
.wait()
|
|
print("HTTP server up and running on \(serverChannel.localAddress!)")
|
|
print("to connect to this server, run")
|
|
print(" curl http://localhost:\(serverChannel.localAddress!.port!)")
|
|
} catch {
|
|
try group.syncShutdownGracefully()
|
|
throw error
|
|
}
|
|
try fullyShutdownPromise.futureResult.wait()
|
|
}
|
|
|
|
try group.syncShutdownGracefully()
|
|
}
|
|
|
|
try runServer()
|