1// Package httpunix provides a HTTP transport (net/http.RoundTripper) 2// that uses Unix domain sockets instead of HTTP. 3// 4// This is useful for non-browser connections within the same host, as 5// it allows using the file system for credentials of both client 6// and server, and guaranteeing unique names. 7// 8// The URLs look like this: 9// 10// http+unix://LOCATION/PATH_ETC 11// 12// where LOCATION is translated to a file system path with 13// Transport.RegisterLocation, and PATH_ETC follow normal http: scheme 14// conventions. 15package httpunix 16 17import ( 18 "context" 19 "errors" 20 "net" 21 "net/http" 22 "sync" 23 "time" 24) 25 26// Scheme is the URL scheme used for HTTP over UNIX domain sockets. 27const Scheme = "http+unix" 28 29// Transport is a http.RoundTripper that connects to Unix domain 30// sockets. 31type Transport struct { 32 // DialTimeout is deprecated. Use context instead. 33 DialTimeout time.Duration 34 // RequestTimeout is deprecated and has no effect. 35 RequestTimeout time.Duration 36 // ResponseHeaderTimeout is deprecated. Use context instead. 37 ResponseHeaderTimeout time.Duration 38 39 onceInit sync.Once 40 transport http.Transport 41 42 mu sync.Mutex 43 // map a URL "hostname" to a UNIX domain socket path 44 loc map[string]string 45} 46 47func (t *Transport) initTransport() { 48 t.transport.DialContext = t.dialContext 49 t.transport.DialTLS = t.dialTLS 50 t.transport.DisableCompression = true 51 t.transport.ResponseHeaderTimeout = t.ResponseHeaderTimeout 52} 53 54func (t *Transport) getTransport() *http.Transport { 55 t.onceInit.Do(t.initTransport) 56 return &t.transport 57} 58 59func (t *Transport) dialContext(ctx context.Context, network, addr string) (net.Conn, error) { 60 if network != "tcp" { 61 return nil, errors.New("httpunix internals are confused: network=" + network) 62 } 63 host, port, err := net.SplitHostPort(addr) 64 if err != nil { 65 return nil, err 66 } 67 if port != "80" { 68 return nil, errors.New("httpunix internals are confused: port=" + port) 69 } 70 t.mu.Lock() 71 path, ok := t.loc[host] 72 t.mu.Unlock() 73 if !ok { 74 return nil, errors.New("unknown location: " + host) 75 } 76 d := net.Dialer{ 77 Timeout: t.DialTimeout, 78 } 79 return d.DialContext(ctx, "unix", path) 80} 81 82func (t *Transport) dialTLS(network, addr string) (net.Conn, error) { 83 return nil, errors.New("httpunix: TLS over UNIX domain sockets is not supported") 84} 85 86// RegisterLocation registers an URL location and maps it to the given 87// file system path. 88// 89// Calling RegisterLocation twice for the same location is a 90// programmer error, and causes a panic. 91func (t *Transport) RegisterLocation(loc string, path string) { 92 t.mu.Lock() 93 defer t.mu.Unlock() 94 if t.loc == nil { 95 t.loc = make(map[string]string) 96 } 97 if _, exists := t.loc[loc]; exists { 98 panic("location " + loc + " already registered") 99 } 100 t.loc[loc] = path 101} 102 103var _ http.RoundTripper = (*Transport)(nil) 104 105// RoundTrip executes a single HTTP transaction. See 106// net/http.RoundTripper. 107func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { 108 if req.URL == nil { 109 return nil, errors.New("http+unix: nil Request.URL") 110 } 111 if req.URL.Scheme != Scheme { 112 return nil, errors.New("unsupported protocol scheme: " + req.URL.Scheme) 113 } 114 if req.URL.Host == "" { 115 return nil, errors.New("http+unix: no Host in request URL") 116 } 117 118 tt := t.getTransport() 119 req = req.Clone(req.Context()) 120 // get http.Transport to cooperate 121 req.URL.Scheme = "http" 122 return tt.RoundTrip(req) 123} 124