Initial commit
This commit is contained in:
85
lib/httpconn/client.go
Normal file
85
lib/httpconn/client.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package httpconn
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"git.crumpington.com/public/jldb/lib/errs"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrInvalidStatus = errors.New("invalid status")
|
||||
|
||||
func Dial(rawURL string) (net.Conn, error) {
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, errs.Unexpected.WithErr(err)
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "https":
|
||||
return DialHTTPS(u.Host+":443", u.Path)
|
||||
case "http":
|
||||
return DialHTTP(u.Host, u.Path)
|
||||
default:
|
||||
return nil, errs.Unexpected.WithMsg("Unknown scheme: " + u.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func DialHTTPS(host, path string) (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
d := tls.Dialer{}
|
||||
conn, err := d.DialContext(ctx, "tcp", host)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, errs.IO.WithErr(err)
|
||||
}
|
||||
return finishDialing(conn, host, path)
|
||||
}
|
||||
|
||||
func DialHTTPSWithIP(ip, host, path string) (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
d := tls.Dialer{Config: &tls.Config{ServerName: host}}
|
||||
conn, err := d.DialContext(ctx, "tcp", ip)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return nil, errs.IO.WithErr(err)
|
||||
}
|
||||
return finishDialing(conn, host, path)
|
||||
}
|
||||
|
||||
func DialHTTP(host, path string) (net.Conn, error) {
|
||||
conn, err := net.Dial("tcp", host)
|
||||
if err != nil {
|
||||
return nil, errs.IO.WithErr(err)
|
||||
}
|
||||
return finishDialing(conn, host, path)
|
||||
}
|
||||
|
||||
func finishDialing(conn net.Conn, host, path string) (net.Conn, error) {
|
||||
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n")
|
||||
io.WriteString(conn, "Host: "+host+"\n\n")
|
||||
|
||||
// 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, errs.IO.WithErr(err)
|
||||
}
|
||||
|
||||
if resp.Status != "200 OK" {
|
||||
conn.Close()
|
||||
return nil, errs.IO.WithMsg("invalid status: %s", resp.Status)
|
||||
}
|
||||
|
||||
conn.SetDeadline(time.Time{})
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
42
lib/httpconn/conn_test.go
Normal file
42
lib/httpconn/conn_test.go
Normal 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
|
||||
})
|
||||
}
|
||||
32
lib/httpconn/server.go
Normal file
32
lib/httpconn/server.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user