mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-05-15 17:38:53 +08:00
Motivation: Especially with TLS but also without, in real production environments it can be handy to be able to write pcap files from NIO directly. Modifications: add a ChannelHandler that can write a PCAP trace from what's going on in the ChannelPipeline. Result: easier debugging in production
103 lines
4.4 KiB
Swift
103 lines
4.4 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2018 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 NIO
|
|
import NIOHTTP1
|
|
import NIOExtras
|
|
|
|
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)
|
|
context.writeAndFlush(self.wrapOutboundOut(.end(nil))).whenComplete { (_: Result<(), Error>) in
|
|
context.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")
|
|
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.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)
|
|
}
|
|
}
|
|
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
|
|
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.pipeline.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.pipeline.configureHTTPServerPipeline(withPipeliningAssistance: true, withErrorHandling: true).flatMap {
|
|
channel.pipeline.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()
|