mirror of
https://github.com/hanzoai/oxy.git
synced 2026-09-30 23:42:31 +00:00
Start recovering all tests
This commit is contained in:
+11
-30
@@ -1,18 +1,15 @@
|
||||
// package forwarder implements http handler that forwards requests to remote server
|
||||
// and serves back the response
|
||||
package forwarder
|
||||
package forward
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Router tells forwarder where to route the request
|
||||
type Router interface {
|
||||
Route(r *http.Request) (*url.URL, error)
|
||||
}
|
||||
"github.com/mailgun/oxy/netutils"
|
||||
)
|
||||
|
||||
// ReqRewriter can alter request headers and body
|
||||
type ReqRewriter interface {
|
||||
@@ -45,15 +42,12 @@ func ErrorHandler(h http.Handler) optSetter {
|
||||
|
||||
type Forwarder struct {
|
||||
errHandler http.Handler
|
||||
router Router
|
||||
roundTripper http.RoundTripper
|
||||
rewriter ReqRewriter
|
||||
}
|
||||
|
||||
func New(router Router, setters ...optSetter) (*Forwarder, error) {
|
||||
f := &Forwarder{
|
||||
router: router,
|
||||
}
|
||||
func New(setters ...optSetter) (*Forwarder, error) {
|
||||
f := &Forwarder{}
|
||||
for _, s := range setters {
|
||||
if err := s(f); err != nil {
|
||||
return nil, err
|
||||
@@ -68,24 +62,19 @@ func New(router Router, setters ...optSetter) (*Forwarder, error) {
|
||||
h = "localhost"
|
||||
}
|
||||
f.rewriter = &HeaderRewriter{TrustForwardHeader: true, Hostname: h}
|
||||
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (l *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
u, err := l.router.Route(req)
|
||||
func (f *Forwarder) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
response, err := f.roundTripper.RoundTrip(f.copyRequest(req, req.URL))
|
||||
if err != nil {
|
||||
l.errHandler.ServeHTTP(w, req)
|
||||
f.errHandler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
response, err := l.roundTripper.RoundTrip(l.copyRequest(req, u))
|
||||
if err != nil {
|
||||
l.errHandler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
copyHeaders(w.Header(), response.Header)
|
||||
netutils.CopyHeaders(w.Header(), response.Header)
|
||||
w.WriteHeader(response.StatusCode)
|
||||
io.Copy(w, response.Body)
|
||||
response.Body.Close()
|
||||
@@ -109,14 +98,6 @@ func (l *Forwarder) copyRequest(req *http.Request, u *url.URL) *http.Request {
|
||||
outReq.Close = false
|
||||
|
||||
outReq.Header = make(http.Header)
|
||||
copyHeaders(outReq.Header, req.Header)
|
||||
netutils.CopyHeaders(outReq.Header, req.Header)
|
||||
return outReq
|
||||
}
|
||||
|
||||
func copyHeaders(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package forward
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/mailgun/oxy/testutils"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
func TestFwd(t *testing.T) { TestingT(t) }
|
||||
|
||||
type FwdSuite struct{}
|
||||
|
||||
var _ = Suite(&FwdSuite{})
|
||||
|
||||
func (s *FwdSuite) TestForwardSimple(c *C) {
|
||||
called := false
|
||||
srv := testutils.NewTestServer(func(w http.ResponseWriter, req *http.Request) {
|
||||
called = true
|
||||
w.Write([]byte("hello"))
|
||||
})
|
||||
defer srv.Close()
|
||||
|
||||
f, err := New()
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
proxy := testutils.NewTestServer(func(w http.ResponseWriter, req *http.Request) {
|
||||
req.URL = testutils.ParseURI(srv.URL)
|
||||
f.ServeHTTP(w, req)
|
||||
})
|
||||
defer proxy.Close()
|
||||
|
||||
re, body, err := testutils.Get(proxy.URL, testutils.Opts{})
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(string(body), Equals, "hello")
|
||||
c.Assert(re.StatusCode, Equals, http.StatusOK)
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package forwarder
|
||||
package forward
|
||||
|
||||
const (
|
||||
XForwardedProto = "X-Forwarded-Proto"
|
||||
|
||||
+4
-2
@@ -1,9 +1,11 @@
|
||||
package forwarder
|
||||
package forward
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mailgun/oxy/netutils"
|
||||
)
|
||||
|
||||
// Rewriter is responsible for removing hop-by-hop headers and setting forwarding headers
|
||||
@@ -37,5 +39,5 @@ func (rw *HeaderRewriter) Rewrite(req *http.Request) {
|
||||
|
||||
// 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.
|
||||
removeHeaders(HopHeaders, req.Header)
|
||||
netutils.RemoveHeaders(req.Header, HopHeaders...)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package netutils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type ProxyWriter struct {
|
||||
@@ -21,3 +22,39 @@ func (p *ProxyWriter) WriteHeader(code int) {
|
||||
p.Code = code
|
||||
p.WriteHeader(code)
|
||||
}
|
||||
|
||||
// CopyURL provides update safe copy by avoiding shallow copying User field
|
||||
func CopyURL(i *url.URL) *url.URL {
|
||||
out := *i
|
||||
if i.User != nil {
|
||||
out.User = &(*i.User)
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
// CopyHeaders copies http headers from source to destination, it
|
||||
// does not overide, but adds multiple headers
|
||||
func CopyHeaders(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HasHeaders determines whether any of the header names is present in the http headers
|
||||
func HasHeaders(names []string, headers http.Header) bool {
|
||||
for _, h := range names {
|
||||
if headers.Get(h) != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveHeaders removes the header with the given names from the headers map
|
||||
func RemoveHeaders(headers http.Header, names ...string) {
|
||||
for _, h := range names {
|
||||
headers.Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package netutils
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
. "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
func TestUtils(t *testing.T) { TestingT(t) }
|
||||
|
||||
type NetUtilsSuite struct{}
|
||||
|
||||
var _ = Suite(&NetUtilsSuite{})
|
||||
|
||||
// Make sure copy does it right, so the copied url
|
||||
// is safe to alter without modifying the other
|
||||
func (s *NetUtilsSuite) TestCopyUrl(c *C) {
|
||||
urlA := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "localhost:5000",
|
||||
Path: "/upstream",
|
||||
Opaque: "opaque",
|
||||
RawQuery: "a=1&b=2",
|
||||
Fragment: "#hello",
|
||||
User: &url.Userinfo{},
|
||||
}
|
||||
urlB := CopyURL(urlA)
|
||||
c.Assert(urlB, DeepEquals, urlB)
|
||||
urlB.Scheme = "https"
|
||||
c.Assert(urlB, Not(DeepEquals), urlA)
|
||||
}
|
||||
|
||||
// Make sure copy headers is not shallow and copies all headers
|
||||
func (s *NetUtilsSuite) TestCopyHeaders(c *C) {
|
||||
source, destination := make(http.Header), make(http.Header)
|
||||
source.Add("a", "b")
|
||||
source.Add("c", "d")
|
||||
|
||||
CopyHeaders(destination, source)
|
||||
|
||||
c.Assert(destination.Get("a"), Equals, "b")
|
||||
c.Assert(destination.Get("c"), Equals, "d")
|
||||
|
||||
// make sure that altering source does not affect the destination
|
||||
source.Del("a")
|
||||
c.Assert(source.Get("a"), Equals, "")
|
||||
c.Assert(destination.Get("a"), Equals, "b")
|
||||
}
|
||||
|
||||
func (s *NetUtilsSuite) TestHasHeaders(c *C) {
|
||||
source := make(http.Header)
|
||||
source.Add("a", "b")
|
||||
source.Add("c", "d")
|
||||
c.Assert(HasHeaders([]string{"a", "f"}, source), Equals, true)
|
||||
c.Assert(HasHeaders([]string{"i", "j"}, source), Equals, false)
|
||||
}
|
||||
|
||||
func (s *NetUtilsSuite) TestRemoveHeaders(c *C) {
|
||||
source := make(http.Header)
|
||||
source.Add("a", "b")
|
||||
source.Add("a", "m")
|
||||
source.Add("c", "d")
|
||||
RemoveHeaders(source, "a")
|
||||
c.Assert(source.Get("a"), Equals, "")
|
||||
c.Assert(source.Get("c"), Equals, "d")
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package retry
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/mailgun/oxy/netutils"
|
||||
@@ -38,6 +39,20 @@ func (r *Retry) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
c := &context{r: req}
|
||||
for i := 0; i < r.maxAttempts; i++ {
|
||||
pw := &netutils.ProxyWriter{W: w}
|
||||
|
||||
// this is not the first attempt, we need to rewind the body
|
||||
if i != 0 {
|
||||
seeker, ok := req.Body.(io.Seeker)
|
||||
if !ok {
|
||||
r.errHandler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
_, err := seeker.Seek(0, 0)
|
||||
if err != nil {
|
||||
r.errHandler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
r.next.ServeHTTP(pw, req)
|
||||
c.attempt = i + 1
|
||||
if !r.predicate(c) {
|
||||
|
||||
@@ -163,7 +163,7 @@ func (rb *Rebalancer) upsertServer(u *url.URL, weight int) {
|
||||
if s, i := rb.findServer(u); i != -1 {
|
||||
s.origWeight = weight
|
||||
}
|
||||
rb.servers = append(rb.servers, &rbServer{url: copyURL(u), origWeight: weight, curWeight: weight})
|
||||
rb.servers = append(rb.servers, &rbServer{url: netutils.CopyURL(u), origWeight: weight, curWeight: weight})
|
||||
}
|
||||
|
||||
func (r *Rebalancer) findServer(u *url.URL) (*rbServer, int) {
|
||||
|
||||
+5
-11
@@ -1,4 +1,4 @@
|
||||
// package roundrobing implements dynamic weighted round robin load balancer http handler
|
||||
// package roundrobin implements dynamic weighted round robin load balancer http handler
|
||||
package roundrobin
|
||||
|
||||
import (
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/mailgun/oxy/netutils"
|
||||
)
|
||||
|
||||
// Weight is an optional functional argument that sets weight of the server
|
||||
@@ -58,7 +60,7 @@ func (r *RoundRobin) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
r.errHandler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
req.URL = copyURL(srv.url)
|
||||
req.URL = netutils.CopyURL(srv.url)
|
||||
r.next.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
@@ -136,7 +138,7 @@ func (rr *RoundRobin) UpsertServer(u *url.URL, options ...serverSetter) error {
|
||||
return fmt.Errorf("Server %v already exists", u)
|
||||
}
|
||||
|
||||
srv := &server{url: copyURL(u)}
|
||||
srv := &server{url: netutils.CopyURL(u)}
|
||||
for _, o := range options {
|
||||
if err := o(srv); err != nil {
|
||||
return err
|
||||
@@ -215,14 +217,6 @@ type server struct {
|
||||
// This is really a convenience, so users can add servers with smaller weights if they did not set it initially
|
||||
const defaultWeight = 10
|
||||
|
||||
func copyURL(i *url.URL) *url.URL {
|
||||
out := *i
|
||||
if i.User != nil {
|
||||
out.User = &(*i.User)
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
func sameURL(a, b *url.URL) bool {
|
||||
return a.Path == b.Path && a.Host == b.Host && a.Scheme == b.Scheme
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mailgun/oxy/netutils"
|
||||
)
|
||||
|
||||
func NewTestServer(handler http.HandlerFunc) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(handler))
|
||||
}
|
||||
|
||||
func NewTestResponder(response string) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(response))
|
||||
}))
|
||||
}
|
||||
|
||||
// ParseURI is the version of url.ParseRequestURI that panics if incorrect, helpful to shorten the tests
|
||||
func ParseURI(uri string) *url.URL {
|
||||
out, err := url.ParseRequestURI(uri)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type Opts struct {
|
||||
Host string
|
||||
Method string
|
||||
Body string
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
func MakeRequest(url string, opts Opts) (*http.Response, []byte, error) {
|
||||
method := "GET"
|
||||
if opts.Method != "" {
|
||||
method = opts.Method
|
||||
}
|
||||
request, _ := http.NewRequest(method, url, strings.NewReader(opts.Body))
|
||||
if opts.Headers != nil {
|
||||
netutils.CopyHeaders(request.Header, opts.Headers)
|
||||
}
|
||||
|
||||
if len(opts.Host) != 0 {
|
||||
request.Host = opts.Host
|
||||
}
|
||||
|
||||
var tr *http.Transport
|
||||
if strings.HasPrefix(url, "https") {
|
||||
tr = &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
} else {
|
||||
tr = &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: tr,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return fmt.Errorf("No redirects")
|
||||
},
|
||||
}
|
||||
response, err := client.Do(request)
|
||||
if err == nil {
|
||||
bodyBytes, err := ioutil.ReadAll(response.Body)
|
||||
return response, bodyBytes, err
|
||||
}
|
||||
return response, nil, err
|
||||
}
|
||||
|
||||
func Get(url string, o Opts) (*http.Response, []byte, error) {
|
||||
o.Method = "GET"
|
||||
return MakeRequest(url, o)
|
||||
}
|
||||
Reference in New Issue
Block a user