package utils import ( "bytes" "io" "net/http" "strings" ) // VersionInfo is used to model entities which has a version. // It is basically a tupple with name and version. type VersionInfo interface { Name() string Version() string } func validVersion(version VersionInfo) bool { stopChars := " \t\r\n/" if strings.ContainsAny(version.Name(), stopChars) { return false } if strings.ContainsAny(version.Version(), stopChars) { return false } return true } // Convert versions to a string and append the string to the string base. // // Each VersionInfo will be converted to a string in the format of // "product/version", where the "product" is get from the Name() method, while // version is get from the Version() method. Several pieces of verson information // will be concatinated and separated by space. func appendVersions(base string, versions ...VersionInfo) string { if len(versions) == 0 { return base } var buf bytes.Buffer if len(base) > 0 { buf.Write([]byte(base)) } for _, v := range versions { name := []byte(v.Name()) version := []byte(v.Version()) if len(name) == 0 || len(version) == 0 { continue } if !validVersion(v) { continue } buf.Write([]byte(v.Name())) buf.Write([]byte("/")) buf.Write([]byte(v.Version())) buf.Write([]byte(" ")) } return buf.String() } // HTTPRequestDecorator is used to change an instance of // http.Request. It could be used to add more header fields, // change body, etc. type HTTPRequestDecorator 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) } // HTTPUserAgentDecorator appends the product/version to the user agent field // of a request. type HTTPUserAgentDecorator struct { versions []VersionInfo } func NewHTTPUserAgentDecorator(versions ...VersionInfo) HTTPRequestDecorator { return &HTTPUserAgentDecorator{ versions: versions, } } func (h *HTTPUserAgentDecorator) ChangeRequest(req *http.Request) (newReq *http.Request, err error) { if req == nil { return req, nil } userAgent := appendVersions(req.UserAgent(), h.versions...) if len(userAgent) > 0 { req.Header.Set("User-Agent", userAgent) } return req, nil } type HTTPMetaHeadersDecorator struct { Headers map[string][]string } func (h *HTTPMetaHeadersDecorator) ChangeRequest(req *http.Request) (newReq *http.Request, err error) { if h.Headers == nil { return req, nil } for k, v := range h.Headers { req.Header[k] = v } return req, nil } type HTTPAuthDecorator struct { login string password string } func NewHTTPAuthDecorator(login, password string) HTTPRequestDecorator { return &HTTPAuthDecorator{ login: login, password: password, } } func (self *HTTPAuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { req.SetBasicAuth(self.login, self.password) return req, nil } // HTTPRequestFactory creates an HTTP request // and applies a list of decorators on the request. type HTTPRequestFactory struct { decorators []HTTPRequestDecorator } func NewHTTPRequestFactory(d ...HTTPRequestDecorator) *HTTPRequestFactory { return &HTTPRequestFactory{ decorators: d, } } func (self *HTTPRequestFactory) AddDecorator(d ...HTTPRequestDecorator) { self.decorators = append(self.decorators, d...) } // NewRequest() creates a new *http.Request, // applies all decorators in the HTTPRequestFactory on the request, // then applies decorators provided by d on the request. func (h *HTTPRequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...HTTPRequestDecorator) (*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, err = dec.ChangeRequest(req) if err != nil { return nil, err } } for _, dec := range d { req, err = dec.ChangeRequest(req) if err != nil { return nil, err } } Debugf("%v -- HEADERS: %v", req.URL, req.Header) return req, err }