0
Fork 0
mirror of https://github.com/willnorris/imageproxy.git synced 2024-12-16 21:56:43 -05:00

stash changes

This commit is contained in:
Will Norris 2018-05-15 12:09:56 -07:00
parent 1fcd100d16
commit 0afca60c21
2 changed files with 29 additions and 125 deletions

View file

@ -33,6 +33,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/gregjones/httpcache" "github.com/gregjones/httpcache"
tphttp "willnorris.com/go/imageproxy/third_party/http" tphttp "willnorris.com/go/imageproxy/third_party/http"
"willnorris.com/go/imageproxy/third_party/httputil"
) )
var emptyBody io.ReadCloser = ioutil.NopCloser(new(bytes.Buffer)) var emptyBody io.ReadCloser = ioutil.NopCloser(new(bytes.Buffer))
@ -66,6 +67,8 @@ type Proxy struct {
// If a call runs for longer than its time limit, a 504 Gateway Timeout // If a call runs for longer than its time limit, a 504 Gateway Timeout
// response is returned. A Timeout of zero means no timeout. // response is returned. A Timeout of zero means no timeout.
Timeout time.Duration Timeout time.Duration
rp *httputil.ReverseProxy
} }
// NewProxy constructs a new proxy. The provided http RoundTripper will be // NewProxy constructs a new proxy. The provided http RoundTripper will be
@ -91,6 +94,11 @@ func NewProxy(transport http.RoundTripper, cache Cache) *Proxy {
} }
proxy.Client = client proxy.Client = client
proxy.rp = &httputil.ReverseProxy{
Director: proxy.director,
Transport: client.Transport,
ModifyResponse: proxy.modifyResponse,
}
return &proxy return &proxy
} }
@ -106,80 +114,43 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
var h http.Handler = http.HandlerFunc(p.serveImage) var h http.Handler = p.rp
if p.Timeout > 0 { if p.Timeout > 0 {
h = tphttp.TimeoutHandler(h, p.Timeout, "Gateway timeout waiting for remote resource.") h = tphttp.TimeoutHandler(h, p.Timeout, "Gateway timeout waiting for remote resource.")
} }
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
} }
// serveImage handles incoming requests for proxied images. func (p *Proxy) director(req *http.Request) error {
func (p *Proxy) serveImage(w http.ResponseWriter, r *http.Request) { r, err := NewRequest(req, p.DefaultBaseURL)
req, err := NewRequest(r, p.DefaultBaseURL)
if err != nil { if err != nil {
p.writeError(w, err) return err
return
} }
// assign static settings from proxy to req.Options // assign static settings from proxy to r.Options
req.Options.ScaleUp = p.ScaleUp r.Options.ScaleUp = p.ScaleUp
if err := p.allowed(req); err != nil { if err := p.allowed(r); err != nil {
p.writeError(w, err) return err
return
} }
resp, err := p.Client.Get(req.String()) *req.URL = *r.URL
if err != nil { req.URL.Fragment = r.Options.String()
p.writeError(w, err) return nil
return }
}
defer resp.Body.Close()
func (p *Proxy) modifyResponse(resp *http.Response) error {
cached := resp.Header.Get(httpcache.XFromCache) cached := resp.Header.Get(httpcache.XFromCache)
glog.Infof("request: %v (served from cache: %v)", *req, cached == "1") if resp.Request != nil && resp.Request.URL != nil {
glog.Infof("request: %v (served from cache: %v)", resp.Request.URL.String(), cached == "1")
copyHeader(w.Header(), resp.Header, "Cache-Control", "Last-Modified", "Expires", "Etag", "Link")
if should304(r, resp) {
w.WriteHeader(http.StatusNotModified)
return
} }
copyHeader(w.Header(), resp.Header, "Content-Length", "Content-Type") if should304(resp.Request, resp) {
w.WriteHeader(resp.StatusCode) resp.StatusCode = http.StatusNotModified
io.Copy(w, resp.Body) resp.Body = emptyBody
}
// writerError writes err to the http response.
func (p *Proxy) writeError(w http.ResponseWriter, err error) {
type statusCoder interface {
StatusCode() int
} }
glog.Error(err) return nil
code := http.StatusBadGateway
if err, ok := err.(statusCoder); ok {
code = err.StatusCode()
}
http.Error(w, err.Error(), code)
}
// copyHeader copies header values from src to dst, adding to any existing
// values with the same header name. If keys is not empty, only those header
// keys will be copied.
func copyHeader(dst, src http.Header, keys ...string) {
if len(keys) == 0 {
for k, _ := range src {
keys = append(keys, k)
}
}
for _, key := range keys {
k := http.CanonicalHeaderKey(key)
for _, v := range src[k] {
dst.Add(k, v)
}
}
} }
// allowed determines whether the specified request contains an allowed // allowed determines whether the specified request contains an allowed
@ -307,7 +278,7 @@ func (t *TransformingTransport) RoundTrip(req *http.Request) (*http.Response, er
if should304(req, resp) { if should304(req, resp) {
// bare 304 response, full response will be used from cache // bare 304 response, full response will be used from cache
return &http.Response{StatusCode: http.StatusNotModified, Body: emptyBody}, nil return &http.Response{StatusCode: http.StatusNotModified, Body: emptyBody, Request: req}, nil
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -323,6 +294,7 @@ func (t *TransformingTransport) RoundTrip(req *http.Request) (*http.Response, er
glog.Errorf("error transforming image: %v", err) glog.Errorf("error transforming image: %v", err)
img = b img = b
} }
glog.Infof("image size after transform: %d", len(img))
// replay response with transformed image and updated content length // replay response with transformed image and updated content length
buf := new(bytes.Buffer) buf := new(bytes.Buffer)

View file

@ -24,78 +24,10 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"reflect"
"strings" "strings"
"testing" "testing"
) )
func TestCopyHeader(t *testing.T) {
tests := []struct {
dst, src http.Header
keys []string
want http.Header
}{
// empty
{http.Header{}, http.Header{}, nil, http.Header{}},
{http.Header{}, http.Header{}, []string{}, http.Header{}},
{http.Header{}, http.Header{}, []string{"A"}, http.Header{}},
// nothing to copy
{
dst: http.Header{"A": []string{"a1"}},
src: http.Header{},
keys: nil,
want: http.Header{"A": []string{"a1"}},
},
{
dst: http.Header{},
src: http.Header{"A": []string{"a"}},
keys: []string{"B"},
want: http.Header{},
},
// copy headers
{
dst: http.Header{},
src: http.Header{"A": []string{"a"}},
keys: nil,
want: http.Header{"A": []string{"a"}},
},
{
dst: http.Header{"A": []string{"a"}},
src: http.Header{"B": []string{"b"}},
keys: nil,
want: http.Header{"A": []string{"a"}, "B": []string{"b"}},
},
{
dst: http.Header{"A": []string{"a"}},
src: http.Header{"B": []string{"b"}, "C": []string{"c"}},
keys: []string{"B"},
want: http.Header{"A": []string{"a"}, "B": []string{"b"}},
},
{
dst: http.Header{"A": []string{"a1"}},
src: http.Header{"A": []string{"a2"}},
keys: nil,
want: http.Header{"A": []string{"a1", "a2"}},
},
}
for _, tt := range tests {
// copy dst map
got := make(http.Header)
for k, v := range tt.dst {
got[k] = v
}
copyHeader(got, tt.src, tt.keys...)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("copyHeader(%v, %v, %v) returned %v, want %v", tt.dst, tt.src, tt.keys, got, tt.want)
}
}
}
func TestAllowed(t *testing.T) { func TestAllowed(t *testing.T) {
whitelist := []string{"good"} whitelist := []string{"good"}
key := []byte("c0ffee") key := []byte("c0ffee")