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