Hey everyone,
i'm new with Swift and specially with SwiftNIO. I'm trying to create a http server with Lifecycle stuff. Now my biggest problem is the next error.
NIOHTTP1/HTTPServerPipelineHandler.swift:104: Fatal error: Unexpectedly received a response in state idle
I think something happens and the readChannel function is executed when it should not.
The server creation code:
class http_server_test{
private let group: MultiThreadedEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
private var server: ServerBootstrap
private let host = "127.0.0.1"
private let port = 8080
private var channel:Channel!
var lifecycle : ServiceLifecycle = ServiceLifecycle(configuration: ServiceLifecycle.Configuration(label: "http", installBacktrace: true))
init() {
server = ServerBootstrap(group: group)
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
server
.childChannelInitializer { channel in
channel.pipeline.configureHTTPServerPipeline().flatMap {
channel.pipeline.addHandlers([Handler(&self.lifecycle)])
}
}
.childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16)
}
public func start() {
print("Starting HTTP server...")
do {
channel = try server.bind(host: host, port: port).wait()
print("Server started and listening on \(channel.localAddress!)")
try channel.closeFuture.wait()
} catch {
print("ERROR")
}
}
public func stop() {
do {
try group.syncShutdownGracefully()
print("Server closed")
} catch {
print("ERROR")
}
}
}
My handler class code:
class Handler:ChannelInboundHandler {
typealias InboundIn = HTTPServerRequestPart
typealias OutboundOut = HTTPServerResponsePart
let logger = Logger(label: "SubSystemLifecycle")
let componentlifecycle: ComponentLifecycle = ComponentLifecycle(label: "SubSystem")
let router:Router = Router()
var isStarted = false
var pendingProcess = false
var context:ChannelHandlerContext? = nil
init(_ lifecycle: inout ServiceLifecycle) {
lifecycle.register(self.componentlifecycle)
componentlifecycle.start { error in
if let error = error {
print("Lifecycle failed starting ☠️: \(error)")
} else {
print("Lifecycle started successfully 🚀")
self.isStarted = true
if self.pendingProcess == true && self.context != nil {
self.move(context:self.context!)
}
}
}
}
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
print("I'am the channelRead")
router.setRequest(self.unwrapInboundIn(data))
self.context = context
if isStarted {
move(context: context)
}
else {
pendingProcess = true
}
}
// more methods...
}
The Router class method move only returns a simple html to show, there is nothing of NIO there.
lukasa
(Cory Benfield)
2
The issue here is that you're sending your HTML response immediately when you get a new connection, without waiting for a request. You need to wait for a request to come in on channelRead before you call self.move.
I think i'm doing that in Router.
func move(context: ChannelHandlerContext) -> String {
switch request {
case .head(let headers):
print("Received headers: \(headers)")
print(headers.uri)
// HTML things....
default:
if let r = request {
return "Ignoring part: \(r)"
} else {
return ""
}
} ```
lukasa
(Cory Benfield)
5
Where do you write responses from? Where is the call to context.write?
In the Handler class i have a method move().
func move(context: ChannelHandlerContext){
let html = router.move(context: context)
let responseHeaders = HTTPHeaders([("content-type", "text/html")])
let responseData = context.channel.allocator.buffer(string: html)
let responseHead = HTTPResponseHead(version: .init(major: 1, minor: 1), status: .ok, headers: responseHeaders)
let responsePartHead = HTTPServerResponsePart.head(responseHead)
context.write(self.wrapOutboundOut(responsePartHead), promise: nil)
let responsePartBody = HTTPServerResponsePart.body(.byteBuffer(responseData))
context.write(self.wrapOutboundOut(responsePartBody), promise: nil)
let responsePartEnd = HTTPServerResponsePart.end(nil)
context.writeAndFlush(self.wrapOutboundOut(responsePartEnd), promise: nil)
}```
lukasa
(Cory Benfield)
7
This seems to be able to call move multiple times: once for each part of the HTTP request. HTTP requests come in at least two parts: .head and .end, with the possibility for many more. You aren't keeping track of enough state to send only one response to a given request.
Generally, the right thing to do is to process a request when your receive .end, not .head.