i tried to create a simple proxy that just listens on port 8001 and connects to google.com.
import NIOCore
import NIOPosix
@main
enum Main
{
public static
func main() async throws
{
let executor:MultiThreadedEventLoopGroup = .init(numberOfThreads: 1)
let bootstrap:ServerBootstrap = .init(group: executor)
.serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), SO_REUSEADDR),
value: 1)
.childChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), SO_REUSEADDR),
value: 1)
.childChannelInitializer
{
(incoming:any Channel) in
let bridge:(GlueHandler, GlueHandler) = GlueHandler.bridge()
let bootstrap:ClientBootstrap = .init(group: executor)
.connectTimeout(.seconds(3))
.channelInitializer
{
$0.pipeline.addHandler(bridge.1)
}
let forward:EventLoopFuture<any Channel> = bootstrap.connect(
host: "google.com",
port: 443)
return incoming.pipeline.addHandler(bridge.0)
.and(forward)
.map
{
_ in
print("Connected to google.com")
}
}
let channel:any Channel = try await bootstrap.bind(host: "127.0.0.1", port: 8001).get()
print("Listening...")
try await channel.closeFuture.get()
}
}
here is the glue handler, which is basically the same as the one in the example repo:
import NIOCore
final
class GlueHandler
{
private
var partner:GlueHandler?
private
var context:ChannelHandlerContext?
private
var readPending:Bool
private
init()
{
self.readPending = false
}
}
extension GlueHandler
{
static
func bridge() -> (GlueHandler, GlueHandler)
{
let bridge:(GlueHandler, GlueHandler) = (.init(), .init())
bridge.0.partner = bridge.1
bridge.1.partner = bridge.0
return bridge
}
}
extension GlueHandler
{
private
func partnerWrite(_ data:NIOAny)
{
self.context?.write(data, promise: nil)
}
private
func partnerFlush()
{
self.context?.flush()
}
private
func partnerWriteEOF()
{
self.context?.close(mode: .output, promise: nil)
}
private
func partnerCloseFull()
{
self.context?.close(promise: nil)
}
private
func partnerBecameWritable()
{
if self.readPending
{
self.readPending = false
self.context?.read()
}
}
private
var partnerWritable:Bool
{
self.context?.channel.isWritable ?? false
}
}
extension GlueHandler:ChannelDuplexHandler
{
typealias InboundIn = NIOAny
typealias OutboundIn = NIOAny
typealias OutboundOut = NIOAny
func handlerAdded(context:ChannelHandlerContext)
{
self.context = context
}
func handlerRemoved(context:ChannelHandlerContext)
{
self.context = nil
self.partner = nil
}
func channelRead(context:ChannelHandlerContext, data:NIOAny)
{
self.partner?.partnerWrite(data)
}
func channelReadComplete(context:ChannelHandlerContext)
{
self.partner?.partnerFlush()
}
func channelInactive(context:ChannelHandlerContext)
{
self.partner?.partnerCloseFull()
}
func userInboundEventTriggered(context:ChannelHandlerContext, event:Any)
{
if case ChannelEvent.inputClosed = event
{
self.partner?.partnerWriteEOF()
}
}
func errorCaught(context:ChannelHandlerContext, error:any Error)
{
self.partner?.partnerCloseFull()
}
func channelWritabilityChanged(context:ChannelHandlerContext)
{
if context.channel.isWritable
{
self.partner?.partnerBecameWritable()
}
}
func read(context:ChannelHandlerContext)
{
if case true? = self.partner?.partnerWritable
{
context.read()
}
else
{
self.readPending = true
}
}
}
however, when i try to run it, and navigate to https://127.0.0.1:8001/
in a browser, the page fails to load, although the server seems to have successfully connected to the proxy target.
Listening...
Connected to google.com
not sure what i am doing wrong here.
i also get a Sendable
error when passing one of the GlueHandler
s to the channelInitializer
closure, and if i configure numberOfThreads
to something other than 1
, i get a precondition failure in GlueHandler
due to self.partner?.partnerWritable
being called on the wrong event loop.