commit 15108412e6d608ebaaadbf8df84f62a4da96afe7 Author: David Fifield david@bamsoftware.com Date: Mon Jan 14 22:52:14 2019 -0700
Refactor the helper into an http.RoundTripper. --- meek-client/helper.go | 82 ++++++++++++++++++++++++++++++++++++++-------- meek-client/meek-client.go | 23 ++++++++----- 2 files changed, 83 insertions(+), 22 deletions(-)
diff --git a/meek-client/helper.go b/meek-client/helper.go index 6b89f5a..f412aaa 100644 --- a/meek-client/helper.go +++ b/meek-client/helper.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "net" "net/http" + "net/textproto" "net/url" "strconv" "time" @@ -40,6 +41,11 @@ type ProxySpec struct { Port int `json:"port"` }
+type HelperRoundTripper struct { + ReadTimeout time.Duration + WriteTimeout time.Duration +} + // Return a ProxySpec suitable for the proxy URL in u. func makeProxySpec(u *url.URL) (*ProxySpec, error) { spec := new(ProxySpec) @@ -80,9 +86,7 @@ func makeProxySpec(u *url.URL) (*ProxySpec, error) { return spec, nil }
-// Do an HTTP roundtrip through the configured browser extension, using the -// payload data in buf and the request metadata in info. -func roundTripWithHelper(buf []byte, info *RequestInfo) (*http.Response, error) { +func (rt *HelperRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { s, err := net.DialTCP("tcp", nil, options.HelperAddr) if err != nil { return nil, err @@ -90,28 +94,51 @@ func roundTripWithHelper(buf []byte, info *RequestInfo) (*http.Response, error) defer s.Close()
// Encode our JSON. - req := JSONRequest{ - Method: "POST", - URL: info.URL.String(), + jsonReq := JSONRequest{ + Method: req.Method, + URL: req.URL.String(), Header: make(map[string]string), - Body: buf, + Body: make([]byte, 0), } - req.Header["X-Session-Id"] = info.SessionID - if info.Host != "" { - req.Header["Host"] = info.Host + + // We take only the first value for each header key, due to limitations + // in the helper JSON protocol. + for key, values := range req.Header { + if len(values) == 0 { + continue + } + value := values[0] + key = textproto.CanonicalMIMEHeaderKey(key) + jsonReq.Header[key] = value + } + // req.Host overrides req.Header. + if req.Host != "" { + jsonReq.Header["Host"] = req.Host } - req.Proxy, err = makeProxySpec(options.ProxyURL) + + if req.Body != nil { + jsonReq.Body, err = ioutil.ReadAll(req.Body) + if err != nil { + return nil, err + } + err = req.Body.Close() + if err != nil { + return nil, err + } + } + + jsonReq.Proxy, err = makeProxySpec(options.ProxyURL) if err != nil { return nil, err } - encReq, err := json.Marshal(&req) + encReq, err := json.Marshal(&jsonReq) if err != nil { return nil, err } // log.Printf("encoded %s", encReq)
// Send the request. - s.SetWriteDeadline(time.Now().Add(helperWriteTimeout)) + s.SetWriteDeadline(time.Now().Add(rt.WriteTimeout)) err = binary.Write(s, binary.BigEndian, uint32(len(encReq))) if err != nil { return nil, err @@ -123,7 +150,7 @@ func roundTripWithHelper(buf []byte, info *RequestInfo) (*http.Response, error)
// Read the response. var length uint32 - s.SetReadDeadline(time.Now().Add(helperReadTimeout)) + s.SetReadDeadline(time.Now().Add(rt.ReadTimeout)) err = binary.Read(s, binary.BigEndian, &length) if err != nil { return nil, err @@ -159,3 +186,30 @@ func roundTripWithHelper(buf []byte, info *RequestInfo) (*http.Response, error) } return &resp, nil } + +// Do an HTTP roundtrip through the configured browser extension, using the +// payload data in buf and the request metadata in info. +func roundTripWithHelper(buf []byte, info *RequestInfo) (*http.Response, error) { + var body io.Reader + if len(buf) > 0 { + // Leave body == nil when buf is empty. A nil body is an + // explicit signal that the body is empty. An empty + // *bytes.Reader or the magic value http.NoBody are supposed to + // be equivalent ways to signal an empty body, but in Go 1.8 the + // HTTP/2 code only understands nil. Not leaving body == nil + // causes the Content-Length header to be omitted from HTTP/2 + // requests, which in some cases can cause the server to return + // a 411 "Length Required" error. See + // https://bugs.torproject.org/22865. + body = bytes.NewReader(buf) + } + req, err := http.NewRequest("POST", info.URL.String(), body) + if err != nil { + return nil, err + } + if info.Host != "" { + req.Host = info.Host + } + req.Header.Set("X-Session-Id", info.SessionID) + return helperRoundTripper.RoundTrip(req) +} diff --git a/meek-client/meek-client.go b/meek-client/meek-client.go index bbacbb4..3923b9a 100644 --- a/meek-client/meek-client.go +++ b/meek-client/meek-client.go @@ -83,10 +83,16 @@ const (
var ptInfo pt.ClientInfo
-// We use this RoundTripper to make all our requests (when --helper is not -// used). We use the defaults, except we take control of the Proxy setting +// We use this RoundTripper to make all our requests when --helper is not +// in effect. We use the defaults, except we take control of the Proxy setting // (notably, disabling the default ProxyFromEnvironment). -var httpTransport *http.Transport = http.DefaultTransport.(*http.Transport) +var httpRoundTripper *http.Transport = http.DefaultTransport.(*http.Transport) + +// We use this RoundTripper when --helper is in effect. +var helperRoundTripper = &HelperRoundTripper{ + ReadTimeout: helperReadTimeout, + WriteTimeout: helperWriteTimeout, +}
// Store for command line options. var options struct { @@ -137,7 +143,7 @@ func roundTripWithHTTP(buf []byte, info *RequestInfo) (*http.Response, error) { req.Host = info.Host } req.Header.Set("X-Session-Id", info.SessionID) - return httpTransport.RoundTrip(req) + return httpRoundTripper.RoundTrip(req) }
// Do a roundtrip, trying at most limit times if there is an HTTP status other @@ -411,9 +417,10 @@ func main() { } }
- // Disable the default ProxyFromEnvironment setting. httpTransport.Proxy - // is overridden below if options.ProxyURL is set. - httpTransport.Proxy = nil + // Disable the default ProxyFromEnvironment setting. + // httpRoundTripper.Proxy is overridden below if options.ProxyURL is + // set. + httpRoundTripper.Proxy = nil
// Command-line proxy overrides managed configuration. if options.ProxyURL == nil { @@ -427,7 +434,7 @@ func main() { log.Fatal(fmt.Sprintf("proxy error: %s", err)) } log.Printf("using proxy %s", options.ProxyURL.String()) - httpTransport.Proxy = http.ProxyURL(options.ProxyURL) + httpRoundTripper.Proxy = http.ProxyURL(options.ProxyURL) if ptInfo.ProxyURL != nil { pt.ProxyDone() }
tor-commits@lists.torproject.org