func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { transport := p.Transport if transport == nil { transport = http.DefaultTransport } outreq := new(http.Request) *outreq = *req // includes shallow copies of maps, but okay p.Director(outreq) outreq.Proto = "HTTP/1.1" outreq.ProtoMajor = 1 outreq.ProtoMinor = 1 outreq.Close = false // Remove hop-by-hop headers to the backend. Especially // important is "Connection" because we want a persistent // connection, regardless of what the client sent to us. This // is modifying the same underlying map from req (shallow // copied above) so we only copy it if necessary. copiedHeaders := false for _, h := range hopHeaders { if outreq.Header.Get(h) != "" { if !copiedHeaders { outreq.Header = make(http.Header) copyHeader(outreq.Header, req.Header) copiedHeaders = true } outreq.Header.Del(h) } } if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { // If we aren't the first proxy retain prior // X-Forwarded-For information as a comma+space // separated list and fold multiple headers into one. if prior, ok := outreq.Header["X-Forwarded-For"]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } outreq.Header.Set("X-Forwarded-For", clientIP) } res, err := transport.RoundTrip(outreq) if err != nil { p.logf("http: proxy error: %v", err) rw.WriteHeader(http.StatusInternalServerError) return } defer res.Body.Close() for _, h := range hopHeaders { res.Header.Del(h) } copyHeader(rw.Header(), res.Header) rw.WriteHeader(res.StatusCode) p.copyResponse(rw, res.Body) }
// RequestFromMap creates an http.Request from CGI variables. // The returned Request's Body field is not populated. func RequestFromMap(params map[string]string) (*http.Request, error) { r := new(http.Request) r.Method = params["REQUEST_METHOD"] if r.Method == "" { return nil, errors.New("cgi: no REQUEST_METHOD in environment") } r.Proto = params["SERVER_PROTOCOL"] var ok bool r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto) if !ok { return nil, errors.New("cgi: invalid SERVER_PROTOCOL version") } r.Close = true r.Trailer = http.Header{} r.Header = http.Header{} r.Host = params["HTTP_HOST"] if lenstr := params["CONTENT_LENGTH"]; lenstr != "" { clen, err := strconv.ParseInt(lenstr, 10, 64) if err != nil { return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr) } r.ContentLength = clen } if ct := params["CONTENT_TYPE"]; ct != "" { r.Header.Set("Content-Type", ct) } // Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers for k, v := range params { if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" { continue } r.Header.Add(strings.Replace(k[5:], "_", "-", -1), v) } // TODO: cookies. parsing them isn't exported, though. uriStr := params["REQUEST_URI"] if uriStr == "" { // Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING. uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"] s := params["QUERY_STRING"] if s != "" { uriStr += "?" + s } } // There's apparently a de-facto standard for this. // http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636 if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" { r.TLS = &tls.ConnectionState{HandshakeComplete: true} } if r.Host != "" { // Hostname is provided, so we can reasonably construct a URL. rawurl := r.Host + uriStr if r.TLS == nil { rawurl = "http://" + rawurl } else { rawurl = "https://" + rawurl } url, err := url.Parse(rawurl) if err != nil { return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl) } r.URL = url } // Fallback logic if we don't have a Host header or the URL // failed to parse if r.URL == nil { url, err := url.Parse(uriStr) if err != nil { return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr) } r.URL = url } // Request.RemoteAddr has its port set by Go's standard http // server, so we do here too. We don't have one, though, so we // use a dummy one. r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], "0") return r, nil }