This commit is contained in:
jdl
2024-11-11 06:36:55 +01:00
parent d0587cc585
commit c5419d662e
102 changed files with 4181 additions and 0 deletions

2
httpconn/README.md Normal file
View File

@@ -0,0 +1,2 @@
# httpconn

104
httpconn/client.go Normal file
View File

@@ -0,0 +1,104 @@
package httpconn
import (
"bufio"
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/http"
"net/url"
"time"
)
var (
ErrUnknownScheme = errors.New("uknown scheme")
)
type Dialer struct {
timeout time.Duration
}
func NewDialer() *Dialer {
return &Dialer{timeout: 10 * time.Second}
}
func (d *Dialer) SetTimeout(timeout time.Duration) {
d.timeout = timeout
}
func (d *Dialer) Dial(rawURL string) (net.Conn, error) {
u, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
switch u.Scheme {
case "https":
return d.DialHTTPS(u.Host+":443", u.Path)
case "http":
return d.DialHTTP(u.Host, u.Path)
default:
return nil, ErrUnknownScheme
}
}
func (d *Dialer) DialHTTPS(host, path string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), d.timeout)
dd := tls.Dialer{}
conn, err := dd.DialContext(ctx, "tcp", host)
cancel()
if err != nil {
return nil, err
}
return d.finishDialing(conn, host, path)
}
func (d *Dialer) DialHTTP(host, path string) (net.Conn, error) {
conn, err := net.DialTimeout("tcp", host, d.timeout)
if err != nil {
return nil, err
}
return d.finishDialing(conn, host, path)
}
func (d *Dialer) finishDialing(conn net.Conn, host, path string) (net.Conn, error) {
conn.SetDeadline(time.Now().Add(d.timeout))
if _, err := io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n"); err != nil {
return nil, err
}
if _, err := io.WriteString(conn, "Host: "+host+"\n\n"); err != nil {
return nil, err
}
// Require successful HTTP response before using the conn.
resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"})
if err != nil {
conn.Close()
return nil, err
}
if resp.Status != "200 OK" {
conn.Close()
return nil, err
}
conn.SetDeadline(time.Time{})
return conn, nil
}
func Dial(rawURL string) (net.Conn, error) {
return NewDialer().Dial(rawURL)
}
func DialHTTPS(host, path string) (net.Conn, error) {
return NewDialer().DialHTTPS(host, path)
}
func DialHTTP(host, path string) (net.Conn, error) {
return NewDialer().DialHTTP(host, path)
}

42
httpconn/conn_test.go Normal file
View File

@@ -0,0 +1,42 @@
package httpconn
import (
"net"
"net/http"
"net/http/httptest"
"strings"
"testing"
"golang.org/x/net/nettest"
)
func TestNetTest_TestConn(t *testing.T) {
nettest.TestConn(t, func() (c1, c2 net.Conn, stop func(), err error) {
connCh := make(chan net.Conn, 1)
doneCh := make(chan bool)
mux := http.NewServeMux()
mux.HandleFunc("/connect", func(w http.ResponseWriter, r *http.Request) {
conn, err := Accept(w, r)
if err != nil {
panic(err)
}
connCh <- conn
<-doneCh
})
srv := httptest.NewServer(mux)
c1, err = DialHTTP(strings.TrimPrefix(srv.URL, "http://"), "/connect")
if err != nil {
panic(err)
}
c2 = <-connCh
return c1, c2, func() {
doneCh <- true
srv.Close()
}, nil
})
}

5
httpconn/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module git.crumpington.com/lib/httpconn
go 1.23.2
require golang.org/x/net v0.30.0

2
httpconn/go.sum Normal file
View File

@@ -0,0 +1,2 @@
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=

32
httpconn/server.go Normal file
View File

@@ -0,0 +1,32 @@
package httpconn
import (
"io"
"net"
"net/http"
"time"
)
func Accept(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
if r.Method != "CONNECT" {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusMethodNotAllowed)
io.WriteString(w, "405 must CONNECT\n")
return nil, http.ErrNotSupported
}
hj, ok := w.(http.Hijacker)
if !ok {
return nil, http.ErrNotSupported
}
conn, _, err := hj.Hijack()
if err != nil {
return nil, err
}
_, _ = io.WriteString(conn, "HTTP/1.0 200 OK\n\n")
conn.SetDeadline(time.Time{})
return conn, nil
}