Having a sync.Once be checked in each net.Conn operation (i.e. on every Read()) seems quite inefficient. I suggest enqueuing accepted connections from the inner net.Listener's Accept(), processing/reading the header asynchronously, and having the publicly exposed net.Listener return the already-processed net.Conns...
Here's some pseudocode to illustrate what I mean:
import "net"
type listener struct {
inner net.Listener
in chan net.Conn
out chan net.Conn
}
// function that reads the proxy protocol header and returns a net.Conn
// implementation which returns the right address on RemoteAddr()
func processFunc(in net.Conn) net.Conn {
// assume implemented
}
func NewListener(inner net.Listener) net.Listener {
wrapped := &listener{
inner: inner,
in: make(chan net.Conn, 1000),
out: make(chan net.Conn, 1000),
}
// kick-off go routine to process newly accepted connections
go func() {
for conn := range wrapped.in {
conn := conn // avoid address capture
go func() {
processed := processFunc(conn)
if processed != nil {
wrapped.out <- processed
}
}()
}
}()
// kick-off go routine to accept new connections
go func() {
for {
conn, err := inner.Accept()
if err != nil {
return // accept errors are not recoverable
}
wrapped.in <- conn
}
}()
return wrapped
}
func (l *listener) Accept() (net.Conn, error) {
return <-l.out, nil
}
func (l *listener) Close() error {
return l.inner.Close()
}
func (l *listener) Addr() net.Addr {
return l.inner.Addr()
}
If there's interest in this I would love to submit a PR... seems indeed more efficient!
Having a
sync.Once
be checked in eachnet.Conn
operation (i.e. on everyRead()
) seems quite inefficient. I suggest enqueuing accepted connections from the innernet.Listener
'sAccept()
, processing/reading the header asynchronously, and having the publicly exposednet.Listener
return the already-processednet.Conn
s...Here's some pseudocode to illustrate what I mean:
If there's interest in this I would love to submit a PR... seems indeed more efficient!