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	"bufio"
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           time.Duration
33	RequestTimeout        time.Duration
34	ResponseHeaderTimeout time.Duration
35
36	mu sync.Mutex
37	// map a URL "hostname" to a UNIX domain socket path
38	loc map[string]string
39}
40
41// RegisterLocation registers an URL location and maps it to the given
42// file system path.
43//
44// Calling RegisterLocation twice for the same location is a
45// programmer error, and causes a panic.
46func (t *Transport) RegisterLocation(loc string, path string) {
47	t.mu.Lock()
48	defer t.mu.Unlock()
49	if t.loc == nil {
50		t.loc = make(map[string]string)
51	}
52	if _, exists := t.loc[loc]; exists {
53		panic("location " + loc + " already registered")
54	}
55	t.loc[loc] = path
56}
57
58var _ http.RoundTripper = (*Transport)(nil)
59
60// RoundTrip executes a single HTTP transaction. See
61// net/http.RoundTripper.
62func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
63	if req.URL == nil {
64		return nil, errors.New("http+unix: nil Request.URL")
65	}
66	if req.URL.Scheme != Scheme {
67		return nil, errors.New("unsupported protocol scheme: " + req.URL.Scheme)
68	}
69	if req.URL.Host == "" {
70		return nil, errors.New("http+unix: no Host in request URL")
71	}
72	t.mu.Lock()
73	path, ok := t.loc[req.URL.Host]
74	t.mu.Unlock()
75	if !ok {
76		return nil, errors.New("unknown location: " + req.Host)
77	}
78
79	c, err := net.DialTimeout("unix", path, t.DialTimeout)
80	if err != nil {
81		return nil, err
82	}
83	r := bufio.NewReader(c)
84	if t.RequestTimeout > 0 {
85		c.SetWriteDeadline(time.Now().Add(t.RequestTimeout))
86	}
87	if err := req.Write(c); err != nil {
88		return nil, err
89	}
90	if t.ResponseHeaderTimeout > 0 {
91		c.SetReadDeadline(time.Now().Add(t.ResponseHeaderTimeout))
92	}
93	resp, err := http.ReadResponse(r, req)
94	return resp, err
95}
96