Start recovering all tests

This commit is contained in:
Alexander Klizhentas
2014-12-09 23:28:30 -08:00
parent d2be2555b4
commit ee978f2a31
10 changed files with 266 additions and 45 deletions
+11 -30
View File
@@ -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)
}
}
}
+40
View File
@@ -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
View File
@@ -1,4 +1,4 @@
package forwarder
package forward
const (
XForwardedProto = "X-Forwarded-Proto"
+4 -2
View File
@@ -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...)
}
+37
View File
@@ -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)
}
}
+68
View File
@@ -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")
}
+15
View File
@@ -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) {
+1 -1
View File
@@ -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
View File
@@ -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
}
+84
View File
@@ -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)
}