package httpconn import ( "bufio" "context" "crypto/tls" "errors" "io" "net" "net/http" "net/url" "time" "git.crumpington.com/public/jldb/lib/errs" ) 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 }