mirror of
https://github.com/willnorris/imageproxy.git
synced 2024-12-16 21:56:43 -05:00
stash changes
This commit is contained in:
parent
1fcd100d16
commit
0afca60c21
2 changed files with 29 additions and 125 deletions
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in a new issue