87 lines
2.0 KiB
Go
87 lines
2.0 KiB
Go
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
|
|
}
|