virtual

package
v0.0.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 9, 2022 License: MIT Imports: 16 Imported by: 0

README

Bhojpur Web - Virtual Hosting

It is a very simple library that lets you implement virtual hosting features in Bhojpur Web for different protocols (e.g., HTTP and TLS/SSL). The virtual hosting library has a high-level and a low-level interface.

The high-level virtual hosting library interface lets you wrap existing net.Listeners with the muxer objects. You can then Listen() on a muxer for a particular virtual host name of interest which will return to you a net.Listener for just connections with the virtual hostname of interest.

The lower-level virtual hosting library interface are just functions which extract the name/routing information for the given protocol and return an object implementing net.Conn that works as if no bytes had been consumed.

Simple HTTP Multiplexing

import (
	"net"
	"github.com/bhojpur/web/pkg/virtual"
)

primary, _ := net.Listen("tcp", *listen)

// start multiplexing on the primary TCP listener
mux, _ := virtual.NewHTTPMuxer(primary, muxTimeout)

// listen for connections to different domains
for _, v := range virtualHosts {
	vhost := v

	// vhost.Name is a virtual hostname like "test.bhojpur.net"
	muxListener, _ := mux.Listen(vhost.Name())

	go func(vh virtualHost, ml net.Listener) {
		for {
			conn, _ := ml.Accept()
			go vh.Handle(conn)
		}
	}(vhost, muxListener)
}

for {
	conn, err := mux.NextError()

	switch err.(type) {
	case vhost.BadRequest:
		log.Printf("got a bad request!")
		conn.Write([]byte("bad request"))
	case vhost.NotFound:
		log.Printf("got a connection for an unknown virtual host")
		conn.Write([]byte("virtual host not found"))
	case vhost.Closed:
		log.Printf("closed conn: %s", err)
	default:
		if conn != nil {
			conn.Write([]byte("server error"))
		}
	}

	if conn != nil {
		conn.Close()
	}
}

Low-level Virtual Host API for HTTP

import (
	"net"
	"github.com/bhojpur/web/pkg/virtual"
)

primary, _ := net.Listen("tcp", *listen)

// accept a new connection
conn, _ := primary.Accept()

// parse out the HTTP request and the Host header
if vhostConn, err = virtual.HTTP(conn); err != nil {
	panic("Not a valid HTTP connection!")
}

fmt.Printf("Target Host: ", vhostConn.Host())
// Target Host: bhojpur.net

// vhostConn contains the entire request as if no bytes had been consumed
bytes, _ := ioutil.ReadAll(vhostConn)
fmt.Printf("%s", bytes)
// GET / HTTP/1.1
// Host: bhojpur.net
// User-Agent: ...
// ...
Advanced Introspection

The entire HTTP request headers are available for inspection, in case you want to multiplex on something besides the Host header:

import (
	"net"
	"github.com/bhojpur/web/pkg/virtual"
)

primary, _ := net.Listen("tcp", *listen)

// accept a new connection
conn, _ := primary.Accept()

// parse out the HTTP request and the Host header
if vhostConn, err = virtual.HTTP(conn); err != nil {
	panic("Not a valid HTTP connection!")
}

httpVersion := virtual.Request.MinorVersion
customRouting := virtual.Request.Header["X-Custom-Routing-Header"]

Likewise for TLS, you can look at detailed information about the ClientHello message:

import (
	"net"
	"github.com/bhojpur/web/pkg/virtual"
)

primary, _ := net.Listen("tcp", *listen)

// accept a new connection
conn, _ := primary.Accept()

if vhostConn, err = virtual.TLS(conn); err != nil {
	panic("Not a valid TLS connection!")
}

cipherSuites := virtual.ClientHelloMsg.CipherSuites
sessionId := virtual.ClientHelloMsg.SessionId
Memory Reduction with Free

After you are done multiplexing, you probably do not need to inspect the HTTP header anymore, so you can make it available for garbage collection:

// look up the upstream host
upstreamHost := hostMapping[vhostConn.Host()]

