From b7dd9dea3f2f90dcece85d26f4af0c368214a371 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sat, 15 Jan 2022 23:35:12 +0800 Subject: [PATCH] add pht(s) --- cmd/gost/register.go | 2 + go.mod | 3 + go.sum | 7 +- pkg/dialer/pht/conn.go | 159 ++++++++++++++++++++ pkg/dialer/pht/dialer.go | 143 ++++++++++++++++++ pkg/dialer/pht/metadata.go | 48 ++++++ pkg/listener/pht/conn.go | 20 +++ pkg/listener/pht/listener.go | 282 +++++++++++++++++++++++++++++++++++ pkg/listener/pht/metadata.go | 29 ++++ 9 files changed, 689 insertions(+), 4 deletions(-) create mode 100644 pkg/dialer/pht/conn.go create mode 100644 pkg/dialer/pht/dialer.go create mode 100644 pkg/dialer/pht/metadata.go create mode 100644 pkg/listener/pht/conn.go create mode 100644 pkg/listener/pht/listener.go create mode 100644 pkg/listener/pht/metadata.go diff --git a/cmd/gost/register.go b/cmd/gost/register.go index c752e21..70c1f9a 100644 --- a/cmd/gost/register.go +++ b/cmd/gost/register.go @@ -21,6 +21,7 @@ import ( _ "github.com/go-gost/gost/pkg/dialer/kcp" _ "github.com/go-gost/gost/pkg/dialer/obfs/http" _ "github.com/go-gost/gost/pkg/dialer/obfs/tls" + _ "github.com/go-gost/gost/pkg/dialer/pht" _ "github.com/go-gost/gost/pkg/dialer/quic" _ "github.com/go-gost/gost/pkg/dialer/ssh" _ "github.com/go-gost/gost/pkg/dialer/tcp" @@ -56,6 +57,7 @@ import ( _ "github.com/go-gost/gost/pkg/listener/kcp" _ "github.com/go-gost/gost/pkg/listener/obfs/http" _ "github.com/go-gost/gost/pkg/listener/obfs/tls" + _ "github.com/go-gost/gost/pkg/listener/pht" _ "github.com/go-gost/gost/pkg/listener/quic" _ "github.com/go-gost/gost/pkg/listener/redirect/udp" _ "github.com/go-gost/gost/pkg/listener/rtcp" diff --git a/go.mod b/go.mod index d16cafc..766fd76 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/pelletier/go-toml v1.9.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect + github.com/rs/xid v1.3.0 github.com/shadowsocks/go-shadowsocks2 v0.1.4 github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 github.com/sirupsen/logrus v1.8.1 @@ -62,3 +63,5 @@ require ( gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 ) + +require github.com/marten-seemann/qpack v0.2.1 // indirect diff --git a/go.sum b/go.sum index 4852ccc..4d7f0ef 100644 --- a/go.sum +++ b/go.sum @@ -264,6 +264,7 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco= @@ -281,10 +282,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.44 h1:4rpqcegYPVkvIeVhITrKP1sRR3KjfRc1nrOPMUZmLyc= -github.com/miekg/dns v1.1.44/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.45 h1:g5fRIhm9nx7g8osrAvgb16QJfmyMsyOCb+J7LSv+Qzk= github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/milosgajdos/tenus v0.0.3 h1:jmaJzwaY1DUyYVD0lM4U+uvP2kkEg1VahDqRFxIkVBE= @@ -338,6 +336,8 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= @@ -707,7 +707,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 h1:BonxutuHCTL0rBDnZlKjpGIQFTjyUVTexFOdWkB6Fg0= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= diff --git a/pkg/dialer/pht/conn.go b/pkg/dialer/pht/conn.go new file mode 100644 index 0000000..dadbb65 --- /dev/null +++ b/pkg/dialer/pht/conn.go @@ -0,0 +1,159 @@ +package pht + +import ( + "bufio" + "bytes" + "encoding/base64" + "errors" + "fmt" + "net" + "net/http" + "time" + + "github.com/go-gost/gost/pkg/logger" +) + +type conn struct { + cid string + addr string + client *http.Client + tlsEnabled bool + buf []byte + rxc chan []byte + closed chan struct{} + md metadata + logger logger.Logger +} + +func (c *conn) Read(b []byte) (n int, err error) { + if len(c.buf) == 0 { + select { + case c.buf = <-c.rxc: + case <-c.closed: + err = net.ErrClosed + return + } + } + + n = copy(b, c.buf) + c.buf = c.buf[n:] + + return +} + +func (c *conn) Write(b []byte) (n int, err error) { + if len(b) == 0 { + return + } + + buf := bytes.NewBufferString(base64.StdEncoding.EncodeToString(b)) + buf.WriteByte('\n') + + var url string + if c.tlsEnabled { + url = fmt.Sprintf("https://%s%s?token=%s", c.addr, c.md.pushPath, c.cid) + } else { + url = fmt.Sprintf("http://%s%s?token=%s", c.addr, c.md.pushPath, c.cid) + } + r, err := http.NewRequest(http.MethodPost, url, buf) + if err != nil { + return + } + + resp, err := c.client.Do(r) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err = errors.New(resp.Status) + return + } + + n = len(b) + return +} + +func (c *conn) readLoop() { + defer c.Close() + + var url string + if c.tlsEnabled { + url = fmt.Sprintf("https://%s%s?token=%s", c.addr, c.md.pullPath, c.cid) + } else { + url = fmt.Sprintf("http://%s%s?token=%s", c.addr, c.md.pullPath, c.cid) + } + for { + err := func() error { + r, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return err + } + + resp, err := c.client.Do(r) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + b, err := base64.StdEncoding.DecodeString(scanner.Text()) + if err != nil { + return err + } + select { + case c.rxc <- b: + case <-c.closed: + return net.ErrClosed + } + } + + return scanner.Err() + }() + + if err != nil { + c.logger.Error(err) + return + } + } +} + +func (c *conn) LocalAddr() net.Addr { + return &net.TCPAddr{} +} + +func (c *conn) RemoteAddr() net.Addr { + addr, _ := net.ResolveTCPAddr("tcp", c.addr) + if addr == nil { + addr = &net.TCPAddr{} + } + + return addr +} + +func (c *conn) Close() error { + select { + case <-c.closed: + default: + close(c.closed) + } + return nil +} + +func (c *conn) SetReadDeadline(t time.Time) error { + return nil +} + +func (c *conn) SetWriteDeadline(t time.Time) error { + return nil +} + +func (c *conn) SetDeadline(t time.Time) error { + return nil +} diff --git a/pkg/dialer/pht/dialer.go b/pkg/dialer/pht/dialer.go new file mode 100644 index 0000000..03a0669 --- /dev/null +++ b/pkg/dialer/pht/dialer.go @@ -0,0 +1,143 @@ +package pht + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/httputil" + "strings" + "time" + + "github.com/go-gost/gost/pkg/dialer" + "github.com/go-gost/gost/pkg/logger" + md "github.com/go-gost/gost/pkg/metadata" + "github.com/go-gost/gost/pkg/registry" +) + +func init() { + registry.RegisterDialer("pht", NewDialer) + registry.RegisterDialer("phts", NewTLSDialer) +} + +type phtDialer struct { + tlsEnabled bool + md metadata + logger logger.Logger + options dialer.Options +} + +func NewDialer(opts ...dialer.Option) dialer.Dialer { + options := dialer.Options{} + for _, opt := range opts { + opt(&options) + } + + return &phtDialer{ + logger: options.Logger, + options: options, + } +} + +func NewTLSDialer(opts ...dialer.Option) dialer.Dialer { + options := dialer.Options{} + for _, opt := range opts { + opt(&options) + } + + return &phtDialer{ + tlsEnabled: true, + logger: options.Logger, + options: options, + } +} + +func (d *phtDialer) Init(md md.Metadata) (err error) { + return d.parseMetadata(md) +} + +func (d *phtDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) { + tr := &http.Transport{ + // Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + if d.tlsEnabled { + tr.TLSClientConfig = d.options.TLSConfig + } + + client := &http.Client{ + Timeout: 60 * time.Second, + Transport: tr, + } + token, err := d.authorize(ctx, client, addr) + if err != nil { + d.logger.Error(err) + return nil, err + } + + c := &conn{ + cid: token, + addr: addr, + client: client, + tlsEnabled: d.tlsEnabled, + rxc: make(chan []byte, 128), + closed: make(chan struct{}), + md: d.md, + logger: d.logger, + } + go c.readLoop() + + return c, nil +} + +func (d *phtDialer) authorize(ctx context.Context, client *http.Client, addr string) (token string, err error) { + var url string + if d.tlsEnabled { + url = fmt.Sprintf("https://%s%s", addr, d.md.authorizePath) + } else { + url = fmt.Sprintf("http://%s%s", addr, d.md.authorizePath) + } + r, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return + } + + if d.logger.IsLevelEnabled(logger.DebugLevel) { + dump, _ := httputil.DumpRequest(r, false) + d.logger.Debug(string(dump)) + } + + resp, err := client.Do(r) + if err != nil { + return + } + defer resp.Body.Close() + + if d.logger.IsLevelEnabled(logger.DebugLevel) { + dump, _ := httputil.DumpResponse(resp, false) + d.logger.Debug(string(dump)) + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + return + } + + if strings.HasPrefix(string(data), "token=") { + token = strings.TrimPrefix(string(data), "token=") + } + if token == "" { + err = errors.New("authorize failed") + } + return +} diff --git a/pkg/dialer/pht/metadata.go b/pkg/dialer/pht/metadata.go new file mode 100644 index 0000000..df1df02 --- /dev/null +++ b/pkg/dialer/pht/metadata.go @@ -0,0 +1,48 @@ +package pht + +import ( + "strings" + "time" + + mdata "github.com/go-gost/gost/pkg/metadata" +) + +const ( + dialTimeout = "dialTimeout" + defaultAuthorizePath = "/authorize" + defaultPushPath = "/push" + defaultPullPath = "/pull" +) + +const ( + defaultDialTimeout = 5 * time.Second +) + +type metadata struct { + dialTimeout time.Duration + authorizePath string + pushPath string + pullPath string +} + +func (d *phtDialer) parseMetadata(md mdata.Metadata) (err error) { + const ( + authorizePath = "authorizePath" + pushPath = "pushPath" + pullPath = "pullPath" + ) + + d.md.authorizePath = mdata.GetString(md, authorizePath) + if !strings.HasPrefix(d.md.authorizePath, "/") { + d.md.authorizePath = defaultAuthorizePath + } + d.md.pushPath = mdata.GetString(md, pushPath) + if !strings.HasPrefix(d.md.pushPath, "/") { + d.md.pushPath = defaultPushPath + } + d.md.pullPath = mdata.GetString(md, pullPath) + if !strings.HasPrefix(d.md.pullPath, "/") { + d.md.pullPath = defaultPullPath + } + return +} diff --git a/pkg/listener/pht/conn.go b/pkg/listener/pht/conn.go new file mode 100644 index 0000000..8c0e1e0 --- /dev/null +++ b/pkg/listener/pht/conn.go @@ -0,0 +1,20 @@ +package pht + +import ( + "net" +) + +// pht connection, wrapped up just like a net.Conn +type conn struct { + net.Conn + remoteAddr net.Addr + localAddr net.Addr +} + +func (c *conn) LocalAddr() net.Addr { + return c.localAddr +} + +func (c *conn) RemoteAddr() net.Addr { + return c.remoteAddr +} diff --git a/pkg/listener/pht/listener.go b/pkg/listener/pht/listener.go new file mode 100644 index 0000000..e889f51 --- /dev/null +++ b/pkg/listener/pht/listener.go @@ -0,0 +1,282 @@ +// plain http tunnel + +package pht + +import ( + "bufio" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "net" + "net/http" + "net/http/httputil" + "os" + "strings" + "sync" + "time" + + "github.com/go-gost/gost/pkg/common/bufpool" + "github.com/go-gost/gost/pkg/listener" + "github.com/go-gost/gost/pkg/logger" + md "github.com/go-gost/gost/pkg/metadata" + "github.com/go-gost/gost/pkg/registry" + "github.com/rs/xid" +) + +func init() { + registry.RegisterListener("pht", NewListener) + registry.RegisterListener("phts", NewTLSListener) +} + +type phtListener struct { + tlsEnabled bool + server *http.Server + addr net.Addr + conns sync.Map + cqueue chan net.Conn + errChan chan error + logger logger.Logger + md metadata + options listener.Options +} + +func NewListener(opts ...listener.Option) listener.Listener { + options := listener.Options{} + for _, opt := range opts { + opt(&options) + } + return &phtListener{ + logger: options.Logger, + options: options, + } +} + +func NewTLSListener(opts ...listener.Option) listener.Listener { + options := listener.Options{} + for _, opt := range opts { + opt(&options) + } + return &phtListener{ + tlsEnabled: true, + logger: options.Logger, + options: options, + } +} + +func (l *phtListener) Init(md md.Metadata) (err error) { + if err = l.parseMetadata(md); err != nil { + return + } + + ln, err := net.Listen("tcp", l.options.Addr) + if err != nil { + return err + } + l.addr = ln.Addr() + + mux := http.NewServeMux() + mux.HandleFunc("/authorize", l.handleAuthorize) + mux.HandleFunc("/push", l.handlePush) + mux.HandleFunc("/pull", l.handlePull) + + l.server = &http.Server{ + Addr: l.options.Addr, + Handler: mux, + } + if l.tlsEnabled { + l.server.TLSConfig = l.options.TLSConfig + ln = tls.NewListener(ln, l.options.TLSConfig) + } + + l.cqueue = make(chan net.Conn, l.md.backlog) + l.errChan = make(chan error, 1) + + go func() { + if err := l.server.Serve(ln); err != nil { + l.logger.Error(err) + } + }() + + return +} + +func (l *phtListener) Accept() (conn net.Conn, err error) { + var ok bool + select { + case conn = <-l.cqueue: + case err, ok = <-l.errChan: + if !ok { + err = listener.ErrClosed + } + } + return +} + +func (l *phtListener) Addr() net.Addr { + return l.addr +} + +func (l *phtListener) Close() (err error) { + select { + case <-l.errChan: + default: + err = l.server.Close() + l.errChan <- err + close(l.errChan) + } + return nil +} + +func (l *phtListener) handleAuthorize(w http.ResponseWriter, r *http.Request) { + if l.logger.IsLevelEnabled(logger.DebugLevel) { + dump, _ := httputil.DumpRequest(r, false) + l.logger.Debug(string(dump)) + } + + raddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr) + if raddr == nil { + raddr = &net.TCPAddr{} + } + + // connection id + cid := xid.New().String() + + c1, c2 := net.Pipe() + c := &conn{ + Conn: c1, + localAddr: l.addr, + remoteAddr: raddr, + } + + select { + case l.cqueue <- c: + default: + c.Close() + l.logger.Warnf("connection queue is full, client %s discarded", r.RemoteAddr) + w.WriteHeader(http.StatusTooManyRequests) + return + } + + w.Write([]byte(fmt.Sprintf("token=%s", cid))) + l.conns.Store(cid, c2) +} + +func (l *phtListener) handlePush(w http.ResponseWriter, r *http.Request) { + if l.logger.IsLevelEnabled(logger.DebugLevel) { + dump, _ := httputil.DumpRequest(r, false) + l.logger.Debug(string(dump)) + } + + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusBadRequest) + return + } + + if err := r.ParseForm(); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + cid := r.Form.Get("token") + v, ok := l.conns.Load(cid) + if !ok { + w.WriteHeader(http.StatusForbidden) + return + } + conn := v.(net.Conn) + + br := bufio.NewReader(r.Body) + data, err := br.ReadString('\n') + if err != nil { + l.logger.Error(err) + conn.Close() + l.conns.Delete(cid) + w.WriteHeader(http.StatusBadRequest) + return + } + + data = strings.TrimSuffix(data, "\n") + if len(data) == 0 { + return + } + + b, err := base64.StdEncoding.DecodeString(data) + if err != nil { + l.logger.Error(err) + l.conns.Delete(cid) + conn.Close() + w.WriteHeader(http.StatusBadRequest) + return + } + + conn.SetWriteDeadline(time.Now().Add(30 * time.Second)) + defer conn.SetWriteDeadline(time.Time{}) + + if _, err := conn.Write(b); err != nil { + l.logger.Error(err) + l.conns.Delete(cid) + conn.Close() + w.WriteHeader(http.StatusGone) + } +} + +func (l *phtListener) handlePull(w http.ResponseWriter, r *http.Request) { + if l.logger.IsLevelEnabled(logger.DebugLevel) { + dump, _ := httputil.DumpRequest(r, false) + l.logger.Debug(string(dump)) + } + + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusBadRequest) + return + } + + if err := r.ParseForm(); err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + cid := r.Form.Get("token") + v, ok := l.conns.Load(cid) + if !ok { + w.WriteHeader(http.StatusForbidden) + return + } + + conn := v.(net.Conn) + + w.WriteHeader(http.StatusOK) + if fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + + b := bufpool.Get(4096) + defer bufpool.Put(b) + + for { + conn.SetReadDeadline(time.Now().Add(10 * time.Second)) + n, err := conn.Read(*b) + if err != nil { + if !errors.Is(err, os.ErrDeadlineExceeded) { + l.logger.Error(err) + l.conns.Delete(cid) + conn.Close() + } else { + (*b)[0] = '\n' + w.Write((*b)[:1]) + } + return + } + + bw := bufio.NewWriter(w) + bw.WriteString(base64.StdEncoding.EncodeToString((*b)[:n])) + bw.WriteString("\n") + if err := bw.Flush(); err != nil { + return + } + if fw, ok := w.(http.Flusher); ok { + fw.Flush() + } + } +} diff --git a/pkg/listener/pht/metadata.go b/pkg/listener/pht/metadata.go new file mode 100644 index 0000000..264e340 --- /dev/null +++ b/pkg/listener/pht/metadata.go @@ -0,0 +1,29 @@ +package pht + +import ( + mdata "github.com/go-gost/gost/pkg/metadata" +) + +const ( + defaultBacklog = 128 +) + +type metadata struct { + path string + backlog int +} + +func (l *phtListener) parseMetadata(md mdata.Metadata) (err error) { + const ( + path = "path" + backlog = "backlog" + ) + + l.md.backlog = mdata.GetInt(md, backlog) + if l.md.backlog <= 0 { + l.md.backlog = defaultBacklog + } + + l.md.path = mdata.GetString(md, path) + return +}