Hello, i am using SwiftNIO as local proxy server to traffic HTTP. I have two problem right now:
1 By using the official example ConnecteHandler to handle HTTPS, I implement ProxyHandler to handle HTTP, I encountered the following error, it seem that i have received the respond, but error in type conversion
NIOCore/NIOAny.swift:200: Fatal error: tried to decode as type HTTPPart<HTTPResponseHead, IOData> but found HTTPPart<HTTPResponseHead, ByteBuffer> with contents other(NIOHTTP1.HTTPPart<NIOHTTP1.HTTPResponseHead, NIOCore.ByteBuffer>.head(HTTPResponseHead .....
here is the code of ProxyHandler, the GlueHandler use the official example:
final class HTTPConnectHandler: ChannelDuplexHandler, RemovableChannelHandler {
func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {
if case let .pendingConnection(head) = self.state {
self.state = .connected
context.fireChannelRead(self.wrapInboundOut(.head(head)))
if let bufferedBody = self.bufferedBody {
context.fireChannelRead(self.wrapInboundOut(.body(.byteBuffer(bufferedBody))))
self.bufferedBody = nil
}
if let bufferedEnd = self.bufferedEnd {
context.fireChannelRead(self.wrapInboundOut(.end(bufferedEnd)))
self.bufferedEnd = nil
}
context.fireChannelReadComplete()
}
context.leavePipeline(removalToken: removalToken)
}
enum State {
case idle
case pendingConnection(head: HTTPRequestHead)
case connected
}
enum ConnectError: Error {
case invalidURL
case wrongScheme
case wrongHost
}
typealias InboundIn = HTTPServerRequestPart
typealias InboundOut = HTTPClientRequestPart
typealias OutboundIn = HTTPClientResponsePart
typealias OutboundOut = HTTPServerResponsePart
private var state = State.idle
private var bufferedBody: ByteBuffer?
private var bufferedEnd: HTTPHeaders?
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
guard case .head(var head) = self.unwrapInboundIn(data) else {
let unwrapped = self.unwrapInboundIn(data)
switch unwrapped {
case .body(let buffer):
switch state {
case .connected:
context.fireChannelRead(self.wrapInboundOut(.body(.byteBuffer(buffer))))
case .pendingConnection(_):
self.bufferedBody = buffer
default:
// shouldnt happen
break
}
case .end(let headers):
switch state {
case .connected:
context.fireChannelRead(self.wrapInboundOut(.end(headers)))
case .pendingConnection(_):
self.bufferedEnd = headers
default:
// shouldnt happen
break
}
case .head(_):
assertionFailure("Not possible")
break
}
return
}
guard let parsedUrl = URL(string: head.uri) else {
context.fireErrorCaught(ConnectError.invalidURL)
return
}
guard parsedUrl.scheme == "http" else {
context.fireErrorCaught(ConnectError.wrongScheme)
return
}
guard let host = head.headers.first(name: "Host"), host == parsedUrl.host else {
context.fireErrorCaught(ConnectError.wrongHost)
return
}
var targetUrl = parsedUrl.path
if let query = parsedUrl.query {
targetUrl += "?\(query)"
}
head.uri = targetUrl
switch state {
case .idle:
state = .pendingConnection(head: head)
connectTo(host: host, port: 80, context: context)
case .pendingConnection(_):
break
case .connected:
context.fireChannelRead(self.wrapInboundOut(.head(head)))
}
}
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
switch self.unwrapOutboundIn(data) {
case .head(let head):
context.write(self.wrapOutboundOut(.head(head)), promise: nil)
case .body(let body):
context.write(self.wrapOutboundOut(.body(.byteBuffer(body))), promise: nil)
case .end(let trailers):
context.write(self.wrapOutboundOut(.end(trailers)), promise: nil)
}
}
private func connectTo(host: String, port: Int, context: ChannelHandlerContext) {
let channelFuture = ClientBootstrap(group: context.eventLoop)
.channelInitializer { channel in
channel.pipeline.addHandler(HTTPRequestEncoder()).flatMap {
channel.pipeline.addHandler(ByteToMessageHandler(HTTPResponseDecoder()))
}
}
.connect(host: host, port: port)
channelFuture.whenSuccess { channel in
self.connectSucceeded(channel: channel, context: context)
}
channelFuture.whenFailure { error in
self.connectFailed(error: error, context: context)
}
}
private func connectSucceeded(channel: Channel, context: ChannelHandlerContext) {
self.glue(channel, context: context)
}
private func connectFailed(error: Error, context: ChannelHandlerContext) {
context.fireErrorCaught(error)
}
private func glue(_ peerChannel: Channel, context: ChannelHandlerContext) {
self.removeEncoder(context: context)
let (localGlue, peerGlue) = GlueHandler.matchedPair()
context.channel.pipeline.addHandler(localGlue).and(peerChannel.pipeline.addHandler(peerGlue)).whenComplete { result in
switch result {
case .success(_):
context.pipeline.removeHandler(self, promise: nil)
case .failure(_):
peerChannel.close(mode: .all, promise: nil)
context.close(promise: nil)
}
}
}
private func removeEncoder(context: ChannelHandlerContext) {
context.pipeline.context(handlerType: HTTPResponseEncoder.self).whenSuccess {_ in
context.pipeline.removeHandler(context: $0, promise: nil)
}
}
}
2 How to configure ServerBootstrap so that https is processed using connecHandler and http is processed using proxyhandler
I am a new user of swiftNIO and hope to get your feedback.
Thanks