github.com/codemac/docker@v1.2.1-0.20150518222241-6a18412d5b9c/pkg/requestdecorator/requestdecorator.go (about)

     1  // Package requestdecorator provides helper functions to decorate a request with
     2  // user agent versions, auth, meta headers.
     3  package requestdecorator
     4  
     5  import (
     6  	"errors"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/Sirupsen/logrus"
    12  )
    13  
    14  var (
    15  	ErrNilRequest = errors.New("request cannot be nil")
    16  )
    17  
    18  // UAVersionInfo is used to model UserAgent versions.
    19  type UAVersionInfo struct {
    20  	Name    string
    21  	Version string
    22  }
    23  
    24  func NewUAVersionInfo(name, version string) UAVersionInfo {
    25  	return UAVersionInfo{
    26  		Name:    name,
    27  		Version: version,
    28  	}
    29  }
    30  
    31  func (vi *UAVersionInfo) isValid() bool {
    32  	const stopChars = " \t\r\n/"
    33  	name := vi.Name
    34  	vers := vi.Version
    35  	if len(name) == 0 || strings.ContainsAny(name, stopChars) {
    36  		return false
    37  	}
    38  	if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
    39  		return false
    40  	}
    41  	return true
    42  }
    43  
    44  // Convert versions to a string and append the string to the string base.
    45  //
    46  // Each UAVersionInfo will be converted to a string in the format of
    47  // "product/version", where the "product" is get from the name field, while
    48  // version is get from the version field. Several pieces of verson information
    49  // will be concatinated and separated by space.
    50  func appendVersions(base string, versions ...UAVersionInfo) string {
    51  	if len(versions) == 0 {
    52  		return base
    53  	}
    54  
    55  	verstrs := make([]string, 0, 1+len(versions))
    56  	if len(base) > 0 {
    57  		verstrs = append(verstrs, base)
    58  	}
    59  
    60  	for _, v := range versions {
    61  		if !v.isValid() {
    62  			continue
    63  		}
    64  		verstrs = append(verstrs, v.Name+"/"+v.Version)
    65  	}
    66  	return strings.Join(verstrs, " ")
    67  }
    68  
    69  // Decorator is used to change an instance of
    70  // http.Request. It could be used to add more header fields,
    71  // change body, etc.
    72  type Decorator interface {
    73  	// ChangeRequest() changes the request accordingly.
    74  	// The changed request will be returned or err will be non-nil
    75  	// if an error occur.
    76  	ChangeRequest(req *http.Request) (newReq *http.Request, err error)
    77  }
    78  
    79  // UserAgentDecorator appends the product/version to the user agent field
    80  // of a request.
    81  type UserAgentDecorator struct {
    82  	Versions []UAVersionInfo
    83  }
    84  
    85  func (h *UserAgentDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
    86  	if req == nil {
    87  		return req, ErrNilRequest
    88  	}
    89  
    90  	userAgent := appendVersions(req.UserAgent(), h.Versions...)
    91  	if len(userAgent) > 0 {
    92  		req.Header.Set("User-Agent", userAgent)
    93  	}
    94  	return req, nil
    95  }
    96  
    97  type MetaHeadersDecorator struct {
    98  	Headers map[string][]string
    99  }
   100  
   101  func (h *MetaHeadersDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
   102  	if h.Headers == nil {
   103  		return req, ErrNilRequest
   104  	}
   105  	for k, v := range h.Headers {
   106  		req.Header[k] = v
   107  	}
   108  	return req, nil
   109  }
   110  
   111  type AuthDecorator struct {
   112  	login    string
   113  	password string
   114  }
   115  
   116  func NewAuthDecorator(login, password string) Decorator {
   117  	return &AuthDecorator{
   118  		login:    login,
   119  		password: password,
   120  	}
   121  }
   122  
   123  func (self *AuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
   124  	if req == nil {
   125  		return req, ErrNilRequest
   126  	}
   127  	req.SetBasicAuth(self.login, self.password)
   128  	return req, nil
   129  }
   130  
   131  // RequestFactory creates an HTTP request
   132  // and applies a list of decorators on the request.
   133  type RequestFactory struct {
   134  	decorators []Decorator
   135  }
   136  
   137  func NewRequestFactory(d ...Decorator) *RequestFactory {
   138  	return &RequestFactory{
   139  		decorators: d,
   140  	}
   141  }
   142  
   143  func (f *RequestFactory) AddDecorator(d ...Decorator) {
   144  	f.decorators = append(f.decorators, d...)
   145  }
   146  
   147  func (f *RequestFactory) GetDecorators() []Decorator {
   148  	return f.decorators
   149  }
   150  
   151  // NewRequest() creates a new *http.Request,
   152  // applies all decorators in the Factory on the request,
   153  // then applies decorators provided by d on the request.
   154  func (h *RequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...Decorator) (*http.Request, error) {
   155  	req, err := http.NewRequest(method, urlStr, body)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	// By default, a nil factory should work.
   161  	if h == nil {
   162  		return req, nil
   163  	}
   164  	for _, dec := range h.decorators {
   165  		req, _ = dec.ChangeRequest(req)
   166  	}
   167  	for _, dec := range d {
   168  		req, _ = dec.ChangeRequest(req)
   169  	}
   170  	logrus.Debugf("%v -- HEADERS: %v", req.URL, req.Header)
   171  	return req, err
   172  }