Merge pull request #11900 from runcom/11899-refactor-utils-http
Refactor utils/http.go
This commit is contained in:
commit
d09a989733
3 changed files with 396 additions and 0 deletions
2
requestdecorator/README.md
Normal file
2
requestdecorator/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
This package provides helper functions for decorating a request with user agent
|
||||
versions, auth, meta headers.
|
172
requestdecorator/requestdecorator.go
Normal file
172
requestdecorator/requestdecorator.go
Normal file
|
@ -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
|
||||
}
|
222
requestdecorator/requestdecorator_test.go
Normal file
222
requestdecorator/requestdecorator_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue