173 lines
4 KiB
Go
173 lines
4 KiB
Go
|
// 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
|
||
|
}
|