New upstream version 0.0~git20150427.b75d861
Dmitry Smirnov
4 years ago
0 | *.test |
0 | Copyright (c) 2013-2015 Tommi Virtanen. | |
1 | ||
2 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
3 | of this software and associated documentation files (the "Software"), to deal | |
4 | in the Software without restriction, including without limitation the rights | |
5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
6 | copies of the Software, and to permit persons to whom the Software is | |
7 | furnished to do so, subject to the following conditions: | |
8 | ||
9 | The above copyright notice and this permission notice shall be included in | |
10 | all copies or substantial portions of the Software. | |
11 | ||
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
18 | THE SOFTWARE. |
0 | // Package httpunix provides a HTTP transport (net/http.RoundTripper) | |
1 | // that uses Unix domain sockets instead of HTTP. | |
2 | // | |
3 | // This is useful for non-browser connections within the same host, as | |
4 | // it allows using the file system for credentials of both client | |
5 | // and server, and guaranteeing unique names. | |
6 | // | |
7 | // The URLs look like this: | |
8 | // | |
9 | // http+unix://LOCATION/PATH_ETC | |
10 | // | |
11 | // where LOCATION is translated to a file system path with | |
12 | // Transport.RegisterLocation, and PATH_ETC follow normal http: scheme | |
13 | // conventions. | |
14 | package httpunix | |
15 | ||
16 | import ( | |
17 | "bufio" | |
18 | "errors" | |
19 | "net" | |
20 | "net/http" | |
21 | "sync" | |
22 | "time" | |
23 | ) | |
24 | ||
25 | // Scheme is the URL scheme used for HTTP over UNIX domain sockets. | |
26 | const Scheme = "http+unix" | |
27 | ||
28 | // Transport is a http.RoundTripper that connects to Unix domain | |
29 | // sockets. | |
30 | type Transport struct { | |
31 | DialTimeout time.Duration | |
32 | RequestTimeout time.Duration | |
33 | ResponseHeaderTimeout time.Duration | |
34 | ||
35 | mu sync.Mutex | |
36 | // map a URL "hostname" to a UNIX domain socket path | |
37 | loc map[string]string | |
38 | } | |
39 | ||
40 | // RegisterLocation registers an URL location and maps it to the given | |
41 | // file system path. | |
42 | // | |
43 | // Calling RegisterLocation twice for the same location is a | |
44 | // programmer error, and causes a panic. | |
45 | func (t *Transport) RegisterLocation(loc string, path string) { | |
46 | t.mu.Lock() | |
47 | defer t.mu.Unlock() | |
48 | if t.loc == nil { | |
49 | t.loc = make(map[string]string) | |
50 | } | |
51 | if _, exists := t.loc[loc]; exists { | |
52 | panic("location " + loc + " already registered") | |
53 | } | |
54 | t.loc[loc] = path | |
55 | } | |
56 | ||
57 | var _ http.RoundTripper = (*Transport)(nil) | |
58 | ||
59 | // RoundTrip executes a single HTTP transaction. See | |
60 | // net/http.RoundTripper. | |
61 | func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { | |
62 | if req.URL == nil { | |
63 | return nil, errors.New("http+unix: nil Request.URL") | |
64 | } | |
65 | if req.URL.Scheme != Scheme { | |
66 | return nil, errors.New("unsupported protocol scheme: " + req.URL.Scheme) | |
67 | } | |
68 | if req.URL.Host == "" { | |
69 | return nil, errors.New("http+unix: no Host in request URL") | |
70 | } | |
71 | t.mu.Lock() | |
72 | path, ok := t.loc[req.URL.Host] | |
73 | t.mu.Unlock() | |
74 | if !ok { | |
75 | return nil, errors.New("unknown location: " + req.Host) | |
76 | } | |
77 | ||
78 | c, err := net.DialTimeout("unix", path, t.DialTimeout) | |
79 | if err != nil { | |
80 | return nil, err | |
81 | } | |
82 | r := bufio.NewReader(c) | |
83 | if t.RequestTimeout > 0 { | |
84 | c.SetWriteDeadline(time.Now().Add(t.RequestTimeout)) | |
85 | } | |
86 | if err := req.Write(c); err != nil { | |
87 | return nil, err | |
88 | } | |
89 | if t.ResponseHeaderTimeout > 0 { | |
90 | c.SetReadDeadline(time.Now().Add(t.ResponseHeaderTimeout)) | |
91 | } | |
92 | resp, err := http.ReadResponse(r, req) | |
93 | return resp, err | |
94 | } |
0 | package httpunix_test | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "log" | |
5 | "net" | |
6 | "net/http" | |
7 | "net/http/httputil" | |
8 | "time" | |
9 | ||
10 | "github.com/tv42/httpunix" | |
11 | ) | |
12 | ||
13 | func Example_clientStandalone() { | |
14 | // This example shows using a customized http.Client. | |
15 | u := &httpunix.Transport{ | |
16 | DialTimeout: 100 * time.Millisecond, | |
17 | RequestTimeout: 1 * time.Second, | |
18 | ResponseHeaderTimeout: 1 * time.Second, | |
19 | } | |
20 | u.RegisterLocation("myservice", "/path/to/socket") | |
21 | ||
22 | var client = http.Client{ | |
23 | Transport: u, | |
24 | } | |
25 | ||
26 | resp, err := client.Get("http+unix://myservice/urlpath/as/seen/by/server") | |
27 | if err != nil { | |
28 | log.Fatal(err) | |
29 | } | |
30 | buf, err := httputil.DumpResponse(resp, true) | |
31 | if err != nil { | |
32 | log.Fatal(err) | |
33 | } | |
34 | fmt.Printf("%s", buf) | |
35 | resp.Body.Close() | |
36 | } | |
37 | ||
38 | func Example_clientIntegrated() { | |
39 | // This example shows handling all net/http requests for the | |
40 | // http+unix URL scheme. | |
41 | u := &httpunix.Transport{ | |
42 | DialTimeout: 100 * time.Millisecond, | |
43 | RequestTimeout: 1 * time.Second, | |
44 | ResponseHeaderTimeout: 1 * time.Second, | |
45 | } | |
46 | u.RegisterLocation("myservice", "/path/to/socket") | |
47 | ||
48 | // If you want to use http: with the same client: | |
49 | t := &http.Transport{} | |
50 | t.RegisterProtocol(httpunix.Scheme, u) | |
51 | var client = http.Client{ | |
52 | Transport: t, | |
53 | } | |
54 | ||
55 | resp, err := client.Get("http+unix://myservice/urlpath/as/seen/by/server") | |
56 | if err != nil { | |
57 | log.Fatal(err) | |
58 | } | |
59 | buf, err := httputil.DumpResponse(resp, true) | |
60 | if err != nil { | |
61 | log.Fatal(err) | |
62 | } | |
63 | fmt.Printf("%s", buf) | |
64 | resp.Body.Close() | |
65 | } | |
66 | ||
67 | func Example_server() { | |
68 | l, err := net.Listen("unix", "/path/to/socket") | |
69 | if err != nil { | |
70 | log.Fatal(err) | |
71 | } | |
72 | defer l.Close() | |
73 | ||
74 | if err := http.Serve(l, nil); err != nil { | |
75 | log.Fatal(err) | |
76 | } | |
77 | } |