Merge pull request #13265 from tiborvass/refactor-v1-auth
registry: Refactor requestfactory to use http.RoundTrippers
This commit is contained in:
commit
31232249f7
8 changed files with 267 additions and 396 deletions
|
@ -1,2 +0,0 @@
|
||||||
This package provides helper functions for decorating a request with user agent
|
|
||||||
versions, auth, meta headers.
|
|
|
@ -1,172 +0,0 @@
|
||||||
// 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
|
|
||||||
}
|
|
|
@ -1,222 +0,0 @@
|
||||||
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 l := len(requestFactory.GetDecorators()); l != 2 {
|
|
||||||
t.Fatalf("Expected to have two decorators, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 l := len(requestFactory.GetDecorators()); l != 1 {
|
|
||||||
t.Fatalf("Expected to have one decorators, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 l := len(requestFactory.GetDecorators()); l != 0 {
|
|
||||||
t.Fatalf("Expected to have zero decorators, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
ad := NewAuthDecorator("test", "password")
|
|
||||||
requestFactory.AddDecorator(ad)
|
|
||||||
|
|
||||||
if l := len(requestFactory.GetDecorators()); l != 1 {
|
|
||||||
t.Fatalf("Expected to have one decorators, got %d", l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
27
transport/LICENSE
Normal file
27
transport/LICENSE
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The oauth2 Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
148
transport/transport.go
Normal file
148
transport/transport.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package transport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestModifier interface {
|
||||||
|
ModifyRequest(*http.Request) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerModifier http.Header
|
||||||
|
|
||||||
|
// NewHeaderRequestModifier returns a RequestModifier that merges the HTTP headers
|
||||||
|
// passed as an argument, with the HTTP headers of a request.
|
||||||
|
//
|
||||||
|
// If the same key is present in both, the modifying header values for that key,
|
||||||
|
// are appended to the values for that same key in the request header.
|
||||||
|
func NewHeaderRequestModifier(header http.Header) RequestModifier {
|
||||||
|
return headerModifier(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h headerModifier) ModifyRequest(req *http.Request) error {
|
||||||
|
for k, s := range http.Header(h) {
|
||||||
|
req.Header[k] = append(req.Header[k], s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransport returns an http.RoundTripper that modifies requests according to
|
||||||
|
// the RequestModifiers passed in the arguments, before sending the requests to
|
||||||
|
// the base http.RoundTripper (which, if nil, defaults to http.DefaultTransport).
|
||||||
|
func NewTransport(base http.RoundTripper, modifiers ...RequestModifier) http.RoundTripper {
|
||||||
|
return &transport{
|
||||||
|
Modifiers: modifiers,
|
||||||
|
Base: base,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transport is an http.RoundTripper that makes HTTP requests after
|
||||||
|
// copying and modifying the request
|
||||||
|
type transport struct {
|
||||||
|
Modifiers []RequestModifier
|
||||||
|
Base http.RoundTripper
|
||||||
|
|
||||||
|
mu sync.Mutex // guards modReq
|
||||||
|
modReq map[*http.Request]*http.Request // original -> modified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
req2 := CloneRequest(req)
|
||||||
|
for _, modifier := range t.Modifiers {
|
||||||
|
if err := modifier.ModifyRequest(req2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.setModReq(req, req2)
|
||||||
|
res, err := t.base().RoundTrip(req2)
|
||||||
|
if err != nil {
|
||||||
|
t.setModReq(req, nil)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.Body = &OnEOFReader{
|
||||||
|
Rc: res.Body,
|
||||||
|
Fn: func() { t.setModReq(req, nil) },
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelRequest cancels an in-flight request by closing its connection.
|
||||||
|
func (t *transport) CancelRequest(req *http.Request) {
|
||||||
|
type canceler interface {
|
||||||
|
CancelRequest(*http.Request)
|
||||||
|
}
|
||||||
|
if cr, ok := t.base().(canceler); ok {
|
||||||
|
t.mu.Lock()
|
||||||
|
modReq := t.modReq[req]
|
||||||
|
delete(t.modReq, req)
|
||||||
|
t.mu.Unlock()
|
||||||
|
cr.CancelRequest(modReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) base() http.RoundTripper {
|
||||||
|
if t.Base != nil {
|
||||||
|
return t.Base
|
||||||
|
}
|
||||||
|
return http.DefaultTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *transport) setModReq(orig, mod *http.Request) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
if t.modReq == nil {
|
||||||
|
t.modReq = make(map[*http.Request]*http.Request)
|
||||||
|
}
|
||||||
|
if mod == nil {
|
||||||
|
delete(t.modReq, orig)
|
||||||
|
} else {
|
||||||
|
t.modReq[orig] = mod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloneRequest returns a clone of the provided *http.Request.
|
||||||
|
// The clone is a shallow copy of the struct and its Header map.
|
||||||
|
func CloneRequest(r *http.Request) *http.Request {
|
||||||
|
// shallow copy of the struct
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
// deep copy of the Header
|
||||||
|
r2.Header = make(http.Header, len(r.Header))
|
||||||
|
for k, s := range r.Header {
|
||||||
|
r2.Header[k] = append([]string(nil), s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r2
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnEOFReader ensures a callback function is called
|
||||||
|
// on Close() and when the underlying Reader returns an io.EOF error
|
||||||
|
type OnEOFReader struct {
|
||||||
|
Rc io.ReadCloser
|
||||||
|
Fn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OnEOFReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = r.Rc.Read(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
r.runFunc()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OnEOFReader) Close() error {
|
||||||
|
err := r.Rc.Close()
|
||||||
|
r.runFunc()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *OnEOFReader) runFunc() {
|
||||||
|
if fn := r.Fn; fn != nil {
|
||||||
|
fn()
|
||||||
|
r.Fn = nil
|
||||||
|
}
|
||||||
|
}
|
1
useragent/README.md
Normal file
1
useragent/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This package provides helper functions to pack version information into a single User-Agent header.
|
60
useragent/useragent.go
Normal file
60
useragent/useragent.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// Package useragent provides helper functions to pack
|
||||||
|
// version information into a single User-Agent header.
|
||||||
|
package useragent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNilRequest = errors.New("request cannot be nil")
|
||||||
|
)
|
||||||
|
|
||||||
|
// VersionInfo is used to model UserAgent versions.
|
||||||
|
type VersionInfo struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vi *VersionInfo) 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 VersionInfo 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.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// AppendVersions("base", VersionInfo{"foo", "1.0"}, VersionInfo{"bar", "2.0"})
|
||||||
|
// results in "base foo/1.0 bar/2.0".
|
||||||
|
func AppendVersions(base string, versions ...VersionInfo) 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, " ")
|
||||||
|
}
|
31
useragent/useragent_test.go
Normal file
31
useragent/useragent_test.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package useragent
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestVersionInfo(t *testing.T) {
|
||||||
|
vi := VersionInfo{"foo", "bar"}
|
||||||
|
if !vi.isValid() {
|
||||||
|
t.Fatalf("VersionInfo should be valid")
|
||||||
|
}
|
||||||
|
vi = VersionInfo{"", "bar"}
|
||||||
|
if vi.isValid() {
|
||||||
|
t.Fatalf("Expected VersionInfo to be invalid")
|
||||||
|
}
|
||||||
|
vi = VersionInfo{"foo", ""}
|
||||||
|
if vi.isValid() {
|
||||||
|
t.Fatalf("Expected VersionInfo to be invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendVersions(t *testing.T) {
|
||||||
|
vis := []VersionInfo{
|
||||||
|
{"foo", "1.0"},
|
||||||
|
{"bar", "0.1"},
|
||||||
|
{"pi", "3.1.4"},
|
||||||
|
}
|
||||||
|
v := AppendVersions("base", vis...)
|
||||||
|
expect := "base foo/1.0 bar/0.1 pi/3.1.4"
|
||||||
|
if v != expect {
|
||||||
|
t.Fatalf("expected %q, got %q", expect, v)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue