func (f *Filter) RoundTrip(ctx *filters.Context, req *http.Request) (*filters.Context, *http.Response, error) { switch req.Method { case "CONNECT": glog.Infof("%s \"DIRECT %s %s %s\" - -", req.RemoteAddr, req.Method, req.Host, req.Proto) remote, err := f.transport.Dial("tcp", req.Host) if err != nil { return ctx, nil, err } switch req.Proto { case "HTTP/2.0": rw := ctx.GetResponseWriter() io.WriteString(rw, "HTTP/1.1 200 OK\r\n\r\n") go httpproxy.IoCopy(remote, req.Body) httpproxy.IoCopy(rw, remote) case "HTTP/1.1", "HTTP/1.0": rw := ctx.GetResponseWriter() hijacker, ok := rw.(http.Hijacker) if !ok { return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments Hijacker", rw) } local, _, err := hijacker.Hijack() if err != nil { return ctx, nil, err } local.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) go httpproxy.IoCopy(remote, local) httpproxy.IoCopy(local, remote) default: glog.Warningf("Unkown req=%#v", req) } ctx.SetHijacked(true) return ctx, nil, nil case "PRI": //TODO: fix for http2 return ctx, nil, nil default: resp, err := f.transport.RoundTrip(req) if err != nil { glog.Errorf("%s \"DIRECT %s %s %s\" error: %s", req.RemoteAddr, req.Method, req.URL.String(), req.Proto, err) data := err.Error() resp = &http.Response{ Status: "502 Bad Gateway", StatusCode: 502, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Request: req, Close: true, ContentLength: int64(len(data)), Body: ioutil.NopCloser(bytes.NewReader([]byte(data))), } err = nil } else { if req.RemoteAddr != "" { glog.Infof("%s \"DIRECT %s %s %s\" %d %s", req.RemoteAddr, req.Method, req.URL.String(), req.Proto, resp.StatusCode, resp.Header.Get("Content-Length")) } } if f.ratelimt.Rate > 0 && resp.ContentLength > f.ratelimt.Threshold { glog.V(2).Infof("RateLimit %#v rate to %#v", req.URL.String(), f.ratelimt.Rate) resp.Body = httpproxy.NewRateLimitReader(resp.Body, f.ratelimt.Rate, f.ratelimt.Capacity) } return ctx, resp, err } }
func (f *Filter) RoundTrip(ctx *filters.Context, req *http.Request) (*filters.Context, *http.Response, error) { switch req.Method { case "CONNECT": glog.Infof("%s \"DIRECT %s %s %s\" - -", req.RemoteAddr, req.Method, req.Host, req.Proto) rconn, err := f.transport.Dial("tcp", req.Host) if err != nil { return ctx, nil, err } rw := ctx.GetResponseWriter() hijacker, ok := rw.(http.Hijacker) if !ok { return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments http.Hijacker", rw) } flusher, ok := rw.(http.Flusher) if !ok { return ctx, nil, fmt.Errorf("http.ResponseWriter(%#v) does not implments http.Flusher", rw) } rw.WriteHeader(http.StatusOK) flusher.Flush() lconn, _, err := hijacker.Hijack() if err != nil { return ctx, nil, fmt.Errorf("%#v.Hijack() error: %v", hijacker, err) } defer lconn.Close() go httpproxy.IoCopy(rconn, lconn) httpproxy.IoCopy(lconn, rconn) ctx.SetHijacked(true) return ctx, nil, nil case "PRI": //TODO: fix for http2 return ctx, nil, nil default: resp, err := f.transport.RoundTrip(req) if err == ErrLoopbackAddr { http.FileServer(http.Dir(".")).ServeHTTP(ctx.GetResponseWriter(), req) ctx.SetHijacked(true) return ctx, nil, nil } if err != nil { glog.Errorf("%s \"DIRECT %s %s %s\" error: %s", req.RemoteAddr, req.Method, req.URL.String(), req.Proto, err) data := err.Error() resp = &http.Response{ Status: "502 Bad Gateway", StatusCode: 502, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Request: req, Close: true, ContentLength: int64(len(data)), Body: ioutil.NopCloser(bytes.NewReader([]byte(data))), } err = nil } else { if req.RemoteAddr != "" { glog.Infof("%s \"DIRECT %s %s %s\" %d %s", req.RemoteAddr, req.Method, req.URL.String(), req.Proto, resp.StatusCode, resp.Header.Get("Content-Length")) } } if f.ratelimt.Rate > 0 && resp.ContentLength > f.ratelimt.Threshold { glog.V(2).Infof("RateLimit %#v rate to %#v", req.URL.String(), f.ratelimt.Rate) resp.Body = httpproxy.NewRateLimitReader(resp.Body, f.ratelimt.Rate, f.ratelimt.Capacity) } return ctx, resp, err } }