// free up the multiplex data
vhostConn.Free()

// vhostConn.Host() == ""
// vhostConn.Request == nil (HTTP)
// vhostConn.ClientHelloMsg == nil (TLS)

TCP Stream Multiplexing

Here is an example of stream client, which responds to simple JSON requests from a stream server.

import (
	"net"
	"github.com/bhojpur/web/pkg/virtual"
)

conn, _ := net.Dial("tcp", "bhojpur.net:1234")
sess := virtual.StreamClient(conn)

for {
    stream, _ := sess.Accept()
    go func(str net.Conn) {
        defer str.Close()
        var req Request
        json.NewDecoder(str).Decode(&req)
        response := handleRequest(&req)
        json.NewEncoder(str).Encode(response)
    }(stream)
}

May be the stream client wants to make a request to the TCP stream server instead of just responding. This is easy as well:

import (
	"net"
	"github.com/bhojpur/web/pkg/virtual"
)

conn, _ := net.Dial("tcp", "bhojpur.net:1234")
sess := virtual.StreamClient(conn)

stream, _ := sess.Open()
req := Request{
    Query: "What is the meaning of Thekua, the cookies and everything?",
}
json.NewEncoder(stream).Encode(&req)
var resp Response
json.dec.Decode(&resp)
if resp.Answer != "42" {
    panic("wrong answer to the ultimate question!")
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BadRequest

type BadRequest struct {
	// contains filtered or unexported fields
}

BadRequest is returned when extraction of the virtual host name fails

type ClientHelloMsg

type ClientHelloMsg struct {
	Raw                []byte
	Vers               uint16
	Random             []byte
	SessionId          []byte
	CipherSuites       []uint16
	CompressionMethods []uint8
	NextProtoNeg       bool
	ServerName         string
	OcspStapling       bool
	SupportedCurves    []uint16
	SupportedPoints    []uint8
	TicketSupported    bool
	SessionTicket      []uint8
}

type Closed

type Closed struct {
	// contains filtered or unexported fields
}

Closed is returned when the underlying connection is closed

type Config

type Config struct {
	// Maximum size of unread data to receive and buffer (per-stream). Default 256KB.
	MaxWindowSize uint32
	// Maximum number of inbound streams to queue for Accept(). Default 128.
	AcceptBacklog uint32
	// Function creating the Session's framer. Deafult frame.NewFramer()
	NewFramer func(io.Reader, io.Writer) frame.Framer
	// contains filtered or unexported fields
}

type Conn

type Conn interface {
	net.Conn
	Host() string
	Free()
}

type ErrorCode

type ErrorCode uint32

ErrorCode is a 32-bit integer indicating the type of an error condition

const (
	NoError ErrorCode = iota
	ProtocolError
	InternalError
	FlowControlError
	StreamClosed
	StreamRefused
	StreamCancelled
	StreamReset
	FrameSizeError
	AcceptQueueFull
	EnhanceYourCalm
	RemoteGoneAway
	StreamsExhausted
	WriteTimeout
	SessionClosed
	PeerEOF

	ErrorUnknown ErrorCode = 0xFF
)

func GetError

func GetError(err error) (ErrorCode, error)

type HTTPConn

type HTTPConn struct {
	Request *http.Request
	// contains filtered or unexported fields
}

func HTTP

func HTTP(conn net.Conn) (httpConn *HTTPConn, err error)

HTTP parses the header of the first HTTP request on conn and returns a new, unread connection with metadata for virtual host multiplexing

func (*HTTPConn) Free

func (c *HTTPConn) Free()

Free sets Request to nil so that it can be garbage collected

func (*HTTPConn) Host

func (c *HTTPConn) Host() string

func (HTTPConn) Read

func (c HTTPConn) Read(p []byte) (n int, err error)

type HTTPMuxer

type HTTPMuxer struct {
	*VirtualHostMuxer
}

func NewHTTPMuxer

func NewHTTPMuxer(listener net.Listener, muxTimeout time.Duration) (*HTTPMuxer, error)

NewHTTPMuxer begins multiplexing HTTP connections on the given listener by inspecting the HTTP Host header in new connections.

func (*HTTPMuxer) HandleError

func (m *HTTPMuxer) HandleError(conn net.Conn, err error)

func (*HTTPMuxer) HandleErrors

func (m *HTTPMuxer) HandleErrors()

HandleErrors handles muxing errors by calling .NextError(). You must invoke this function, if you do not want to handle the errors yourself.

type Heartbeat

type Heartbeat struct {
	TypedStreamSession
	// contains filtered or unexported fields
}

func NewHeartbeat

func NewHeartbeat(sess TypedStreamSession, cb func(time.Duration), config *HeartbeatConfig) *Heartbeat

func (*Heartbeat) Accept

func (h *Heartbeat) Accept() (net.Conn, error)

func (*Heartbeat) AcceptStream

func (h *Heartbeat) AcceptStream() (Stream, error)

func (*Heartbeat) AcceptTypedStream

func (h *Heartbeat) AcceptTypedStream() (TypedStream, error)

func (*Heartbeat) Close

func (h *Heartbeat) Close() error

func (*Heartbeat) Start

func (h *Heartbeat) Start()

type HeartbeatConfig

type HeartbeatConfig struct {
	Interval  time.Duration
	Tolerance time.Duration
	Type      StreamType
}

func NewHeartbeatConfig

func NewHeartbeatConfig() *HeartbeatConfig

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

Listener is returned by a call to Listen() on a multiplexer. A Listener only receives connections that were made to the name passed into the muxer's Listen call.

Listener implements the net.Listener interface, so you can Accept() new connections and Close() it when finished. When you Close() a Listener, the parent muxer will stop listening for connections to the Listener's name.

func (*Listener) Accept

func (l *Listener) Accept() (net.Conn, error)

Accept returns the next multiplexed connection for this listener and blocks until one is available.

func (*Listener) Addr

func (l *Listener) Addr() net.Addr

Addr returns the address of the bound listener used by the parent muxer.

func (*Listener) Close

func (l *Listener) Close() error

Close stops the parent muxer from listening for connections to the multiplexed virtual host name.

func (*Listener) Name

func (l *Listener) Name() string

Name returns the name of the virtual host this listener receives connections on.

type NotFound

type NotFound struct {
	// contains filtered or unexported fields
}

NotFound is returned when a virtual host is not found

type Session

type Session interface {

	// Open initiates a new stream on the session. It is equivalent to
	// OpenStream(0, false)
	Open() (net.Conn, error)

	// OpenStream initiates a new stream on the session. A caller can specify an
	// opaque stream type.  Setting fin to true will cause the stream to be
	// half-closed from the local side immediately upon creation.
	OpenStream() (Stream, error)

	// Accept returns the next stream initiated by the remote side
	Accept() (net.Conn, error)

	// Accept returns the next stream initiated by the remote side
	AcceptStream() (Stream, error)

	// Attempts to close the Session cleanly. Closes the underlying stream transport.
	Close() error

	// LocalAddr returns the local address of the transport stream over which the session is running.
	LocalAddr() net.Addr

	// RemoteAddr returns the address of the remote side of the transport stream over which the session is running.
	RemoteAddr() net.Addr

	// Addr returns the session transport's local address
	Addr() net.Addr

	// Wait blocks until the session has shutdown and returns an error
	// explaining the session termination.
	Wait() (error, error, []byte)
}

Session multiplexes many Streams over a single underlying stream transport. Both sides of the Bhojpur Web multiplexer session can open new Streams. The Sessions can also accept new streams from the remote side.

A Session implements the net.Listener interface, returning new Streams from the remote side.

func StreamClient

func StreamClient(trans io.ReadWriteCloser, config *Config) Session

StreamClient returns a new client-side connection using trans as the transport.

func StreamServer

func StreamServer(trans io.ReadWriteCloser, config *Config) Session

StreamServer returns a server session using trans as the transport.

type Stream

type Stream interface {
	// Write writes the bytes in the given buffer to the stream
	Write([]byte) (int, error)

	// Read reads the next bytes on the stream into the given buffer
	Read([]byte) (int, error)

	// Closes the stream.
	Close() error

	// Half-closes the stream. Calls to Write will fail after this is invoked.
	CloseWrite() error

	// SetDeadline sets a time after which future Read and Write operations will
	// fail.
	//
	// Some implementation may not support this.
	SetDeadline(time.Time) error

	// SetReadDeadline sets a time after which future Read operations will fail.
	//
	// Some implementation may not support this.
	SetReadDeadline(time.Time) error

	// SetWriteDeadline sets a time after which future Write operations will
	// fail.
	//
	// Some implementation may not support this.
	SetWriteDeadline(time.Time) error

	// Id returns the stream's unique identifier.
	Id() uint32

	// Session returns the session object this stream is running on.
	Session() Session

	// RemoteAddr returns the session transport's remote address.
	RemoteAddr() net.Addr

	// LocalAddr returns the session transport's local address.
	LocalAddr() net.Addr
}

Stream is a full duplex stream-oriented connection that is multiplexed over a Session. Stream implements the net.Conn inteface.

type StreamType

type StreamType uint32

type TLSConn

type TLSConn struct {
	ClientHelloMsg *ClientHelloMsg
	// contains filtered or unexported fields
}

A Conn represents a secured connection. It implements the net.Conn interface.

func TLS

func TLS(conn net.Conn) (tlsConn *TLSConn, err error)

TLS parses the ClientHello message on conn and returns a new, unread connection with metadata for virtual host muxing

func (*TLSConn) Free

func (c *TLSConn) Free()

func (*TLSConn) Host

func (c *TLSConn) Host() string

func (TLSConn) Read

func (c TLSConn) Read(p []byte) (n int, err error)

type TLSMuxer

type TLSMuxer struct {
	*VirtualHostMuxer
}

func NewTLSMuxer

func NewTLSMuxer(listener net.Listener, muxTimeout time.Duration) (*TLSMuxer, error)

NewTLSMuxer begins muxing TLS connections by inspecting the SNI extension.

func (*TLSMuxer) HandleErrors

func (m *TLSMuxer) HandleErrors()

HandleErrors is the default error handler for TLS muxers. At the moment, it simply closes connections which are invalid or destined for virtual host names that it is not listening for. You must invoke this function, if you do not want to handle the errors yourself.

func (*TLSMuxer) Listen

func (m *TLSMuxer) Listen(name string) (net.Listener, error)

type TypedStream

type TypedStream interface {
	Stream
	StreamType() StreamType
}

type TypedStreamSession

type TypedStreamSession interface {
	Session
	OpenTypedStream(stype StreamType) (Stream, error)
	AcceptTypedStream() (TypedStream, error)
}

func NewTypedStreamSession

func NewTypedStreamSession(s Session) TypedStreamSession

type VirtualHostMuxer

type VirtualHostMuxer struct {
	sync.RWMutex // protects the registry
	// contains filtered or unexported fields
}

func NewVirtualHostMuxer

func NewVirtualHostMuxer(listener net.Listener, vhostFn muxFn, muxTimeout time.Duration) (*VirtualHostMuxer, error)

func (*VirtualHostMuxer) Close

func (m *VirtualHostMuxer) Close()

Close closes the underlying listener

func (*VirtualHostMuxer) Listen

func (m *VirtualHostMuxer) Listen(name string) (net.Listener, error)

Listen begins multiplexing the underlying connection to send new connections for the given name over the returned listener.

func (*VirtualHostMuxer) NextError

func (m *VirtualHostMuxer) NextError() (net.Conn, error)

NextError returns the next error encountered, while multiplexing a connection. The net.Conn may be nil if the wrapped listener returned an error from Accept()

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL