WIP: designing server/listener interfaces

This commit is contained in:
AJ ONeal 2020-05-20 01:49:40 -06:00
parent 6621ccf47e
commit 9a97d153e8
1 changed files with 245 additions and 0 deletions

View File

@ -0,0 +1,245 @@
package packer
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"testing"
"time"
"github.com/gorilla/websocket"
)
func TestDialServer(t *testing.T) {
// TODO replace the websocket connection with a mock server
relay := os.Getenv("RELAY")
authz := os.Getenv("SECRET")
ctx := context.Background()
wsd := websocket.Dialer{}
headers := http.Header{}
headers.Set("Authorization", fmt.Sprintf("Bearer %s", authz))
// *http.Response
wsconn, _, err := wsd.DialContext(ctx, relay, headers)
if nil != err {
t.Fatal(err)
return
}
mux := &MyMux{}
err = ListenAndServe(wsconn, mux)
t.Fatal(err)
}
type Listener struct {
ws *websocket.Conn
incoming chan *Conn
close chan struct{}
}
func ListenAndServe(ws *websocket.Conn, mux Mux) error {
listener := Listen(ws)
return Serve(listener, mux)
}
func Listen(ws *websocket.Conn) *Listener {
listener := &Listener{
ws: ws,
incoming: make(chan *Conn, 1),
close: make(chan struct{}),
}
ctx := context.TODO()
r := &WSConn{
c: ws,
r: nil,
w: nil,
}
decoder := NewDecoder(ctx, r)
// Feed websocket into Decoder
th := &testHandler2{
conns: map[string]*Conn{},
connCh: listener.incoming,
}
go func() {
// TODO pass error to Accept()
err := decoder.StreamDecode(th, 0)
fmt.Printf("the main stream is done: %q", err)
}()
return listener
}
type testHandler2 struct {
conns map[string]*Conn
connCh chan *Conn
chunksParsed int
bytesRead int
}
func (th *testHandler2) WriteMessage(a Addr, b []byte) {
th.chunksParsed++
addr := &a
_, ok := th.conns[addr.Network()]
if !ok {
rconn, wconn := net.Pipe()
conn := &Conn{
updated: time.Now(),
relayRemoteAddr: *addr,
relay: rconn,
local: wconn,
}
th.conns[addr.Network()] = conn
th.connCh <- conn
}
th.bytesRead += len(b)
}
func Serve(listener *Listener, mux Mux) error {
w := &WSConn{
c: listener.ws,
r: nil,
w: nil,
}
ctx := context.TODO()
encoder := NewEncoder(ctx, w)
encoder.Start()
for {
client, err := listener.Accept()
if nil != err {
return err
}
lconn, err := mux.LookupTarget(client.LocalAddr())
if nil != err {
conn.Close()
continue
}
go func() {
// TODO handle err
err := encoder.StreamEncode(*conn.LocalAddr(), lconn, 0)
fmt.Printf("a stream is done: %q", err)
}()
}
}
func Blah() {
go func() {
pipe
}
}
func (l *Listener) Accept() (*Conn, error) {
select {
case conn, ok := <-l.incoming:
if ok {
return conn, nil
}
return nil, io.EOF
case <-l.close:
l.ws.Close()
// TODO is another error more suitable?
return nil, http.ErrServerClosed
}
}
type Mux interface {
LookupTarget(*Addr) (net.Conn, error)
}
type MyMux struct {
}
// this function is very client-specific logic
func (m *MyMux) LookupTarget(paddr *Addr) (net.Conn, error) {
//if target := LookupPort(paddr.Port()); nil != target { }
if target := m.LookupServername(paddr.Hostname()); nil != target {
tconn, err := net.Dial(target.Network(), target.Hostname())
if nil != err {
return nil, err
}
/*
// TODO for http proxy
return mplexer.TargetOptions {
Hostname // default localhost
Termination // default TLS
XFWD // default... no?
Port // default 0
Conn // should be dialed beforehand
}, nil
*/
return tconn, nil
}
// TODO
return nil, errors.New("Bad Gateway")
}
func (m *MyMux) LookupServername(servername string) *Addr {
return NewAddr(
HTTPS,
TCP, // TCP -> termination.None? / Plain?
"localhost",
3000,
)
}
type WSConn struct {
c *websocket.Conn
r io.Reader
w io.WriteCloser
pingCh chan struct{}
}
func (ws *WSConn) Read(b []byte) (int, error) {
if nil == ws.r {
_, r, err := ws.c.NextReader()
if nil != err {
return 0, err
}
ws.r = r
}
n, err := ws.r.Read(b)
if io.EOF == err {
err = nil
}
return n, err
}
func (ws *WSConn) Write(b []byte) (int, error) {
// TODO create or reset ping deadline
w, err := ws.c.NextWriter(websocket.BinaryMessage)
if nil != err {
return 0, err
}
ws.w = w
n, err := ws.w.Write(b)
if nil != err {
return n, err
}
err = ws.w.Close()
return n, err
}
func (ws *WSConn) Close() error {
// TODO handle EOF as websocket.CloseNormal?
message := websocket.FormatCloseMessage(websocket.CloseGoingAway, "closing connection")
deadline := time.Now().Add(10 * time.Second)
err := ws.c.WriteControl(websocket.CloseMessage, message, deadline)
if nil != err {
fmt.Fprintf(os.Stderr, "failed to write close message to websocket: %s\n", err)
}
_ = ws.c.Close()
return err
}