-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor utils/http.go, fixes moby#11899
Signed-off-by: Antonio Murdaca <[email protected]>
- Loading branch information
Antonio Murdaca
committed
Mar 30, 2015
1 parent
d95fae0
commit 0995ab5
Showing
10 changed files
with
428 additions
and
215 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
This package provides helper functions for decorating a request with user agent | ||
versions, auth, meta headers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
// Package requestdecorator provides helper functions to decorate a request with | ||
// user agent versions, auth, meta headers. | ||
package requestdecorator | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/Sirupsen/logrus" | ||
) | ||
|
||
var ( | ||
ErrNilRequest = errors.New("request cannot be nil") | ||
) | ||
|
||
// UAVersionInfo is used to model UserAgent versions. | ||
type UAVersionInfo struct { | ||
Name string | ||
Version string | ||
} | ||
|
||
func NewUAVersionInfo(name, version string) UAVersionInfo { | ||
return UAVersionInfo{ | ||
Name: name, | ||
Version: version, | ||
} | ||
} | ||
|
||
func (vi *UAVersionInfo) isValid() bool { | ||
const stopChars = " \t\r\n/" | ||
name := vi.Name | ||
vers := vi.Version | ||
if len(name) == 0 || strings.ContainsAny(name, stopChars) { | ||
return false | ||
} | ||
if len(vers) == 0 || strings.ContainsAny(vers, stopChars) { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// Convert versions to a string and append the string to the string base. | ||
// | ||
// Each UAVersionInfo will be converted to a string in the format of | ||
// "product/version", where the "product" is get from the name field, while | ||
// version is get from the version field. Several pieces of verson information | ||
// will be concatinated and separated by space. | ||
func appendVersions(base string, versions ...UAVersionInfo) string { | ||
if len(versions) == 0 { | ||
return base | ||
} | ||
|
||
verstrs := make([]string, 0, 1+len(versions)) | ||
if len(base) > 0 { | ||
verstrs = append(verstrs, base) | ||
} | ||
|
||
for _, v := range versions { | ||
if !v.isValid() { | ||
continue | ||
} | ||
verstrs = append(verstrs, v.Name+"/"+v.Version) | ||
} | ||
return strings.Join(verstrs, " ") | ||
} | ||
|
||
// Decorator is used to change an instance of | ||
// http.Request. It could be used to add more header fields, | ||
// change body, etc. | ||
type Decorator interface { | ||
// ChangeRequest() changes the request accordingly. | ||
// The changed request will be returned or err will be non-nil | ||
// if an error occur. | ||
ChangeRequest(req *http.Request) (newReq *http.Request, err error) | ||
} | ||
|
||
// UserAgentDecorator appends the product/version to the user agent field | ||
// of a request. | ||
type UserAgentDecorator struct { | ||
Versions []UAVersionInfo | ||
} | ||
|
||
func (h *UserAgentDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { | ||
if req == nil { | ||
return req, ErrNilRequest | ||
} | ||
|
||
userAgent := appendVersions(req.UserAgent(), h.Versions...) | ||
if len(userAgent) > 0 { | ||
req.Header.Set("User-Agent", userAgent) | ||
} | ||
return req, nil | ||
} | ||
|
||
type MetaHeadersDecorator struct { | ||
Headers map[string][]string | ||
} | ||
|
||
func (h *MetaHeadersDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { | ||
if h.Headers == nil { | ||
return req, ErrNilRequest | ||
} | ||
for k, v := range h.Headers { | ||
req.Header[k] = v | ||
} | ||
return req, nil | ||
} | ||
|
||
type AuthDecorator struct { | ||
login string | ||
password string | ||
} | ||
|
||
func NewAuthDecorator(login, password string) Decorator { | ||
return &AuthDecorator{ | ||
login: login, | ||
password: password, | ||
} | ||
} | ||
|
||
func (self *AuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { | ||
if req == nil { | ||
return req, ErrNilRequest | ||
} | ||
req.SetBasicAuth(self.login, self.password) | ||
return req, nil | ||
} | ||
|
||
// RequestFactory creates an HTTP request | ||
// and applies a list of decorators on the request. | ||
type RequestFactory struct { | ||
decorators []Decorator | ||
} | ||
|
||
func NewRequestFactory(d ...Decorator) *RequestFactory { | ||
return &RequestFactory{ | ||
decorators: d, | ||
} | ||
} | ||
|
||
func (f *RequestFactory) AddDecorator(d ...Decorator) { | ||
f.decorators = append(f.decorators, d...) | ||
} | ||
|
||
func (f *RequestFactory) GetDecorators() []Decorator { | ||
return f.decorators | ||
} | ||
|
||
// NewRequest() creates a new *http.Request, | ||
// applies all decorators in the Factory on the request, | ||
// then applies decorators provided by d on the request. | ||
func (h *RequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...Decorator) (*http.Request, error) { | ||
req, err := http.NewRequest(method, urlStr, body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// By default, a nil factory should work. | ||
if h == nil { | ||
return req, nil | ||
} | ||
for _, dec := range h.decorators { | ||
req, _ = dec.ChangeRequest(req) | ||
} | ||
for _, dec := range d { | ||
req, _ = dec.ChangeRequest(req) | ||
} | ||
logrus.Debugf("%v -- HEADERS: %v", req.URL, req.Header) | ||
return req, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
package requestdecorator | ||
|
||
import ( | ||
"net/http" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestUAVersionInfo(t *testing.T) { | ||
uavi := NewUAVersionInfo("foo", "bar") | ||
if !uavi.isValid() { | ||
t.Fatalf("UAVersionInfo should be valid") | ||
} | ||
uavi = NewUAVersionInfo("", "bar") | ||
if uavi.isValid() { | ||
t.Fatalf("Expected UAVersionInfo to be invalid") | ||
} | ||
uavi = NewUAVersionInfo("foo", "") | ||
if uavi.isValid() { | ||
t.Fatalf("Expected UAVersionInfo to be invalid") | ||
} | ||
} | ||
|
||
func TestUserAgentDecorator(t *testing.T) { | ||
httpVersion := make([]UAVersionInfo, 2) | ||
httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion")) | ||
httpVersion = append(httpVersion, NewUAVersionInfo("name", "version")) | ||
uad := &UserAgentDecorator{ | ||
Versions: httpVersion, | ||
} | ||
|
||
req, err := http.NewRequest("GET", "/something", strings.NewReader("test")) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
reqDecorated, err := uad.ChangeRequest(req) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if reqDecorated.Header.Get("User-Agent") != "testname/testversion name/version" { | ||
t.Fatalf("Request should have User-Agent 'testname/testversion name/version'") | ||
} | ||
} | ||
|
||
func TestUserAgentDecoratorErr(t *testing.T) { | ||
httpVersion := make([]UAVersionInfo, 0) | ||
uad := &UserAgentDecorator{ | ||
Versions: httpVersion, | ||
} | ||
|
||
var req *http.Request | ||
_, err := uad.ChangeRequest(req) | ||
if err == nil { | ||
t.Fatalf("Expected to get ErrNilRequest instead no error was returned") | ||
} | ||
} | ||
|
||
func TestMetaHeadersDecorator(t *testing.T) { | ||
var headers = map[string][]string{ | ||
"key1": {"value1"}, | ||
"key2": {"value2"}, | ||
} | ||
mhd := &MetaHeadersDecorator{ | ||
Headers: headers, | ||
} | ||
|
||
req, err := http.NewRequest("GET", "/something", strings.NewReader("test")) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
reqDecorated, err := mhd.ChangeRequest(req) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
v, ok := reqDecorated.Header["key1"] | ||
if !ok { | ||
t.Fatalf("Expected to have header key1") | ||
} | ||
if v[0] != "value1" { | ||
t.Fatalf("Expected value for key1 isn't value1") | ||
} | ||
|
||
v, ok = reqDecorated.Header["key2"] | ||
if !ok { | ||
t.Fatalf("Expected to have header key2") | ||
} | ||
if v[0] != "value2" { | ||
t.Fatalf("Expected value for key2 isn't value2") | ||
} | ||
} | ||
|
||
func TestMetaHeadersDecoratorErr(t *testing.T) { | ||
mhd := &MetaHeadersDecorator{} | ||
|
||
var req *http.Request | ||
_, err := mhd.ChangeRequest(req) | ||
if err == nil { | ||
t.Fatalf("Expected to get ErrNilRequest instead no error was returned") | ||
} | ||
} | ||
|
||
func TestAuthDecorator(t *testing.T) { | ||
ad := NewAuthDecorator("test", "password") | ||
|
||
req, err := http.NewRequest("GET", "/something", strings.NewReader("test")) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
reqDecorated, err := ad.ChangeRequest(req) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
username, password, ok := reqDecorated.BasicAuth() | ||
if !ok { | ||
t.Fatalf("Cannot retrieve basic auth info from request") | ||
} | ||
if username != "test" { | ||
t.Fatalf("Expected username to be test, got %s", username) | ||
} | ||
if password != "password" { | ||
t.Fatalf("Expected password to be password, got %s", password) | ||
} | ||
} | ||
|
||
func TestAuthDecoratorErr(t *testing.T) { | ||
ad := &AuthDecorator{} | ||
|
||
var req *http.Request | ||
_, err := ad.ChangeRequest(req) | ||
if err == nil { | ||
t.Fatalf("Expected to get ErrNilRequest instead no error was returned") | ||
} | ||
} | ||
|
||
func TestRequestFactory(t *testing.T) { | ||
ad := NewAuthDecorator("test", "password") | ||
httpVersion := make([]UAVersionInfo, 2) | ||
httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion")) | ||
httpVersion = append(httpVersion, NewUAVersionInfo("name", "version")) | ||
uad := &UserAgentDecorator{ | ||
Versions: httpVersion, | ||
} | ||
|
||
requestFactory := NewRequestFactory(ad, uad) | ||
|
||
if dlen := requestFactory.GetDecorators(); len(dlen) != 2 { | ||
t.Fatalf("Expected to have two decorators, got %d", dlen) | ||
} | ||
|
||
req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test")) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
username, password, ok := req.BasicAuth() | ||
if !ok { | ||
t.Fatalf("Cannot retrieve basic auth info from request") | ||
} | ||
if username != "test" { | ||
t.Fatalf("Expected username to be test, got %s", username) | ||
} | ||
if password != "password" { | ||
t.Fatalf("Expected password to be password, got %s", password) | ||
} | ||
if req.Header.Get("User-Agent") != "testname/testversion name/version" { | ||
t.Fatalf("Request should have User-Agent 'testname/testversion name/version'") | ||
} | ||
} | ||
|
||
func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { | ||
ad := NewAuthDecorator("test", "password") | ||
|
||
requestFactory := NewRequestFactory(ad) | ||
|
||
if dlen := requestFactory.GetDecorators(); len(dlen) != 1 { | ||
t.Fatalf("Expected to have one decorators, got %d", dlen) | ||
} | ||
|
||
ad2 := NewAuthDecorator("test2", "password2") | ||
|
||
req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"), ad2) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
username, password, ok := req.BasicAuth() | ||
if !ok { | ||
t.Fatalf("Cannot retrieve basic auth info from request") | ||
} | ||
if username != "test2" { | ||
t.Fatalf("Expected username to be test, got %s", username) | ||
} | ||
if password != "password2" { | ||
t.Fatalf("Expected password to be password, got %s", password) | ||
} | ||
} | ||
|
||
func TestRequestFactoryAddDecorator(t *testing.T) { | ||
requestFactory := NewRequestFactory() | ||
|
||
if dlen := requestFactory.GetDecorators(); len(dlen) != 0 { | ||
t.Fatalf("Expected to have zero decorators, got %d", dlen) | ||
} | ||
|
||
ad := NewAuthDecorator("test", "password") | ||
requestFactory.AddDecorator(ad) | ||
|
||
if dlen := requestFactory.GetDecorators(); len(dlen) != 1 { | ||
t.Fatalf("Expected to have one decorators, got %d", dlen) | ||
} | ||
} | ||
|
||
func TestRequestFactoryNil(t *testing.T) { | ||
var requestFactory RequestFactory | ||
_, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test")) | ||
if err != nil { | ||
t.Fatalf("Expected not to get and error, got %s", err) | ||
} | ||
} |
Oops, something went wrong.