master
李光春 2 years ago
parent d1f38fbada
commit b718272697

@ -8,7 +8,6 @@ require (
github.com/nilorg/sdk v0.0.0-20210429091026-95b6cdc95c84
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/h2non/gentleman.v2 v2.0.5
)
require (
@ -16,7 +15,6 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect

@ -34,8 +34,6 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/nilorg/sdk v0.0.0-20210429091026-95b6cdc95c84 h1:Nxk1uViXfb9MHgtHBlQFWzlQCsJbDQuotfTsAFcFP3o=
github.com/nilorg/sdk v0.0.0-20210429091026-95b6cdc95c84/go.mod h1:X1swpPdqguAZaBDoEPyEWHSsJii0YQ1o+3piMv6W3JU=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
@ -112,8 +110,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/h2non/gentleman.v2 v2.0.5 h1:ckmb6cLxL2DDk7WN7LSdxXDq7jNkOicFg4JZ4ZnDNuE=
gopkg.in/h2non/gentleman.v2 v2.0.5/go.mod h1:A1c7zwrTgAyyf6AbpvVksYtBayTB4STBUGmdkEtlHeA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

@ -102,13 +102,13 @@ func NumericalToString(value interface{}) (string, bool) {
val = strconv.FormatUint(uint64(intVal), 10)
case uint64:
intVal, _ := value.(uint64)
val = strconv.FormatUint(uint64(intVal), 10)
val = strconv.FormatUint(intVal, 10)
case float32:
floatVal, _ := value.(float32)
val = strconv.FormatFloat(float64(floatVal), 'f', -1, 32)
case float64:
floatVal, _ := value.(float64)
val = strconv.FormatFloat(float64(floatVal), 'f', -1, 64)
val = strconv.FormatFloat(floatVal, 'f', -1, 64)
}
return val, true
}

3
vendor/golang.org/x/net/AUTHORS generated vendored

@ -1,3 +0,0 @@
# This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at http://tip.golang.org/AUTHORS.

@ -1,3 +0,0 @@
# This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at http://tip.golang.org/CONTRIBUTORS.

27
vendor/golang.org/x/net/LICENSE generated vendored

@ -1,27 +0,0 @@
Copyright (c) 2009 The Go 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.

22
vendor/golang.org/x/net/PATENTS generated vendored

@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

@ -1,181 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run gen.go
// Package publicsuffix provides a public suffix list based on data from
// https://publicsuffix.org/
//
// A public suffix is one under which Internet users can directly register
// names. It is related to, but different from, a TLD (top level domain).
//
// "com" is a TLD (top level domain). Top level means it has no dots.
//
// "com" is also a public suffix. Amazon and Google have registered different
// siblings under that domain: "amazon.com" and "google.com".
//
// "au" is another TLD, again because it has no dots. But it's not "amazon.au".
// Instead, it's "amazon.com.au".
//
// "com.au" isn't an actual TLD, because it's not at the top level (it has
// dots). But it is an eTLD (effective TLD), because that's the branching point
// for domain name registrars.
//
// Another name for "an eTLD" is "a public suffix". Often, what's more of
// interest is the eTLD+1, or one more label than the public suffix. For
// example, browsers partition read/write access to HTTP cookies according to
// the eTLD+1. Web pages served from "amazon.com.au" can't read cookies from
// "google.com.au", but web pages served from "maps.google.com" can share
// cookies from "www.google.com", so you don't have to sign into Google Maps
// separately from signing into Google Web Search. Note that all four of those
// domains have 3 labels and 2 dots. The first two domains are each an eTLD+1,
// the last two are not (but share the same eTLD+1: "google.com").
//
// All of these domains have the same eTLD+1:
// - "www.books.amazon.co.uk"
// - "books.amazon.co.uk"
// - "amazon.co.uk"
// Specifically, the eTLD+1 is "amazon.co.uk", because the eTLD is "co.uk".
//
// There is no closed form algorithm to calculate the eTLD of a domain.
// Instead, the calculation is data driven. This package provides a
// pre-compiled snapshot of Mozilla's PSL (Public Suffix List) data at
// https://publicsuffix.org/
package publicsuffix // import "golang.org/x/net/publicsuffix"
// TODO: specify case sensitivity and leading/trailing dot behavior for
// func PublicSuffix and func EffectiveTLDPlusOne.
import (
"fmt"
"net/http/cookiejar"
"strings"
)
// List implements the cookiejar.PublicSuffixList interface by calling the
// PublicSuffix function.
var List cookiejar.PublicSuffixList = list{}
type list struct{}
func (list) PublicSuffix(domain string) string {
ps, _ := PublicSuffix(domain)
return ps
}
func (list) String() string {
return version
}
// PublicSuffix returns the public suffix of the domain using a copy of the
// publicsuffix.org database compiled into the library.
//
// icann is whether the public suffix is managed by the Internet Corporation
// for Assigned Names and Numbers. If not, the public suffix is either a
// privately managed domain (and in practice, not a top level domain) or an
// unmanaged top level domain (and not explicitly mentioned in the
// publicsuffix.org list). For example, "foo.org" and "foo.co.uk" are ICANN
// domains, "foo.dyndns.org" and "foo.blogspot.co.uk" are private domains and
// "cromulent" is an unmanaged top level domain.
//
// Use cases for distinguishing ICANN domains like "foo.com" from private
// domains like "foo.appspot.com" can be found at
// https://wiki.mozilla.org/Public_Suffix_List/Use_Cases
func PublicSuffix(domain string) (publicSuffix string, icann bool) {
lo, hi := uint32(0), uint32(numTLD)
s, suffix, icannNode, wildcard := domain, len(domain), false, false
loop:
for {
dot := strings.LastIndex(s, ".")
if wildcard {
icann = icannNode
suffix = 1 + dot
}
if lo == hi {
break
}
f := find(s[1+dot:], lo, hi)
if f == notFound {
break
}
u := nodes[f] >> (nodesBitsTextOffset + nodesBitsTextLength)
icannNode = u&(1<<nodesBitsICANN-1) != 0
u >>= nodesBitsICANN
u = children[u&(1<<nodesBitsChildren-1)]
lo = u & (1<<childrenBitsLo - 1)
u >>= childrenBitsLo
hi = u & (1<<childrenBitsHi - 1)
u >>= childrenBitsHi
switch u & (1<<childrenBitsNodeType - 1) {
case nodeTypeNormal:
suffix = 1 + dot
case nodeTypeException:
suffix = 1 + len(s)
break loop
}
u >>= childrenBitsNodeType
wildcard = u&(1<<childrenBitsWildcard-1) != 0
if !wildcard {
icann = icannNode
}
if dot == -1 {
break
}
s = s[:dot]
}
if suffix == len(domain) {
// If no rules match, the prevailing rule is "*".
return domain[1+strings.LastIndex(domain, "."):], icann
}
return domain[suffix:], icann
}
const notFound uint32 = 1<<32 - 1
// find returns the index of the node in the range [lo, hi) whose label equals
// label, or notFound if there is no such node. The range is assumed to be in
// strictly increasing node label order.
func find(label string, lo, hi uint32) uint32 {
for lo < hi {
mid := lo + (hi-lo)/2
s := nodeLabel(mid)
if s < label {
lo = mid + 1
} else if s == label {
return mid
} else {
hi = mid
}
}
return notFound
}
// nodeLabel returns the label for the i'th node.
func nodeLabel(i uint32) string {
x := nodes[i]
length := x & (1<<nodesBitsTextLength - 1)
x >>= nodesBitsTextLength
offset := x & (1<<nodesBitsTextOffset - 1)
return text[offset : offset+length]
}
// EffectiveTLDPlusOne returns the effective top level domain plus one more
// label. For example, the eTLD+1 for "foo.bar.golang.org" is "golang.org".
func EffectiveTLDPlusOne(domain string) (string, error) {
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") || strings.Contains(domain, "..") {
return "", fmt.Errorf("publicsuffix: empty label in domain %q", domain)
}
suffix, _ := PublicSuffix(domain)
if len(domain) <= len(suffix) {
return "", fmt.Errorf("publicsuffix: cannot derive eTLD+1 for domain %q", domain)
}
i := len(domain) - len(suffix) - 1
if domain[i] != '.' {
return "", fmt.Errorf("publicsuffix: invalid public suffix %q for domain %q", suffix, domain)
}
return domain[1+strings.LastIndex(domain[:i], "."):], nil
}

File diff suppressed because it is too large Load Diff

@ -1,12 +0,0 @@
root = true
[*]
indent_style = tab
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

@ -1,30 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
.idea/
*.iml
*.out
*.tmp
.DS_Store

@ -1,24 +0,0 @@
language: go
go_import_path: gopkg.in/h2non/gentleman.v2
go:
- "1.14"
- "1.13"
- "1.12"
before_install:
- go get github.com/nbio/st
- go get -u -v github.com/axw/gocov/gocov
- go get -u -v github.com/mattn/goveralls
- go get -u -v golang.org/x/lint/golint
script:
- diff -u <(echo -n) <(gofmt -s -d ./)
- diff -u <(echo -n) <(go vet ./...)
- diff -u <(echo -n) <(golint ./...)
- go test -v -race ./...
- go test -v -race -covermode=atomic -coverprofile=coverage.out
after_success:
- goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN

@ -1,108 +0,0 @@
v2.0.5 / 2021-02-02
===================
* feat(version): bump patch version to avoid go mod cache issue.
v2.0.4 / 2020-02-17
===================
* feat(url): append base path in `url.BaseURL()`.
* feat(api): add method `AddPath()` to `Client`.
v2.0.3 / 2017-10-13
===================
* fix(#37): header copy in redirect plugin
v2.0.2 / 2017-09-20
===================
* fix(#36): make middleware layer thread-safe
* feat(docs): add sponsor ad
v2.0.1 / 2017-09-14
===================
* fix(#35): do not stop on parent middleware layer in error phase
* fix(travis): gentleman requires Go 1.7+
* fix(examples): formatting issue
* fix(examples): formatting issue
* Merge branch 'master' of https://github.com/h2non/gentleman
* feat(#33): add error handling example
* refactor(docs): split community based plugins
* Merge pull request #34 from izumin5210/logger
* Add link for gentleman-logger
* Merge pull request #31 from djui/patch-1
* Fix typo
* feat(docs): add gock reference
v2.0.0 / 2017-07-26
===================
* fix(merge): resolve conflicts from master
* feat(docs): add versions notes
* refactor(docs): update version badge
* refactor(docs): update version badge
* Merge branch 'master' of https://github.com/h2non/gentleman into v2
* rerafctor(docs): use v2 godoc links
* refactor(examples): normalize import statements
* fix(bodytype): add Type() alias public method
* feat(History): update changes
* feat(version): bump to v2.0.0-rc.0
* fix: format code accordingly
* fix(travis): use Go 1.7+
* feat(v2): push v2 preview release
v2.0.0-rc.0 / 2017-03-18
========================
* feat(version): bump to v2.0.0-rc.0
* fix: format code accordingly
* fix(travis): use Go 1.7+
* refactor(context): adopt standard `context`. Introduces several breaking changes
* feat(v2): push v2 preview release
v1.0.4 / 2017-05-31
===================
* Merge pull request #28 from eyalpost/master
* refactor: remove finalizers (this introduces a minor breaking change)
* feat(docs): add v2 notice
v1.0.3 / 2017-03-17
===================
* fix(#23): persist context data across body updates
* fix(lint): use proper code style
* fix(test): several lint issues
* fix(#22): adds support for multipart multiple form values. This introduces a minor interface breaking change
* feat(travis): add Go 1.8 CI support
v1.0.2 / 2017-02-22
===================
* Merge pull request #21 from ewilazarus/master
* fix(#20): Prevents finalizing chunked responses
## 1.0.1 / 2016-01-29
- fix(body): do not enforce POST method unless method is not already present.
## 0.1.3 / 03-03-2016
- fix(#12): define request method via middleware.
## 0.1.2 / 28-02-2016
- feat(body): transparently define POST method if needed.
- feat(multipart): transparently define POST method if needed.
## 0.1.1 / 27-02-2016
- feat(multipart): support custom form field name.
## 0.1.0 / 25-02-2016
- First release.

@ -1,24 +0,0 @@
The MIT License
Copyright (c) 2016-2017 Tomas Aparicio
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -1,461 +0,0 @@
# gentleman [![Build Status](https://travis-ci.org/h2non/gentleman.svg)](https://travis-ci.org/h2non/gentleman) [![GitHub release](https://img.shields.io/badge/version-2.0.5-orange.svg?style=flat)](https://github.com/h2non/gentleman/releases) [![GoDoc](https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg)](https://godoc.org/gopkg.in/h2non/gentleman.v2) [![Coverage Status](https://coveralls.io/repos/github/h2non/gentleman/badge.svg?branch=master)](https://coveralls.io/github/h2non/gentleman?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman)](https://goreportcard.com/report/github.com/h2non/gentleman) [![Go Version](https://img.shields.io/badge/go-v2.0+-green.svg?style=flat)](https://github.com/h2non/gentleman)
Full-featured, plugin-driven, middleware-oriented toolkit to easily create rich, versatile and composable HTTP clients in [Go](http://golang.org).
<img src="http://s10.postimg.org/5e31ox1ft/gentleman.png" align="right" height="260" />
gentleman embraces extensibility and composition principles in order to provide a flexible way to easily create featured HTTP client layers based on built-in or third-party plugins that you can register and reuse across HTTP clients.
As an example, you can easily provide retry policy capabilities or dynamic server discovery in your HTTP clients simply attaching the [retry](https://github.com/h2non/gentleman-retry) or [consul](https://github.com/h2non/gentleman-consul) plugins.
Take a look to the [examples](#examples), list of [supported plugins](#plugins), [HTTP entities](#http-entities) or [middleware layer](#middleware) to get started.
For testing purposes, see [baloo](https://github.com/h2non/baloo), an utility library for expressive end-to-end HTTP API testing, built on top of `gentleman` toolkit. For HTTP mocking, see [gentleman-mock](https://github.com/h2non/gentleman-mock), which uses [gock](https://github.com/h2non/gock) under the hood for easy and expressive HTTP client request mocking.
## Versions
- [v2](https://github.com/h2non/gentleman/) - Latest version. Stable. Recommended.
- [v1](https://github.com/h2non/gentleman/tree/v1) - First version. Stable. Actively maintained.
## Features
- Plugin driven architecture.
- Simple, expressive, fluent API.
- Idiomatic built on top of `net/http` package.
- Context-aware hierarchical middleware layer supporting all the HTTP life cycle.
- Built-in multiplexer for easy composition capabilities.
- Easy to extend via plugins/middleware.
- Ability to easily intercept and modify HTTP traffic on-the-fly.
- Convenient helpers and abstractions over Go's HTTP primitives.
- URL template path params.
- Built-in JSON, XML and multipart bodies serialization and parsing.
- Easy to test via HTTP mocking (e.g: [gentleman-mock](https://github.com/h2non/gentleman-mock)).
- Supports data passing across plugins/middleware via its built-in context.
- Fits good while building domain-specific HTTP API clients.
- Easy to hack.
- Dependency free.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2
```
## Requirements
- Go 1.9+
## Plugins
<table>
<tr>
<th>Name</th>
<th>Docs</th>
<th>Status</th>
<th>Description</th>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/url">url</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/url">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /</a></td>
<td>Easily declare URL, base URL and path values in HTTP requests</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/auth">auth</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/auth">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Declare authorization headers in your requests</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/body">body</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/body">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Easily define bodies based on JSON, XML, strings, buffers or streams</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/bodytype">bodytype</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/bodytype">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Define body MIME type by alias</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/cookies">cookies</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/cookies">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Declare and store HTTP cookies easily</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/compression">compression</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/compression">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Helpers to define enable/disable HTTP compression</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/headers">headers</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/headers">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Manage HTTP headers easily</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/multipart">multipart</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/multipart">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Create multipart forms easily. Supports files and text fields</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/proxy">proxy</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/proxy">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Configure HTTP proxy servers</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/query">query</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/query">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Easily manage query params</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/redirect">redirect</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/redirect">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Easily configure a custom redirect policy</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/timeout">timeout</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/timeout">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Easily configure the HTTP timeouts (request, dial, TLS...)</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/transport">transport</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/transport">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Define a custom HTTP transport easily</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman/tree/master/plugins/tls">tls</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2/plugins/tls">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman"><img src="https://travis-ci.org/h2non/gentleman.png" /></a></td>
<td>Configure the TLS options used by the HTTP transport</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman-retry">retry</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2-retry">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2-retry?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman-retry"><img src="https://travis-ci.org/h2non/gentleman-retry.png" /></a></td>
<td>Provide retry policy capabilities to your HTTP clients</td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman-mock">mock</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2-mock">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2-mock?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman-mock"><img src="https://travis-ci.org/h2non/gentleman-mock.png" /></a></td>
<td>Easy HTTP mocking using <a href="https://github.com/h2non/gock">gock</a></td>
</tr>
<tr>
<td><a href="https://github.com/h2non/gentleman-consul">consul</a></td>
<td>
<a href="https://godoc.org/gopkg.in/h2non/gentleman.v2-consul">
<img src="https://godoc.org/gopkg.in/h2non/gentleman.v2-consul?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/h2non/gentleman-consul"><img src="https://travis-ci.org/h2non/gentleman-consul.png" /></a></td>
<td><a href="https://www.consul.io">Consul</a> based server discovery with configurable retry/backoff policy</td>
</tr>
</table>
### Community plugins
<table>
<tr>
<th>Name</th>
<th>Docs</th>
<th>Status</th>
<th>Description</th>
</tr>
<tr>
<td><a href="https://github.com/izumin5210/gentleman-logger">logger</a></td>
<td>
<a href="https://godoc.org/github.com/izumin5210/gentleman-logger">
<img src="https://godoc.org/github.com/izumin5210/gentleman-logger?status.svg" />
</a>
</td>
<td><a href="https://travis-ci.org/izumin5210/gentleman-logger"><img src="https://travis-ci.org/izumin5210/gentleman-logger.png" /></a></td>
<td>Easily log requests and responses</td>
</tr>
</table>
[Send](https://github.com/h2non/gentleman/pull/new/master) a PR to add your plugin to the list.
### Creating plugins
You can create your own plugins for a wide variety of purposes, such as server discovery, custom HTTP tranport, modify any request/response param, intercept traffic, authentication and so on.
Plugins are essentially a set of middleware function handlers for one or multiple HTTP life cycle phases exposing [a concrete interface](https://github.com/h2non/gentleman/blob/755d55eef0bd26ae6b4ee19fe59001db2c46a51b/plugin/plugin.go#L16-L35) consumed by gentleman middleware layer.
For more details about plugins see the [plugin](https://github.com/h2non/gentleman/tree/master/plugin) package and [examples](https://github.com/h2non/gentleman/tree/master/_examples/plugin).
Also you can take a look to a plugin [implementation example](https://github.com/h2non/gentleman/blob/master/_examples/plugin/plugin.go).
## HTTP entities
`gentleman` provides two HTTP high level entities: `Client` and `Request`.
Each of these entities provides a common API and are both middleware capable, giving you the ability to plug in custom components with own logic into any of them.
`gentleman` was designed to provide strong reusability capabilities.
This is mostly achieved via its built-in hierarchical, inheritance-based middleware layer.
The following list describes how inheritance hierarchy works and is used across gentleman's entities.
- `Client` entity can inherit from other `Client` entity.
- `Request` entity can inherit from a `Client` entity.
- `Client` entity is mostly designed for reusability.
- `Client` entity can create multiple `Request` entities who implicitly inherits from `Client` entity itself.
- `Request` entity is designed to have specific HTTP request logic that is not typically reused.
- Both `Client` and `Request` entities are full middleware capable interfaces.
- Both `Client` and `Request` entities can be cloned in order to produce a copy but side-effects free new entity.
You can see an inheritance usage example [here](https://github.com/h2non/gentleman/blob/master/_examples/inheritance/inheritance.go).
## Middleware
gentleman is completely based on a hierarchical middleware layer based on plugins that executes one or multiple function handlers (aka plugin interface) providing a simple way to plug in intermediate custom logic in your HTTP client.
It supports multiple phases which represents the full HTTP request/response life cycle, giving you the ability to perform actions before and after an HTTP transaction happen, even intercepting and stopping it.
The middleware stack chain is executed in FIFO order designed for single thread model.
Plugins can support goroutines, but plugins implementors should prevent data race issues due to concurrency in multithreading programming.
For more implementation details about the middleware layer, see the [middleware](https://github.com/h2non/gentleman/tree/master/middleware) package and [examples](https://github.com/h2non/gentleman/tree/master/_examples/middleware).
#### Middleware phases
Supported middleware phases triggered by gentleman HTTP dispatcher:
- **request** - Executed before a request is sent over the network.
- **response** - Executed when the client receives the response, even if it failed.
- **error** - Executed in case that an error ocurrs, support both injected or native error.
- **stop** - Executed in case that the request has been manually stopped via middleware (e.g: after interception).
- **intercept** - Executed in case that the request has been intercepted before network dialing.
- **before dial** - Executed before a request is sent over the network.
- **after dial** - Executed after the request dialing was done and the response has been received.
Note that the middleware layer has been designed for easy extensibility, therefore new phases may be added in the future and/or the developer could be able to trigger custom middleware phases if needed.
Feel free to fill an issue to discuss this capabilities in detail.
## API
See [godoc reference](https://godoc.org/gopkg.in/h2non/gentleman.v2) for detailed API documentation.
#### Subpackages
- [plugin](https://github.com/h2non/gentleman/tree/master/plugin) - [godoc](https://godoc.org/gopkg.in/h2non/gentleman.v2/plugin) - Plugin layer for gentleman.
- [mux](https://github.com/h2non/gentleman/tree/master/mux) - [godoc](https://godoc.org/gopkg.in/h2non/gentleman.v2/mux) - HTTP client multiplexer with built-in matchers.
- [middleware](https://github.com/h2non/gentleman/tree/master/middleware) - [godoc](https://godoc.org/gopkg.in/h2non/gentleman.v2/middleware) - Middleware layer used by gentleman.
- [context](https://github.com/h2non/gentleman/tree/master/context) - [godoc](https://godoc.org/gopkg.in/h2non/gentleman.v2/context) - HTTP context implementation for gentleman's middleware.
- [utils](https://github.com/h2non/gentleman/tree/master/utils) - [godoc](https://godoc.org/gopkg.in/h2non/gentleman.v2/utils) - HTTP utilities internally used.
## Examples
See [examples](https://github.com/h2non/gentleman/blob/master/_examples) directory for featured examples.
#### Simple request
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define base URL
cli.URL("http://httpbin.org")
// Create a new request based on the current client
req := cli.Request()
// Define the URL path at request level
req.Path("/headers")
// Set a new header field
req.SetHeader("Client", "gentleman")
// Perform the request
res, err := req.Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
// Reads the whole body and returns it as string
fmt.Printf("Body: %s", res.String())
}
```
#### Send JSON body
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugins/body"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define the Base URL
cli.URL("http://httpbin.org/post")
// Create a new request based on the current client
req := cli.Request()
// Method to be used
req.Method("POST")
// Define the JSON payload via body plugin
data := map[string]string{"foo": "bar"}
req.Use(body.JSON(data))
// Perform the request
res, err := req.Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
#### Composition via multiplexer
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/mux"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define the server url (must be first)
cli.Use(url.URL("http://httpbin.org"))
// Create a new multiplexer based on multiple matchers
mx := mux.If(mux.Method("GET"), mux.Host("httpbin.org"))
// Attach a custom plugin on the multiplexer that will be executed if the matchers passes
mx.Use(url.Path("/headers"))
// Attach the multiplexer on the main client
cli.Use(mx)
// Perform the request
res, err := cli.Request().Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,237 +0,0 @@
package gentleman
import (
gocontext "context"
"net/http"
"gopkg.in/h2non/gentleman.v2/context"
"gopkg.in/h2non/gentleman.v2/middleware"
"gopkg.in/h2non/gentleman.v2/plugin"
"gopkg.in/h2non/gentleman.v2/plugins/cookies"
"gopkg.in/h2non/gentleman.v2/plugins/headers"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
// NewContext is a convenient alias to context.New factory.
var NewContext = context.New
// NewHandler is a convenient alias to context.NewHandler factory.
var NewHandler = context.NewHandler
// NewMiddleware is a convenient alias to middleware.New factory.
var NewMiddleware = middleware.New
// Client represents a high-level HTTP client entity capable
// with a built-in middleware and context.
type Client struct {
// Client entity can inherit behavior from a parent Client.
Parent *Client
// Each Client entity has it's own Context that will be inherited by requests or child clients.
Context *context.Context
// Client entity has its own Middleware layer to compose and inherit behavior.
Middleware middleware.Middleware
}
// New creates a new high level client entity
// able to perform HTTP requests.
func New() *Client {
return &Client{
Context: context.New(),
Middleware: middleware.New(),
}
}
// Request creates a new Request based on the current Client
func (c *Client) Request() *Request {
req := NewRequest()
req.SetClient(c)
return req
}
// Options creates a new OPTIONS request.
func (c *Client) Options() *Request {
req := c.Request()
req.Method("OPTIONS")
return req
}
// Get creates a new GET request.
func (c *Client) Get() *Request {
req := c.Request()
req.Method("GET")
return req
}
// Post creates a new POST request.
func (c *Client) Post() *Request {
req := c.Request()
req.Method("POST")
return req
}
// Put creates a new PUT request.
func (c *Client) Put() *Request {
req := c.Request()
req.Method("PUT")
return req
}
// Delete creates a new DELETE request.
func (c *Client) Delete() *Request {
req := c.Request()
req.Method("DELETE")
return req
}
// Patch creates a new PATCH request.
func (c *Client) Patch() *Request {
req := c.Request()
req.Method("PATCH")
return req
}
// Head creates a new HEAD request.
func (c *Client) Head() *Request {
req := c.Request()
req.Method("HEAD")
return req
}
// Method defines a the default HTTP method used by outgoing client requests.
func (c *Client) Method(name string) *Client {
c.Middleware.UseRequest(func(ctx *context.Context, h context.Handler) {
ctx.Request.Method = name
h.Next(ctx)
})
return c
}
// URL defines the URL for client requests.
// Useful to define at client level the base URL and base path used by child requests.
func (c *Client) URL(uri string) *Client {
c.Use(url.URL(uri))
return c
}
// BaseURL defines the URL schema and host for client requests.
// Useful to define at client level the base URL used by client child requests.
func (c *Client) BaseURL(uri string) *Client {
c.Use(url.BaseURL(uri))
return c
}
// Path defines the URL base path for client requests.
func (c *Client) Path(path string) *Client {
c.Use(url.Path(path))
return c
}
// AddPath concatenates a path slice to the existent path in at client level.
func (c *Client) AddPath(path string) *Client {
c.Use(url.AddPath(path))
return c
}
// Param replaces a path param based on the given param name and value.
func (c *Client) Param(name, value string) *Client {
c.Use(url.Param(name, value))
return c
}
// Params replaces path params based on the given params key-value map.
func (c *Client) Params(params map[string]string) *Client {
c.Use(url.Params(params))
return c
}
// SetHeader sets a new header field by name and value.
// If another header exists with the same key, it will be overwritten.
func (c *Client) SetHeader(key, value string) *Client {
c.Use(headers.Set(key, value))
return c
}
// AddHeader adds a new header field by name and value
// without overwriting any existent header.
func (c *Client) AddHeader(name, value string) *Client {
c.Use(headers.Add(name, value))
return c
}
// SetHeaders adds new header fields based on the given map.
func (c *Client) SetHeaders(fields map[string]string) *Client {
c.Use(headers.SetMap(fields))
return c
}
// AddCookie sets a new cookie field based on the given http.Cookie struct
// without overwriting any existent cookie.
func (c *Client) AddCookie(cookie *http.Cookie) *Client {
c.Use(cookies.Add(cookie))
return c
}
// AddCookies sets a new cookie field based on a list of http.Cookie
// without overwriting any existent cookie.
func (c *Client) AddCookies(data []*http.Cookie) *Client {
c.Use(cookies.AddMultiple(data))
return c
}
// CookieJar creates a cookie jar to store HTTP cookies when they are sent down.
func (c *Client) CookieJar() *Client {
c.Use(cookies.Jar())
return c
}
// UseContext adds a cancelation context to the client to enable the use of early cancelation. This is useful for
// server outgoing calls where we can attach the context from the incoming client. This will allow the downstream
// calls to be canceled early on the case of a tcp close or http2 cancellation.
// e.g. client.Get().URL("someUrl").UseContext( incomingServerRequest.Context() ).Send()
func (c *Client) UseContext(cancelContext gocontext.Context) *Client {
c.UseRequest(func(c *context.Context, handler context.Handler) {
handler.Next(c.SetCancelContext(cancelContext))
})
return c
}
// Use uses a new plugin to the middleware stack.
func (c *Client) Use(p plugin.Plugin) *Client {
c.Middleware.Use(p)
return c
}
// UseRequest uses a new middleware function for request phase.
func (c *Client) UseRequest(fn context.HandlerFunc) *Client {
c.Middleware.UseRequest(fn)
return c
}
// UseResponse uses a new middleware function for response phase.
func (c *Client) UseResponse(fn context.HandlerFunc) *Client {
c.Middleware.UseResponse(fn)
return c
}
// UseError uses a new middleware function for error phase.
func (c *Client) UseError(fn context.HandlerFunc) *Client {
c.Middleware.UseError(fn)
return c
}
// UseHandler uses a new middleware function for the given phase.
func (c *Client) UseHandler(phase string, fn context.HandlerFunc) *Client {
c.Middleware.UseHandler(phase, fn)
return c
}
// UseParent uses another Client as parent
// inheriting its middleware stack and configuration.
func (c *Client) UseParent(parent *Client) *Client {
c.Parent = parent
c.Context.UseParent(parent.Context)
c.Middleware.UseParent(parent.Middleware)
return c
}

@ -1,24 +0,0 @@
The MIT License
Copyright (c) 2016-2017 Tomas Aparicio
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -1,23 +0,0 @@
# gentleman/context [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/context?status.svg)](https://godoc.org/github.com/h2non/gentleman/context) [![API](https://img.shields.io/badge/status-stable-green.svg?style=flat)](https://godoc.org/github.com/h2non/gentleman/context) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman/context)](https://goreportcard.com/report/github.com/h2non/gentleman/context)
Package `context` implements a simple request-aware HTTP context used by plugins and exposed by the middleware layer, designed to share
polymorphic data across plugins in the middleware call chain.
It is built on top of the standard built-in [context](https://golang.org/pkg/context) package in Go.
gentleman's `context` also implements a valid stdlid `context.Context` interface:
https://golang.org/pkg/context/#Context
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/context
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/context) reference.
## License
MIT - Tomas Aparicio

@ -1,347 +0,0 @@
// Package context implements a request-aware HTTP context used by plugins
// and exposed by the middleware layer, designed to share polymorfic data
// types across plugins in the middleware call chain.
//
// It is built on top of the standard built-in context package in Go:
// https://golang.org/pkg/context
//
// gentleman's Context implements the stdlib `context.Context` interface:
// https://golang.org/pkg/context/#Context
package context
import (
"context"
"net/http"
"net/url"
"time"
"gopkg.in/h2non/gentleman.v2/utils"
)
// Key stores the key identifier for the built-in context
var Key interface{} = "$gentleman"
// Store represents the map store for context store.
type Store map[interface{}]interface{}
// Context encapsulates required domain-specific HTTP entities
// to share data and entities for HTTP transactions in a middleware chain
type Context struct {
// Stores the last error for the current Context
Error error
// Flag if the HTTP transaction was explicitly stopped in some phase
Stopped bool
// Context can inherit behavior and data from another Context
Parent *Context
// Reference to the http.Client used in the current HTTP transaction
Client *http.Client
// Reference to the http.Request used in the current HTTP transaction
Request *http.Request
// Reference to the http.Response used in the current HTTP transaction
Response *http.Response
}
// New creates an empty default Context
func New() *Context {
req := createRequest()
res := createResponse(req)
cli := &http.Client{Transport: http.DefaultTransport}
return &Context{Request: req, Response: res, Client: cli}
}
// getStore retrieves the current request context data store.
func (c *Context) getStore() Store {
store, ok := c.Request.Context().Value(Key).(Store)
if !ok {
panic("invalid request context")
}
return store
}
// Set sets a value on the current store
func (c *Context) Set(key interface{}, value interface{}) {
store := c.getStore()
store[key] = value
}
// Get gets a value by key in the current or parent context
func (c *Context) Get(key interface{}) interface{} {
store := c.getStore()
if store == nil {
return store
}
if value, ok := store[key]; ok {
return value
}
if c.Parent != nil {
return c.Parent.Get(key)
}
return nil
}
// GetOk gets a context value from req.
// Returns (nil, false) if key not found in the request context.
func (c *Context) GetOk(key interface{}) (interface{}, bool) {
store := c.getStore()
val, ok := store[key]
if !ok {
if c.Parent != nil {
return c.Parent.GetOk(key)
}
}
return val, ok
}
// GetInt gets an int context value from req.
// Returns an empty string if key not found in the request context,
// or the value does not evaluate to a string
func (c *Context) GetInt(key interface{}) (int, bool) {
value, ok := c.GetOk(key)
if !ok {
if c.Parent != nil {
return c.Parent.GetInt(key)
}
}
if num, ok := value.(int); ok {
return num, ok
}
return 0, false
}
// GetString gets a string context value from req.
// Returns an empty string if key not found in the request context,
// or the value does not evaluate to a string
func (c *Context) GetString(key interface{}) string {
store := c.getStore()
if value, ok := store[key]; ok {
if typed, ok := value.(string); ok {
return typed
}
}
if c.Parent != nil {
return c.Parent.GetString(key)
}
return ""
}
// GetAll returns all stored context values for a request.
// Will always return a valid map. Returns an empty map for
// requests context data previously set
func (c *Context) GetAll() Store {
buf := Store{}
for key, value := range c.getStore() {
buf[key] = value
}
if c.Parent != nil {
for key, value := range c.Parent.GetAll() {
buf[key] = value
}
}
return buf
}
// Delete deletes a stored value from a requests context
func (c *Context) Delete(key interface{}) {
delete(c.getStore(), key)
}
// Clear clears all stored values in the current requests context.
// Parent context store will not be cleaned.
func (c *Context) Clear() {
store := c.getStore()
for key := range store {
delete(store, key)
}
}
// UseParent uses a new parent Context
func (c *Context) UseParent(ctx *Context) {
c.Parent = ctx
}
// Root returns the root Context looking in the parent contexts recursively.
// If the current context has no parent context, it will return the Context itself.
func (c *Context) Root() *Context {
if c.Parent != nil {
return c.Parent.Root()
}
return c
}
// SetRequest replaces the context http.Request
func (c *Context) SetRequest(req *http.Request) {
c.Request = req.WithContext(c.Request.Context())
}
// Clone returns a clone of the current context.
func (c *Context) Clone() *Context {
ctx := new(Context)
*ctx = *c
req := new(http.Request)
*req = *c.Request
ctx.Request = req
c.CopyTo(ctx)
res := new(http.Response)
*res = *c.Response
ctx.Response = res
return ctx
}
// CopyTo copies the current context store into a new Context.
func (c *Context) CopyTo(newCtx *Context) {
store := Store{}
for key, value := range c.getStore() {
store[key] = value
}
ctx := context.WithValue(context.Background(), Key, store)
newCtx.Request = newCtx.Request.WithContext(ctx)
}
// Deadline returns the time when work done on behalf of this context
// should be canceled. Deadline returns ok==false when no deadline is
// set. Successive calls to Deadline return the same results.
func (c *Context) Deadline() (deadline time.Time, ok bool) {
return c.Request.Context().Deadline()
}
// Done returns a channel that's closed when work done on behalf of this
// context should be canceled. Done may return nil if this context can
// never be canceled. Successive calls to Done return the same value.
//
// WithCancel arranges for Done to be closed when cancel is called;
// WithDeadline arranges for Done to be closed when the deadline
// expires; WithTimeout arranges for Done to be closed when the timeout
// elapses.
//
// Done is provided for use in select statements:
//
// // Stream generates values with DoSomething and sends them to out
// // until DoSomething returns an error or ctx.Done is closed.
// func Stream(ctx context.Context, out chan<- Value) error {
// for {
// v, err := DoSomething(ctx)
// if err != nil {
// return err
// }
// select {
// case <-ctx.Done():
// return ctx.Err()
// case out <- v:
// }
// }
// }
//
// See https://blog.golang.org/pipelines for more examples of how to use
// a Done channel for cancelation.
func (c *Context) Done() <-chan struct{} {
return c.Request.Context().Done()
}
// Err returns a non-nil error value after Done is closed. Err returns
// Canceled if the context was canceled or DeadlineExceeded if the
// context's deadline passed. No other values for Err are defined.
// After Done is closed, successive calls to Err return the same value.
func (c *Context) Err() error {
return c.Request.Context().Err()
}
// Value returns the value associated with this context for key, or nil
// if no value is associated with key. Successive calls to Value with
// the same key returns the same result.
//
// Use context values only for request-scoped data that transits
// processes and API boundaries, not for passing optional parameters to
// functions.
//
// A key identifies a specific value in a Context. Functions that wish
// to store values in Context typically allocate a key in a global
// variable then use that key as the argument to context.WithValue and
// Context.Value. A key can be any type that supports equality;
// packages should define keys as an unexported type to avoid
// collisions.
//
// Packages that define a Context key should provide type-safe accessors
// for the values stored using that key:
//
// // Package user defines a User type that's stored in Contexts.
// package user
//
// import "context"
//
// // User is the type of value stored in the Contexts.
// type User struct {...}
//
// // key is an unexported type for keys defined in this package.
// // This prevents collisions with keys defined in other packages.
// type key int
//
// // userKey is the key for user.User values in Contexts. It is
// // unexported; clients use user.NewContext and user.FromContext
// // instead of using this key directly.
// var userKey key = 0
//
// // NewContext returns a new Context that carries value u.
// func NewContext(ctx context.Context, u *User) context.Context {
// return context.WithValue(ctx, userKey, u)
// }
//
// // FromContext returns the User value stored in ctx, if any.
// func FromContext(ctx context.Context) (*User, bool) {
// u, ok := ctx.Value(userKey).(*User)
// return u, ok
// }
func (c *Context) Value(key interface{}) interface{} {
return c.Request.Context().Value(key)
}
// SetCancelContext This will set an external context.Context as a parent to this context so cancellations can be
// propagated quickly and reduce resource usage.
func (c *Context) SetCancelContext(ctx context.Context) *Context {
golRequestContext := context.WithValue(ctx, Key, c.Value(Key))
c.Request = c.Request.WithContext(golRequestContext)
return c
}
// emptyContext creates a new empty context.Context
func emptyContext() context.Context {
return context.WithValue(context.Background(), Key, Store{})
}
// createRequest creates a default http.Request instance.
func createRequest() *http.Request {
// Create HTTP request
req := &http.Request{
Method: "GET",
URL: &url.URL{},
Host: "",
ProtoMajor: 1,
ProtoMinor: 1,
Proto: "HTTP/1.1",
Header: make(http.Header),
Body: utils.NopCloser(),
}
// Return shallow copy of Request with the new context
return req.WithContext(emptyContext())
}
// createResponse creates a default http.Response instance.
func createResponse(req *http.Request) *http.Response {
return &http.Response{
ProtoMajor: 1,
ProtoMinor: 1,
Proto: "HTTP/1.1",
Request: req,
Header: make(http.Header),
Body: utils.NopCloser(),
}
}

@ -1,65 +0,0 @@
package context
// HandlerCtx represents a Context only function handler
type HandlerCtx func(*Context)
// HandlerFunc represents a middleware function handler interface
type HandlerFunc func(*Context, Handler)
// Handler exposes a simple interface providing
// control flow capabilities to middleware functions
type Handler interface {
// Next handler invokes the next plugin in the middleware
// call chain with the given Context
Next(*Context)
// Stop handler stops the current middleware call chain
// with the given Context
Stop(*Context)
// Error handler reports an error and stops the middleware
// call chain with the given Context
Error(*Context, error)
}
// handler encapsulates a HandlerCtx function
type handler struct {
next HandlerCtx
}
// NewHandler creates a new Handler based
// on a given HandlerCtx function
func NewHandler(fn HandlerCtx) Handler {
return handler{once(fn)}
}
// Next continues executing the next middleware
// function in the call chain
func (h handler) Next(ctx *Context) {
h.next(ctx)
}
// Error reports an error and stops the middleware call chain
func (h handler) Error(ctx *Context, err error) {
ctx.Error = err
h.next(ctx)
}
// Stop stops the middleware call chain with a custom Context
func (h handler) Stop(ctx *Context) {
ctx.Stopped = true
h.next(ctx)
}
// once returns a function that can be executed once
// Subsequent calls will be no-op
func once(fn HandlerCtx) HandlerCtx {
called := false
return func(ctx *Context) {
if called {
return
}
called = true
fn(ctx)
}
}

@ -1,154 +0,0 @@
package gentleman
import (
c "gopkg.in/h2non/gentleman.v2/context"
)
// Dispatcher dispatches a given request triggering the middleware
// layer per specific phase and handling the request/response/error
// states accondingly.
type Dispatcher struct {
req *Request
}
// task function represents the required interface for dispatcher pipeline tasks.
type task func(*c.Context) (*c.Context, bool)
// NewDispatcher creates a new Dispatcher based on the given Context.
func NewDispatcher(req *Request) *Dispatcher {
return &Dispatcher{req}
}
// Dispatch triggers the middleware chains and performs the HTTP request.
func (d *Dispatcher) Dispatch() *c.Context {
// Pipeline of tasks to execute in FIFO order
pipeline := []task{
func(ctx *c.Context) (*c.Context, bool) {
return d.runBefore("request", ctx)
},
func(ctx *c.Context) (*c.Context, bool) {
return d.runBefore("before dial", ctx)
},
func(ctx *c.Context) (*c.Context, bool) {
return d.doDial(ctx)
},
func(ctx *c.Context) (*c.Context, bool) {
return d.runAfter("after dial", ctx)
},
func(ctx *c.Context) (*c.Context, bool) {
return d.runAfter("response", ctx)
},
}
// Reference to initial context
ctx := d.req.Context
// Execute tasks in order, stopping in case of error or explicit stop.
for _, task := range pipeline {
var stop bool
if ctx, stop = task(ctx); stop {
break
}
}
return ctx
}
func (d *Dispatcher) doDial(ctx *c.Context) (*c.Context, bool) {
// Perform the request via ctx.Client
res, err := ctx.Client.Do(ctx.Request)
ctx.Error = err
if err != nil {
ctx = d.req.Middleware.Run("error", ctx)
if ctx.Error != nil {
return ctx, true
}
}
// Assign response if present
if res != nil {
ctx.Response = res
}
return ctx, false
}
func (d *Dispatcher) runBefore(phase string, ctx *c.Context) (*c.Context, bool) {
// Run the request middleware
ctx, stop := d.run(phase, ctx)
if stop {
return ctx, true
}
// Verify if the response was injected
ctx, stop = d.intercepted(ctx)
if stop {
return ctx, true
}
// Verify if the should stop
return d.stop(ctx)
}
func (d *Dispatcher) runAfter(phase string, ctx *c.Context) (*c.Context, bool) {
// Trigger the after dial phase
ctx, stop := d.run(phase, ctx)
if stop {
return ctx, true
}
// Verify if the should stop
return d.stop(ctx)
}
func (d *Dispatcher) intercepted(ctx *c.Context) (*c.Context, bool) {
// Verify if the request was intercepted
if ctx.Response.StatusCode == 0 {
return ctx, false
}
// Trigger the intercept middleware
ctx, stop := d.run("intercept", ctx)
if stop {
return ctx, true
}
// Finally trigger the response middleware
ctx, _ = d.run("response", ctx)
return ctx, true
}
func (d *Dispatcher) stop(ctx *c.Context) (*c.Context, bool) {
if !ctx.Stopped {
return ctx, false
}
mw := d.req.Middleware
ctx = mw.Run("stop", ctx)
if ctx.Error != nil {
ctx = mw.Run("error", ctx)
if ctx.Error != nil {
return ctx, true
}
}
return ctx, ctx.Stopped
}
func (d *Dispatcher) run(phase string, ctx *c.Context) (*c.Context, bool) {
mw := d.req.Middleware
// Run the middleware by phase
ctx = mw.Run(phase, ctx)
if ctx.Error == nil {
return ctx, false
}
// Run error middleware
ctx = mw.Run("error", ctx)
if ctx.Error != nil {
return ctx, true
}
return ctx, false
}

@ -1,24 +0,0 @@
The MIT License
Copyright (c) 2016-2017 Tomas Aparicio
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -1,17 +0,0 @@
# gentleman/middleware [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/middleware?status.svg)](https://godoc.org/github.com/h2non/gentleman/middleware) [![API](https://img.shields.io/badge/status-stable-green.svg?style=flat)](https://godoc.org/github.com/h2non/gentleman/middleware) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman/middleware)](https://goreportcard.com/report/github.com/h2non/gentleman/middleware)
`middleware` package implements a simple middleware layer especially designed for HTTP client domain and full HTTP request/response live cycle.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/middleware
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/middleware) reference.
## License
MIT - Tomas Aparicio

@ -1,232 +0,0 @@
// Package middleware implements an HTTP client domain-specific phase-oriented
// middleware layer used internally by gentleman packages.
package middleware
import (
"sync"
c "gopkg.in/h2non/gentleman.v2/context"
"gopkg.in/h2non/gentleman.v2/plugin"
)
// Middleware especifies the required interface that must be
// implemented by middleware capable interfaces.
type Middleware interface {
// Use method is used to register a new plugin in the middleware stack.
Use(plugin.Plugin) Middleware
// UseError is used to register a new error phase middleware function handler.
UseError(c.HandlerFunc) Middleware
// UseRequest is used to register a new request phase middleware function handler.
UseRequest(c.HandlerFunc) Middleware
// UseResposne is used to register a new resposne phase middleware function handler.
UseResponse(c.HandlerFunc) Middleware
// UseHandler is used to register a new phase specific middleware function handler.
UseHandler(string, c.HandlerFunc) Middleware
// Run is used to dispatch the middleware call chain for a specific phase.
Run(string, *c.Context) *c.Context
// UseParent defines a parent middleware for easy inheritance.
UseParent(Middleware) Middleware
// Clone is used to created a new clone of the existent middleware.
Clone() Middleware
// Flush is used to flush the middleware stack.
Flush()
// GetStack is used to retrieve an array of registered plugins.
GetStack() []plugin.Plugin
// SetStack is used to override the stack of registered plugins.
SetStack([]plugin.Plugin)
}
// Layer type represent an HTTP domain
// specific middleware layer with inheritance support.
type Layer struct {
// mtx protects data races for stack
mtx sync.RWMutex
// stack stores the plugins registered in the current middleware instance.
stack []plugin.Plugin
// parent points to a parent middleware for behavior inheritance.
parent Middleware
}
// New creates a new middleware layer.
func New() *Layer {
return &Layer{}
}
// Use registers a new plugin to the middleware stack.
func (s *Layer) Use(plugin plugin.Plugin) Middleware {
s.mtx.Lock()
s.stack = append(s.stack, plugin)
s.mtx.Unlock()
return s
}
// UseHandler registers a phase specific plugin handler in the middleware stack.
func (s *Layer) UseHandler(phase string, fn c.HandlerFunc) Middleware {
s.mtx.Lock()
s.stack = append(s.stack, plugin.NewPhasePlugin(phase, fn))
s.mtx.Unlock()
return s
}
// UseResponse registers a new response phase middleware handler.
func (s *Layer) UseResponse(fn c.HandlerFunc) Middleware {
s.mtx.Lock()
s.stack = append(s.stack, plugin.NewResponsePlugin(fn))
s.mtx.Unlock()
return s
}
// UseRequest registers a new request phase middleware handler.
func (s *Layer) UseRequest(fn c.HandlerFunc) Middleware {
s.mtx.Lock()
s.stack = append(s.stack, plugin.NewRequestPlugin(fn))
s.mtx.Unlock()
return s
}
// UseError registers a new error phase middleware handler.
func (s *Layer) UseError(fn c.HandlerFunc) Middleware {
s.mtx.Lock()
s.stack = append(s.stack, plugin.NewErrorPlugin(fn))
s.mtx.Unlock()
return s
}
// UseParent attachs a parent middleware.
func (s *Layer) UseParent(parent Middleware) Middleware {
s.mtx.Lock()
s.parent = parent
s.mtx.Unlock()
return s
}
// Flush flushes the plugins stack.
func (s *Layer) Flush() {
s.mtx.Lock()
s.stack = s.stack[:0]
s.mtx.Unlock()
}
// SetStack sets the middleware plugin stack overriding the existent one.
func (s *Layer) SetStack(stack []plugin.Plugin) {
s.mtx.Lock()
s.stack = stack
s.mtx.Unlock()
}
// GetStack gets the current middleware plugins stack.
func (s *Layer) GetStack() []plugin.Plugin {
s.mtx.RLock()
defer s.mtx.RUnlock()
return s.stack
}
// Clone creates a new Middleware instance based on the current one.
func (s *Layer) Clone() Middleware {
mw := New()
mw.parent = s.parent
s.mtx.Lock()
mw.stack = append([]plugin.Plugin(nil), s.stack...)
s.mtx.Unlock()
return mw
}
// Run triggers the middleware call chain for the given phase.
func (s *Layer) Run(phase string, ctx *c.Context) *c.Context {
if s.parent != nil {
ctx = s.parent.Run(phase, ctx)
if phase != "error" && (ctx.Error != nil || ctx.Stopped) {
return ctx
}
}
s.mtx.Lock()
s.stack = filter(s.stack)
s.mtx.Unlock()
s.mtx.RLock()
defer s.mtx.RUnlock()
return trigger(phase, s.stack, ctx)
}
func filter(stack []plugin.Plugin) []plugin.Plugin {
buf := []plugin.Plugin{}
for _, plugin := range stack {
if !plugin.Removed() {
buf = append(buf, plugin)
}
}
return buf
}
// Note: this implementation may change in the future
func trigger(phase string, stack []plugin.Plugin, ctx *c.Context) *c.Context {
var wg sync.WaitGroup
wg.Add(1)
// Finisher function
done := func(_ctx *c.Context) {
ctx = _ctx
wg.Done()
}
i := len(stack)
if i == 0 {
wg.Done()
return ctx
}
next := done
for i > 0 {
i--
next = nextHandler(phase, stack[i], next, done)
}
// Exposes current middleware phase via context
ctx.Set("$phase", phase)
// Triggers the middleware call chain
next(ctx)
wg.Wait()
return ctx
}
func nextHandler(phase string, plugin plugin.Plugin, next c.HandlerCtx, done c.HandlerCtx) c.HandlerCtx {
return func(ctx *c.Context) {
handler := c.NewHandler(eval(phase, next, done))
plugin.Exec(phase, ctx, handler)
}
}
func eval(phase string, next c.HandlerCtx, done c.HandlerCtx) c.HandlerCtx {
return func(ctx *c.Context) {
if phase == "error" {
if ctx.Error == nil {
done(ctx)
return
}
next(ctx)
return
}
if ctx.Error != nil || (ctx.Stopped && phase != "stop") {
done(ctx)
return
}
next(ctx)
}
}

@ -1,24 +0,0 @@
The MIT License
Copyright (c) 2016-2017 Tomas Aparicio
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -1,101 +0,0 @@
# gentleman/mux [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/mux?status.svg)](https://godoc.org/github.com/h2non/gentleman/mux) [![API](https://img.shields.io/badge/status-stable-green.svg?style=flat)](https://godoc.org/github.com/h2non/gentleman/mux) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman/mux)](https://goreportcard.com/report/github.com/h2non/gentleman/mux)
`mux` package implements a versatile HTTP client multiplexer with built-in matchers for easy plugin composition.
multiplexer can be used to compose plugins for both request/response phases.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/mux
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/mux) reference.
## Example
Create a multiplexer filtered by a custom matcher function:
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/mux"
"gopkg.in/h2non/gentleman.v2/context"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
func main() {
// Create a new client
cli := gentleman.New()
// Use a custom multiplexer for GET requests
cli.Use(mux.New().AddMatcher(func (ctx *context.Context) bool {
return ctx.GetString("$phase") == "request" && ctx.Request.Method == "GET"
}).Use(url.URL("http://httpbin.org/headers")))
// Perform the request
res, err := cli.Request().Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
Plugin composition via multiplexer:
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/mux"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define the server url (must be first)
cli.Use(url.URL("http://httpbin.org"))
// Create a new multiplexer based on multiple matchers
mx := mux.If(mux.Method("GET"), mux.Host("httpbin.org"))
// Attach a custom plugin on the multiplexer that will be executed if the matchers passes
mx.Use(url.Path("/headers"))
// Attach the multiplexer on the main client
cli.Use(mx)
// Perform the request
res, err := cli.Request().Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,26 +0,0 @@
package mux
import (
c "gopkg.in/h2non/gentleman.v2/context"
)
// If creates a new multiplexer that will be executed if all the mux matchers passes.
func If(muxes ...*Mux) *Mux {
mx := New()
for _, mm := range muxes {
mx.AddMatcher(mm.Matchers...)
}
return mx
}
// Or creates a new multiplexer that will be executed if at least one mux matcher passes.
func Or(muxes ...*Mux) *Mux {
return Match(func(ctx *c.Context) bool {
for _, mm := range muxes {
if mm.Match(ctx) {
return true
}
}
return false
})
}

@ -1,159 +0,0 @@
package mux
import (
c "gopkg.in/h2non/gentleman.v2/context"
types "gopkg.in/h2non/gentleman.v2/plugins/bodytype"
"regexp"
"strings"
)
// Matcher represent the function interface implemented by matchers
type Matcher func(ctx *c.Context) bool
// Match creates a new multiplexer based on a given matcher function.
func Match(matchers ...Matcher) *Mux {
mx := New()
mx.AddMatcher(matchers...)
return mx
}
// Method returns a new multiplexer who matches an HTTP request based on the given method/s.
func Method(methods ...string) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "request" {
return false
}
for _, method := range methods {
if ctx.Request.Method == method {
return true
}
}
return false
})
}
// Path returns a new multiplexer who matches an HTTP request
// path based on the given regexp pattern.
func Path(pattern string) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "request" {
return false
}
matched, _ := regexp.MatchString(pattern, ctx.Request.URL.Path)
return matched
})
}
// URL returns a new multiplexer who matches an HTTP request
// URL based on the given regexp pattern.
func URL(pattern string) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "request" {
return false
}
matched, _ := regexp.MatchString(pattern, ctx.Request.URL.String())
return matched
})
}
// Host returns a new multiplexer who matches an HTTP request
// URL host based on the given regexp pattern.
func Host(pattern string) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "request" {
return false
}
matched, _ := regexp.MatchString(pattern, ctx.Request.URL.Host)
return matched
})
}
// Query returns a new multiplexer who matches an HTTP request
// query param based on the given key and regexp pattern.
func Query(key, pattern string) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "request" {
return false
}
matched, _ := regexp.MatchString(pattern, ctx.Request.URL.Query().Get(key))
return matched
})
}
// RequestHeader returns a new multiplexer who matches an HTTP request
// header field based on the given key and regexp pattern.
func RequestHeader(key, pattern string) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "request" {
return false
}
matched, _ := regexp.MatchString(pattern, ctx.Request.Header.Get(key))
return matched
})
}
// ResponseHeader returns a new multiplexer who matches an HTTP response
// header field based on the given key and regexp pattern.
func ResponseHeader(key, pattern string) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "response" {
return false
}
matched, _ := regexp.MatchString(pattern, ctx.Response.Header.Get(key))
return matched
})
}
// Type returns a new multiplexer who matches an HTTP response
// Content-Type header field based on the given type string.
func Type(kind string) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "response" {
return false
}
if value, ok := types.Types[kind]; ok {
kind = value
}
return strings.Contains(ctx.Response.Header.Get("Content-Type"), kind)
})
}
// Status returns a new multiplexer who matches an HTTP response
// status code based on the given status codes.
func Status(codes ...int) *Mux {
return Match(func(ctx *c.Context) bool {
if ctx.GetString("$phase") != "response" {
return false
}
for _, code := range codes {
if ctx.Response.StatusCode == code {
return true
}
}
return false
})
}
// StatusRange returns a new multiplexer who matches an HTTP response
// status code based on the given status range, including both numbers.
func StatusRange(start, end int) *Mux {
return Match(func(ctx *c.Context) bool {
return ctx.GetString("$phase") == "response" && ctx.Response.StatusCode >= start && ctx.Response.StatusCode <= end
})
}
// Error returns a new multiplexer who matches errors originated
// in the client or in the server.
func Error() *Mux {
return Match(func(ctx *c.Context) bool {
return (ctx.GetString("$phase") == "error" && ctx.Error != nil) ||
(ctx.GetString("$phase") == "response" && ctx.Response.StatusCode >= 500)
})
}
// ServerError returns a new multiplexer who matches response errors by the server.
func ServerError() *Mux {
return Match(func(ctx *c.Context) bool {
return ctx.GetString("$phase") == "response" && ctx.Response.StatusCode >= 500
})
}

@ -1,133 +0,0 @@
// Package mux implements an HTTP domain-specific traffic multiplexer
// with built-in matchers and features for easy plugin composition and activable logic.
package mux
import (
c "gopkg.in/h2non/gentleman.v2/context"
"gopkg.in/h2non/gentleman.v2/middleware"
"gopkg.in/h2non/gentleman.v2/plugin"
)
// Mux is a HTTP request/response/error multiplexer who implements both
// middleware and plugin interfaces.
// It has been designed for easy plugin composition based on HTTP matchers/filters.
type Mux struct {
// Mux also implements a plugin capable interface.
*plugin.Layer
// Matchers stores a list of matcher functions.
Matchers []Matcher
// Middleware stores the multiplexer middleware layer.
Middleware middleware.Middleware
}
// New creates a new multiplexer with default settings.
func New() *Mux {
m := &Mux{Layer: plugin.New()}
m.Middleware = middleware.New()
handler := m.Handler()
m.DefaultHandler = handler
return m
}
// Match matches the give Context againts a list of matchers and
// returns `true` if all the matchers passed.
func (m *Mux) Match(ctx *c.Context) bool {
for _, matcher := range m.Matchers {
if !matcher(ctx) {
return false
}
}
return true
}
// AddMatcher adds a new matcher function in the current mumultiplexer matchers stack.
func (m *Mux) AddMatcher(matchers ...Matcher) *Mux {
m.Matchers = append(m.Matchers, matchers...)
return m
}
// Handler returns the function handler to match an incoming HTTP transacion
// and trigger the equivalent middleware phase.
func (m *Mux) Handler() c.HandlerFunc {
return func(ctx *c.Context, h c.Handler) {
if !m.Match(ctx) {
h.Next(ctx)
return
}
ctx = m.Middleware.Run(ctx.GetString("$phase"), ctx)
if ctx.Error != nil {
h.Error(ctx, ctx.Error)
return
}
if ctx.Stopped {
h.Stop(ctx)
return
}
h.Next(ctx)
}
}
// Use registers a new plugin in the middleware stack.
func (m *Mux) Use(p plugin.Plugin) *Mux {
m.Middleware.Use(p)
return m
}
// UseResponse registers a new response phase middleware handler.
func (m *Mux) UseResponse(fn c.HandlerFunc) *Mux {
m.Middleware.UseResponse(fn)
return m
}
// UseRequest registers a new request phase middleware handler.
func (m *Mux) UseRequest(fn c.HandlerFunc) *Mux {
m.Middleware.UseRequest(fn)
return m
}
// UseError registers a new error phase middleware handler.
func (m *Mux) UseError(fn c.HandlerFunc) *Mux {
m.Middleware.UseError(fn)
return m
}
// UseHandler registers a new error phase middleware handler.
func (m *Mux) UseHandler(phase string, fn c.HandlerFunc) *Mux {
m.Middleware.UseHandler(phase, fn)
return m
}
// UseParent attachs a parent middleware.
func (m *Mux) UseParent(parent middleware.Middleware) *Mux {
m.Middleware.UseParent(parent)
return m
}
// Flush flushes the plugins stack.
func (m *Mux) Flush() {
m.Middleware.Flush()
}
// SetStack sets the middleware plugin stack overriding the existent one.
func (m *Mux) SetStack(stack []plugin.Plugin) {
m.Middleware.SetStack(stack)
}
// GetStack gets the current middleware plugins stack.
func (m *Mux) GetStack() []plugin.Plugin {
return m.Middleware.GetStack()
}
// Clone creates a new Middleware instance based on the current one.
func (m *Mux) Clone() middleware.Middleware {
return m.Middleware.Clone()
}
// Run triggers the middleware call chain for the given phase.
func (m *Mux) Run(phase string, ctx *c.Context) *c.Context {
return m.Middleware.Run(phase, ctx)
}

@ -1,24 +0,0 @@
The MIT License
Copyright (c) 2016-2017 Tomas Aparicio
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -1,61 +0,0 @@
# gentleman/plugin [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/plugin?status.svg)](https://godoc.org/github.com/h2non/gentleman/plugin) [![API](https://img.shields.io/badge/status-stable-green.svg?style=flat)](https://godoc.org/github.com/h2non/gentleman/plugin) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman/plugin)](https://goreportcard.com/report/github.com/h2non/gentleman/plugin)
`plugin` package implements a simple middleware-based plugin layer especially designed for HTTP clients and complete HTTP request/response live cycle control.
The package exposes a simple API with multiple factory helper functions to create plugins more easily.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/plugin
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/plugin) reference.
## Examples
#### Create a request plugin
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/context"
"gopkg.in/h2non/gentleman.v2/plugin"
"net/url"
)
func main() {
// Create a new client
cli := gentleman.New()
// Create a request plugin to define the URL
cli.Use(plugin.NewRequestPlugin(func(ctx *context.Context, h context.Handler) {
u, _ := url.Parse("http://httpbin.org/headers")
ctx.Request.URL = u
h.Next(ctx)
}))
// Perform the request
res, err := cli.Request().Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,135 +0,0 @@
// Package plugin implements a plugin layer for gentleman components.
// Exports the required interface that must be implemented by plugins.
//
// Plugins are phase-oriented middleware function handlers encapsulated
// in a simple interface that will be consumed by the middleware layer in
// order to trigger the plugin handler.
//
// Plugin implementors can decide to build a plugin to handle a unique
// middleware phase or instead handle multiple phases: request, response, error...
package plugin
import "gopkg.in/h2non/gentleman.v2/context"
// Plugin interface that must be implemented by plugins
type Plugin interface {
// Enable enabled the plugin
Enable()
// Disable disables the plugin
Disable()
// Disabled returns true if the plugin is enabled
Disabled() bool
// Remove will remove the plugin from the middleware stack
Remove()
// Enabled returns true if the plugin was removed
Removed() bool
// Exec executes the plugin handler for a specific middleware phase.
Exec(string, *context.Context, context.Handler)
}
// Handlers represents a map to store middleware handler functions per phase.
type Handlers map[string]context.HandlerFunc
// Layer encapsulates an Error, Request and Response function handlers
type Layer struct {
// removed stores if the plugin was removed
removed bool
// disabled stores if the plugin was disabled
disabled bool
// Handlers defines the required handlers
Handlers Handlers
// DefaultHandler is an optional field used to store
// a default handler for any middleware phase.
DefaultHandler context.HandlerFunc
}
// New creates a new plugin layer.
func New() *Layer {
return &Layer{Handlers: make(Handlers)}
}
// Disable will disable the current plugin
func (p *Layer) Disable() {
p.disabled = true
}
// Enable will enable the current plugin
func (p *Layer) Enable() {
p.disabled = false
}
// Disabled returns true if the plugin is enabled
func (p *Layer) Disabled() bool {
return p.disabled
}
// Remove will remove the plugin from the middleware stack
func (p *Layer) Remove() {
p.removed = true
}
// Removed returns true if the plugin Was removed
func (p *Layer) Removed() bool {
return p.removed
}
// SetHandler uses a new handler function for the given middleware phase.
func (p *Layer) SetHandler(phase string, handler context.HandlerFunc) {
p.Handlers[phase] = handler
}
// SetHandlers uses a new map of handler functions.
func (p *Layer) SetHandlers(handlers Handlers) {
p.Handlers = handlers
}
// Exec executes the plugin handler for the given middleware phase passing the given context.
func (p *Layer) Exec(phase string, ctx *context.Context, h context.Handler) {
if p.disabled || p.removed {
h.Next(ctx)
return
}
fn := p.Handlers[phase]
if fn == nil {
fn = p.DefaultHandler
}
if fn == nil {
h.Next(ctx)
return
}
fn(ctx, h)
}
// NewPhasePlugin creates a new plugin layer
// to handle a given middleware phase.
func NewPhasePlugin(phase string, handler context.HandlerFunc) Plugin {
return &Layer{Handlers: Handlers{phase: handler}}
}
// NewResponsePlugin creates a new plugin layer
// to handle response middleware phase
func NewResponsePlugin(handler context.HandlerFunc) Plugin {
return NewPhasePlugin("response", handler)
}
// NewRequestPlugin creates a new plugin layer
// to handle request middleware phase
func NewRequestPlugin(handler context.HandlerFunc) Plugin {
return NewPhasePlugin("request", handler)
}
// NewErrorPlugin creates a new plugin layer
// to handle error middleware phase
func NewErrorPlugin(handler context.HandlerFunc) Plugin {
return NewPhasePlugin("error", handler)
}

@ -1,52 +0,0 @@
# gentleman/body [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/plugins/body?status.svg)](https://godoc.org/github.com/h2non/gentleman/plugins/body) [![API](https://img.shields.io/badge/status-stable-green.svg?style=flat)](https://godoc.org/github.com/h2non/gentleman) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman)](https://goreportcard.com/report/github.com/h2non/gentleman)
gentleman's plugin to easy define HTTP bodies. Supports JSON, XML, strings or streams with interface polymorphism.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/plugins/body
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/plugins/body) reference.
## Example
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugins/body"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define the body we're going to send
data := map[string]string{"foo": "bar"}
cli.Use(body.JSON(data))
// Perform the request
res, err := cli.Request().Method("POST").URL("http://httpbin.org/post").Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,114 +0,0 @@
package body
import (
"bytes"
"encoding/json"
"encoding/xml"
"io"
"io/ioutil"
"strings"
c "gopkg.in/h2non/gentleman.v2/context"
p "gopkg.in/h2non/gentleman.v2/plugin"
"gopkg.in/h2non/gentleman.v2/utils"
)
// String defines the HTTP request body based on the given string.
func String(data string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.Method = getMethod(ctx)
ctx.Request.Body = utils.StringReader(data)
ctx.Request.ContentLength = int64(bytes.NewBufferString(data).Len())
h.Next(ctx)
})
}
// JSON defines a JSON body in the outgoing request.
// Supports strings, array of bytes or buffer.
func JSON(data interface{}) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
buf := &bytes.Buffer{}
switch data.(type) {
case string:
buf.WriteString(data.(string))
case []byte:
buf.Write(data.([]byte))
default:
if err := json.NewEncoder(buf).Encode(data); err != nil {
h.Error(ctx, err)
return
}
}
ctx.Request.Method = getMethod(ctx)
ctx.Request.Body = ioutil.NopCloser(buf)
ctx.Request.ContentLength = int64(buf.Len())
ctx.Request.Header.Set("Content-Type", "application/json")
h.Next(ctx)
})
}
// XML defines a XML body in the outgoing request.
// Supports strings, array of bytes or buffer.
func XML(data interface{}) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
buf := &bytes.Buffer{}
switch data.(type) {
case string:
buf.WriteString(data.(string))
case []byte:
buf.Write(data.([]byte))
default:
if err := xml.NewEncoder(buf).Encode(data); err != nil {
h.Error(ctx, err)
return
}
}
ctx.Request.Method = getMethod(ctx)
ctx.Request.Body = ioutil.NopCloser(buf)
ctx.Request.ContentLength = int64(buf.Len())
ctx.Request.Header.Set("Content-Type", "application/xml")
h.Next(ctx)
})
}
// Reader defines a io.Reader stream as request body.
// Content-Type header won't be defined automatically, you have to declare it manually.
func Reader(body io.Reader) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
rc, ok := body.(io.ReadCloser)
if !ok && body != nil {
rc = ioutil.NopCloser(body)
}
req := ctx.Request
if body != nil {
switch v := body.(type) {
case *bytes.Buffer:
req.ContentLength = int64(v.Len())
case *bytes.Reader:
req.ContentLength = int64(v.Len())
case *strings.Reader:
req.ContentLength = int64(v.Len())
}
}
req.Body = rc
ctx.Request.Method = getMethod(ctx)
h.Next(ctx)
})
}
func getMethod(ctx *c.Context) string {
method := ctx.Request.Method
if method == "" {
return "POST"
}
return method
}

@ -1,66 +0,0 @@
# gentleman/bodytype [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/plugins/bodytype?status.svg)](https://godoc.org/github.com/h2non/gentleman/plugins/bodytype) [![API](https://img.shields.io/badge/status-stable-green.svg?style=flat)](https://godoc.org/github.com/h2non/gentleman/plugins/bodytype) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman)](https://goreportcard.com/report/github.com/h2non/gentleman)
gentleman's plugin to easy define HTTP bodies. Supports JSON, XML, strings or streams with interface polymorphism.
Supported type aliases:
- html - `text/html`
- json - `application/json`
- xml - `application/xml`
- text - `text/plain`
- urlencoded - `application/x-www-form-urlencoded`
- form - `application/x-www-form-urlencoded`
- form-data - `application/x-www-form-urlencoded`
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/plugins/bodytype
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/plugins/bodytype) reference.
## Example
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/bodytype"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define the JSON data to send
data := `{"foo":"bar"}`
cli.Use(body.String(data))
// We're sending a JSON based payload
cli.Use(bodytype.Type("json"))
// Perform the request
res, err := cli.Request().Method("POST").URL("http://httpbin.org/post").Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,39 +0,0 @@
package bodytype
import (
c "gopkg.in/h2non/gentleman.v2/context"
p "gopkg.in/h2non/gentleman.v2/plugin"
"net/http"
)
// Types is a map of MIME type aliases
var Types = map[string]string{
"html": "text/html",
"json": "application/json",
"xml": "application/xml",
"text": "text/plain",
"urlencoded": "application/x-www-form-urlencoded",
"form": "application/x-www-form-urlencoded",
"form-data": "application/x-www-form-urlencoded",
}
// Set sets the Content Type header value, optionally based on a MIME type alias.
func Set(name string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
defineType(name, ctx.Request)
h.Next(ctx)
})
}
// Type is an alias to Set, which defines the Content-Type header
func Type(name string) p.Plugin {
return Set(name)
}
func defineType(name string, req *http.Request) {
match := Types[name]
if match == "" {
match = name
}
req.Header.Set("Content-Type", match)
}

@ -1,54 +0,0 @@
# gentleman/cookies [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/plugins/cookies?status.svg)](https://godoc.org/github.com/h2non/gentleman/plugins/cookies) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman)](https://goreportcard.com/report/github.com/h2non/gentleman)
gentleman's plugin to easily deal and manage cookies HTTP clients.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/plugins/cookies
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/plugins/cookies) reference.
## Example
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugins/cookies"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define cookies
cli.Use(cookies.Set("foo", "bar"))
// Configure cookie jar store
cli.Use(cookies.Jar())
// Perform the request
res, err := cli.Request().URL("http://httpbin.org/cookies").Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,66 +0,0 @@
package cookies
import (
"golang.org/x/net/publicsuffix"
c "gopkg.in/h2non/gentleman.v2/context"
p "gopkg.in/h2non/gentleman.v2/plugin"
"net/http"
"net/http/cookiejar"
)
// Add adds a cookie to the request. Per RFC 6265 section 5.4, AddCookie does not
// attach more than one Cookie header field.
// That means all cookies, if any, are written into the same line, separated by semicolon.
func Add(cookie *http.Cookie) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.AddCookie(cookie)
h.Next(ctx)
})
}
// Set sets a new cookie field by key and value.
func Set(key, value string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
cookie := &http.Cookie{Name: key, Value: value}
ctx.Request.AddCookie(cookie)
h.Next(ctx)
})
}
// DelAll deletes all the cookies by deleting the Cookie header field.
func DelAll() p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.Header.Del("Cookie")
h.Next(ctx)
})
}
// SetMap sets a map of cookies represented by key-value pair.
func SetMap(cookies map[string]string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
for k, v := range cookies {
cookie := &http.Cookie{Name: k, Value: v}
ctx.Request.AddCookie(cookie)
}
h.Next(ctx)
})
}
// AddMultiple adds a list of cookies.
func AddMultiple(cookies []*http.Cookie) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
for _, cookie := range cookies {
ctx.Request.AddCookie(cookie)
}
h.Next(ctx)
})
}
// Jar creates a cookie jar to store HTTP cookies when they are sent down.
func Jar() p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
ctx.Client.Jar = jar
h.Next(ctx)
})
}

@ -1,54 +0,0 @@
# gentleman/headers [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/plugins/headers?status.svg)](https://godoc.org/github.com/h2non/gentleman/plugins/headers) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman)](https://goreportcard.com/report/github.com/h2non/gentleman)
gentleman's plugin to easily manage HTTP headers.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/plugins/headers
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/plugins/headers) reference.
## Example
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugins/headers"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define a custom header
cli.Use(headers.Set("API-Token", "s3cr3t"))
// Remove a header
cli.Use(headers.Del("User-Agent"))
// Perform the request
res, err := cli.Request().URL("http://httpbin.org/headers").Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,42 +0,0 @@
package headers
import (
c "gopkg.in/h2non/gentleman.v2/context"
p "gopkg.in/h2non/gentleman.v2/plugin"
)
// Set sets the header entries associated with key to the single element value.
// It replaces any existing values associated with key.
func Set(key, value string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.Header.Set(key, value)
h.Next(ctx)
})
}
// Add adds the key, value pair to the header.
// It appends to any existing values associated with key.
func Add(key, value string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.Header.Add(key, value)
h.Next(ctx)
})
}
// Del deletes the header fields associated with key.
func Del(key string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.Header.Del(key)
h.Next(ctx)
})
}
// SetMap sets a map of headers represented by key-value pair.
func SetMap(headers map[string]string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
for k, v := range headers {
ctx.Request.Header.Set(k, v)
}
h.Next(ctx)
})
}

@ -1,52 +0,0 @@
# gentleman/multipart [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/plugins/multipart?status.svg)](https://godoc.org/github.com/h2non/gentleman/plugins/multipart) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman)](https://goreportcard.com/report/github.com/h2non/gentleman)
gentleman's plugin to easily define `multipart/form-data` bodies supporting files and string based fields.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/plugins/multipart
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/plugins/multipart) reference.
## Example
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugins/multipart"
)
func main() {
// Create a new client
cli := gentleman.New()
// Create a text based form fields
fields := map[string]string{"foo": "bar", "bar": "baz"}
cli.Use(multipart.Fields(fields))
// Perform the request
res, err := cli.Request().Method("POST").URL("http://httpbin.org/post").Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,139 +0,0 @@
package multipart
import (
"bytes"
"errors"
"io"
"io/ioutil"
"mime/multipart"
"strconv"
"strings"
c "gopkg.in/h2non/gentleman.v2/context"
p "gopkg.in/h2non/gentleman.v2/plugin"
)
// Values represents multiple multipart from values.
type Values []string
// DataFields represents a map of text based fields.
type DataFields map[string]Values
// FormFile represents the file form field data.
type FormFile struct {
Name string
Reader io.Reader
}
// FormData represents the supported form fields by file and string data.
type FormData struct {
Data DataFields
Files []FormFile
}
// File creates a new multipart form based on a unique file field
// from the given io.ReadCloser stream.
func File(name string, reader io.Reader) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
file := FormFile{name, reader}
data := FormData{Files: []FormFile{file}}
handle(ctx, h, data)
})
}
// Files creates a multipart form based on files fields.
func Files(files []FormFile) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
data := FormData{Files: files}
handle(ctx, h, data)
})
}
// Fields creates a new multipart form based on string based fields.
func Fields(fields DataFields) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
data := FormData{Data: fields}
handle(ctx, h, data)
})
}
// Data creates custom form based on the given form data
// who can have files and string based fields.
func Data(data FormData) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
handle(ctx, h, data)
})
}
func handle(ctx *c.Context, h c.Handler, data FormData) {
if err := createForm(data, ctx); err != nil {
h.Error(ctx, err)
return
}
h.Next(ctx)
}
func createForm(data FormData, ctx *c.Context) error {
body := &bytes.Buffer{}
multipartWriter := multipart.NewWriter(body)
for index, file := range data.Files {
if err := writeFile(multipartWriter, data, file, index); err != nil {
return err
}
}
// Populate the other parts of the form (if there are any)
for key, values := range data.Data {
for _, value := range values {
multipartWriter.WriteField(key, value)
}
}
if err := multipartWriter.Close(); err != nil {
return err
}
ctx.Request.Method = setMethod(ctx)
ctx.Request.Body = ioutil.NopCloser(body)
ctx.Request.Header.Add("Content-Type", multipartWriter.FormDataContentType())
return nil
}
func writeFile(multipartWriter *multipart.Writer, data FormData, file FormFile, index int) error {
if file.Reader == nil {
return errors.New("gentleman: file reader cannot be nil")
}
rc, ok := file.Reader.(io.ReadCloser)
if !ok && file.Reader != nil {
rc = ioutil.NopCloser(file.Reader)
}
fileName := "file"
if len(data.Files) > 1 {
fileName = strings.Join([]string{fileName, strconv.Itoa(index + 1)}, "")
}
if file.Name != "" {
fileName = file.Name
}
writer, err := multipartWriter.CreateFormFile(fileName, file.Name)
if err != nil {
return err
}
if _, err = io.Copy(writer, rc); err != nil && err != io.EOF {
return err
}
rc.Close()
return nil
}
func setMethod(ctx *c.Context) string {
method := ctx.Request.Method
if method == "GET" || method == "" {
return "POST"
}
return method
}

@ -1,59 +0,0 @@
# gentleman/query [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/plugins/query?status.svg)](https://godoc.org/github.com/h2non/gentleman/plugins/query) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman)](https://goreportcard.com/report/github.com/h2non/gentleman)
gentleman's plugin to easily manage HTTP query params.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/plugins/query
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/plugins/query) reference.
## Example
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugins/query"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define the base URL to use
cli.Use(url.BaseURL("http://httpbin.org"))
cli.Use(url.Path("/get"))
// Define a custom query param
cli.Use(query.Set("foo", "bar"))
// Remove a query param
cli.Use(query.Del("bar"))
// Perform the request
res, err := cli.Request().Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,58 +0,0 @@
package query
import (
c "gopkg.in/h2non/gentleman.v2/context"
p "gopkg.in/h2non/gentleman.v2/plugin"
)
// Set sets the query param key and value.
// It replaces any existing values.
func Set(key, value string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
query := ctx.Request.URL.Query()
query.Set(key, value)
ctx.Request.URL.RawQuery = query.Encode()
h.Next(ctx)
})
}
// Add adds the query param value to key.
// It appends to any existing values associated with key.
func Add(key, value string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
query := ctx.Request.URL.Query()
query.Add(key, value)
ctx.Request.URL.RawQuery = query.Encode()
h.Next(ctx)
})
}
// Del deletes the query param values associated with key.
func Del(key string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
query := ctx.Request.URL.Query()
query.Del(key)
ctx.Request.URL.RawQuery = query.Encode()
h.Next(ctx)
})
}
// DelAll deletes all the query params.
func DelAll() p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.URL.RawQuery = ""
h.Next(ctx)
})
}
// SetMap sets a map of query params by key-value pair.
func SetMap(params map[string]string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
query := ctx.Request.URL.Query()
for k, v := range params {
query.Set(k, v)
}
ctx.Request.URL.RawQuery = query.Encode()
h.Next(ctx)
})
}

@ -1,59 +0,0 @@
# gentleman/url [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/plugins/url?status.svg)](https://godoc.org/github.com/h2non/gentleman/plugins/url) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman/plugins/url)](https://goreportcard.com/report/github.com/h2non/gentleman/plugins/url)
gentleman's plugin to easily define URL fields in HTTP requests.
Supports full URL parsing, base URL, base path, full path and dynamic path params templating.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/plugins/url
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/plugins/url) reference.
## Example
```go
package main
import (
"fmt"
"gopkg.in/h2non/gentleman.v2"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
func main() {
// Create a new client
cli := gentleman.New()
// Define the base URL
cli.Use(url.BaseURL("http://httpbin.org"))
// Define the path with dynamic value
cli.Use(url.Path("/:resource"))
// Define the path value to be replaced
cli.Use(url.Param("resource", "get"))
// Perform the request
res, err := cli.Request().Send()
if err != nil {
fmt.Printf("Request error: %s\n", err)
return
}
if !res.Ok {
fmt.Printf("Invalid server response: %d\n", res.StatusCode)
return
}
fmt.Printf("Status: %d\n", res.StatusCode)
fmt.Printf("Body: %s", res.String())
}
```
## License
MIT - Tomas Aparicio

@ -1,105 +0,0 @@
package url
import (
"net/url"
"regexp"
"strings"
c "gopkg.in/h2non/gentleman.v2/context"
p "gopkg.in/h2non/gentleman.v2/plugin"
)
// URL parses and defines a new URL in the outgoing request
func URL(uri string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
u, err := url.Parse(normalize(uri))
if err != nil {
h.Error(ctx, err)
return
}
ctx.Request.URL = u
h.Next(ctx)
})
}
// BaseURL parses and defines a schema and host URL values in the outgoing request
func BaseURL(uri string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
u, err := url.Parse(normalize(uri))
if err != nil {
h.Error(ctx, err)
return
}
ctx.Request.URL.Scheme = u.Scheme
ctx.Request.URL.Host = u.Host
if u.Path != "" && u.Path != "/" {
ctx.Request.URL.Path = normalizePath(u.Path)
}
h.Next(ctx)
})
}
// Path defines a new URL path in the outgoing request
func Path(path string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.URL.Path = normalizePath(path)
h.Next(ctx)
})
}
// AddPath concatenates a path slice to the existent path in the outgoing request
func AddPath(path string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.URL.Path += normalizePath(path)
h.Next(ctx)
})
}
// PathPrefix defines a path prefix to the existent path in the outgoing request
func PathPrefix(path string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.URL.Path = normalizePath(path) + ctx.Request.URL.Path
h.Next(ctx)
})
}
// Param replaces one or multiple path param expressions by the given value
func Param(key, value string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
ctx.Request.URL.Path = replace(ctx.Request.URL.Path, key, value)
h.Next(ctx)
})
}
// Params replaces one or multiple path param expressions by the given map of key-value pairs
func Params(params map[string]string) p.Plugin {
return p.NewRequestPlugin(func(ctx *c.Context, h c.Handler) {
for key, value := range params {
ctx.Request.URL.Path = replace(ctx.Request.URL.Path, key, value)
}
h.Next(ctx)
})
}
func replace(str, key, value string) string {
return strings.Replace(str, ":"+key, value, -1)
}
func normalizePath(path string) string {
if path == "/" {
return ""
}
return path
}
func normalize(uri string) string {
match, _ := regexp.MatchString("^http[s]?://", uri)
if match {
return uri
}
return "http://" + uri
}

@ -1,330 +0,0 @@
package gentleman
import (
"errors"
"io"
"net"
"net/http"
"time"
"gopkg.in/h2non/gentleman.v2/context"
"gopkg.in/h2non/gentleman.v2/middleware"
"gopkg.in/h2non/gentleman.v2/mux"
"gopkg.in/h2non/gentleman.v2/plugin"
"gopkg.in/h2non/gentleman.v2/plugins/body"
"gopkg.in/h2non/gentleman.v2/plugins/bodytype"
"gopkg.in/h2non/gentleman.v2/plugins/cookies"
"gopkg.in/h2non/gentleman.v2/plugins/headers"
"gopkg.in/h2non/gentleman.v2/plugins/multipart"
"gopkg.in/h2non/gentleman.v2/plugins/query"
"gopkg.in/h2non/gentleman.v2/plugins/url"
)
const (
// UserAgent represents the static user agent name and version.
UserAgent = "gentleman/" + Version
)
var (
// DialTimeout represents the maximum amount of time the network dialer can take.
DialTimeout = 30 * time.Second
// DialKeepAlive represents the maximum amount of time too keep alive the socket.
DialKeepAlive = 30 * time.Second
// TLSHandshakeTimeout represents the maximum amount of time that
// TLS handshake can take defined in the default http.Transport.
TLSHandshakeTimeout = 10 * time.Second
// RequestTimeout represents the maximum about of time that
// a request can take, including dial / request / redirect processes.
RequestTimeout = 60 * time.Second
// DefaultDialer defines the default network dialer.
DefaultDialer = &net.Dialer{
Timeout: DialTimeout,
KeepAlive: DialKeepAlive,
}
// DefaultTransport stores the default HTTP transport to be used.
DefaultTransport = NewDefaultTransport(DefaultDialer)
)
// Request HTTP entity for gentleman.
// Provides middleware capabilities, built-in context
// and convenient methods to easily setup request params.
type Request struct {
// Stores if the request was already dispatched
dispatched bool
// Optional reference to the gentleman.Client instance
Client *Client
// Request scope Context instance
Context *context.Context
// Request scope Middleware instance
Middleware middleware.Middleware
}
// NewRequest creates a new Request entity.
func NewRequest() *Request {
ctx := context.New()
ctx.Client.Transport = DefaultTransport
ctx.Request.Header.Set("User-Agent", UserAgent)
return &Request{
Context: ctx,
Middleware: middleware.New(),
}
}
// SetClient Attach a client to the current Request
// This is mostly done internally.
func (r *Request) SetClient(cli *Client) *Request {
r.Client = cli
r.Context.UseParent(cli.Context)
r.Middleware.UseParent(cli.Middleware)
return r
}
// Mux is a middleware multiplexer for easy plugin composition.
func (r *Request) Mux() *mux.Mux {
mx := mux.New()
r.Use(mx)
return mx
}
// Method defines the HTTP verb to be used.
func (r *Request) Method(method string) *Request {
r.Middleware.UseRequest(func(ctx *context.Context, h context.Handler) {
ctx.Request.Method = method
h.Next(ctx)
})
return r
}
// URL parses and defines the URL to be used in the outgoing request.
func (r *Request) URL(uri string) *Request {
r.Use(url.URL(uri))
return r
}
// BaseURL parses the given URL and uses the URL schema and host in the outgoing request.
func (r *Request) BaseURL(uri string) *Request {
r.Use(url.BaseURL(uri))
return r
}
// Path defines the request URL path to be used in the outgoing request.
func (r *Request) Path(path string) *Request {
r.Use(url.Path(path))
return r
}
// AddPath concatenates a path slice to the existent path in at request level.
func (r *Request) AddPath(path string) *Request {
r.Use(url.AddPath(path))
return r
}
// Param replaces a path param based on the given param name and value.
func (r *Request) Param(name, value string) *Request {
r.Use(url.Param(name, value))
return r
}
// Params replaces path params based on the given params key-value map.
func (r *Request) Params(params map[string]string) *Request {
r.Use(url.Params(params))
return r
}
// SetQuery sets a new URL query param field.
// If another query param exists with the same key, it will be overwritten.
func (r *Request) SetQuery(name, value string) *Request {
r.Use(query.Set(name, value))
return r
}
// AddQuery adds a new URL query param field
// without overwriting any existent query field.
func (r *Request) AddQuery(name, value string) *Request {
r.Use(query.Add(name, value))
return r
}
// SetQueryParams sets URL query params based on the given map.
func (r *Request) SetQueryParams(params map[string]string) *Request {
r.Use(query.SetMap(params))
return r
}
// SetHeader sets a new header field by name and value.
// If another header exists with the same key, it will be overwritten.
func (r *Request) SetHeader(name, value string) *Request {
r.Use(headers.Set(name, value))
return r
}
// AddHeader adds a new header field by name and value
// without overwriting any existent header.
func (r *Request) AddHeader(name, value string) *Request {
r.Use(headers.Add(name, value))
return r
}
// SetHeaders adds new header fields based on the given map.
func (r *Request) SetHeaders(fields map[string]string) *Request {
r.Use(headers.SetMap(fields))
return r
}
// DelHeader deletes a header field by its name
func (r *Request) DelHeader(name string) *Request {
r.Use(headers.Del(name))
return r
}
// AddCookie sets a new cookie field bsaed on the given http.Cookie struct
// without overwriting any existent cookie.
func (r *Request) AddCookie(cookie *http.Cookie) *Request {
r.Use(cookies.Add(cookie))
return r
}
// AddCookies sets a new cookie field based on a list of http.Cookie
// without overwriting any existent cookie.
func (r *Request) AddCookies(data []*http.Cookie) *Request {
r.Use(cookies.AddMultiple(data))
return r
}
// CookieJar creates a cookie jar to store HTTP cookies when they are sent down.
func (r *Request) CookieJar() *Request {
r.Use(cookies.Jar())
return r
}
// Type defines the Content-Type header field based on the given type name alias or value.
// You can use the following content type aliases: json, xml, form, html, text and urlencoded.
func (r *Request) Type(name string) *Request {
r.Use(bodytype.Set(name))
return r
}
// Body defines the request body based on a io.Reader stream.
func (r *Request) Body(reader io.Reader) *Request {
r.Use(body.Reader(reader))
return r
}
// BodyString defines the request body based on the given string.
// If using this method, you should define the proper Content-Type header
// representing the real content MIME type.
func (r *Request) BodyString(data string) *Request {
r.Use(body.String(data))
return r
}
// JSON serializes and defines as request body based on the given input.
// The proper Content-Type header will be transparently added for you.
func (r *Request) JSON(data interface{}) *Request {
r.Use(body.JSON(data))
return r
}
// XML serializes and defines the request body based on the given input.
// The proper Content-Type header will be transparently added for you.
func (r *Request) XML(data interface{}) *Request {
r.Use(body.XML(data))
return r
}
// Form serializes and defines the request body as multipart/form-data
// based on the given form data.
func (r *Request) Form(data multipart.FormData) *Request {
r.Use(multipart.Data(data))
return r
}
// File serializes and defines the request body as multipart/form-data
// containing one file field.
func (r *Request) File(name string, reader io.Reader) *Request {
r.Use(multipart.File(name, reader))
return r
}
// Files serializes and defines the request body as multipart/form-data
// containing the given file fields.
func (r *Request) Files(files []multipart.FormFile) *Request {
r.Use(multipart.Files(files))
return r
}
// Send is an alias to Do(), which executes the current request
// and returns the response.
func (r *Request) Send() (*Response, error) {
return r.Do()
}
// Do performs the HTTP request and returns the HTTP response.
func (r *Request) Do() (*Response, error) {
if r.dispatched {
return nil, errors.New("gentleman: Request was already dispatched")
}
r.dispatched = true
ctx := NewDispatcher(r).Dispatch()
return buildResponse(ctx)
}
// Use uses a new plugin in the middleware stack.
func (r *Request) Use(p plugin.Plugin) *Request {
r.Middleware.Use(p)
return r
}
// UseRequest uses a request middleware handler.
func (r *Request) UseRequest(fn context.HandlerFunc) *Request {
r.Middleware.UseRequest(fn)
return r
}
// UseResponse uses a response middleware handler.
func (r *Request) UseResponse(fn context.HandlerFunc) *Request {
r.Middleware.UseResponse(fn)
return r
}
// UseError uses an error middleware handler.
func (r *Request) UseError(fn context.HandlerFunc) *Request {
r.Middleware.UseError(fn)
return r
}
// UseHandler uses an new middleware handler for the given phase.
func (r *Request) UseHandler(phase string, fn context.HandlerFunc) *Request {
r.Middleware.UseHandler(phase, fn)
return r
}
// Clone creates a new side-effects free Request based on the current one.
func (r *Request) Clone() *Request {
req := NewRequest()
req.Client = r.Client
req.Context = r.Context.Clone()
req.Middleware = r.Middleware.Clone()
return req
}
// NewDefaultTransport returns a new http.Transport with default values
// based on the given net.Dialer.
func NewDefaultTransport(dialer *net.Dialer) *http.Transport {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: dialer.Dial,
TLSHandshakeTimeout: TLSHandshakeTimeout,
}
return transport
}

@ -1,238 +0,0 @@
package gentleman
// Originally based on grequests: https://github.com/levigross/grequests
// Apache License Version 2.0
import (
"bytes"
"encoding/json"
"encoding/xml"
"io"
"io/ioutil"
"net/http"
"os"
"gopkg.in/h2non/gentleman.v2/context"
"gopkg.in/h2non/gentleman.v2/utils"
)
// Response provides a more convenient and higher level Response struct.
// Implements an io.ReadCloser interface.
type Response struct {
// Ok is a boolean flag that validates that the server returned a 2xx code.
Ok bool
// This is the Go error flag if something went wrong within the request, this flag will be set.
Error error
// Sugar to check if the response status code is a client error (4xx).
ClientError bool
// Sugar to check if the response status code is a server error (5xx).
ServerError bool
// StatusCode is the HTTP Status Code returned by the HTTP Response. Taken from resp.StatusCode.
StatusCode int
// Header stores the response headers as http.Header interface.
Header http.Header
// Cookies stores the parsed response cookies.
Cookies []*http.Cookie
// Expose the native Go http.Response object for convenience.
RawResponse *http.Response
// Expose the native Go http.Request object for convenience.
RawRequest *http.Request
// Expose original request Context for convenience.
Context *context.Context
// Internal buffer store
buffer *bytes.Buffer
}
func buildResponse(ctx *context.Context) (*Response, error) {
resp := ctx.Response
statusRange := int(resp.StatusCode / 100)
res := &Response{
// If your code is within the 2xx range the response is considered `Ok`
Ok: statusRange >= 2 && statusRange <= 3,
Error: ctx.Error,
ClientError: statusRange == 4,
ServerError: statusRange == 5,
Context: ctx,
RawResponse: resp,
RawRequest: ctx.Request,
StatusCode: resp.StatusCode,
Header: resp.Header,
Cookies: resp.Cookies(),
buffer: bytes.NewBuffer([]byte{}),
}
return res, res.Error
}
// Read is part of our ability to support io.ReadCloser
// if someone wants to make use of the raw body.
func (r *Response) Read(p []byte) (n int, err error) {
if r.Error != nil {
return -1, r.Error
}
return r.RawResponse.Body.Read(p)
}
// Close is part of our ability to support io.ReadCloser if
// someone wants to make use of the raw body.
func (r *Response) Close() error {
io.Copy(ioutil.Discard, r.RawResponse.Body)
if r.Error != nil {
return r.Error
}
return r.RawResponse.Body.Close()
}
// SaveToFile allows you to download the contents
// of the response to a file.
func (r *Response) SaveToFile(fileName string) error {
if r.Error != nil {
return r.Error
}
fd, err := os.Create(fileName)
if err != nil {
return err
}
defer r.Close() // This is a noop if we use the internal ByteBuffer
defer fd.Close()
_, err = io.Copy(fd, r.getInternalReader())
if err != nil && err != io.EOF {
return err
}
return nil
}
// JSON is a method that will populate a struct that is provided `userStruct`
// with the JSON returned within the response body.
func (r *Response) JSON(userStruct interface{}) error {
if r.Error != nil {
return r.Error
}
jsonDecoder := json.NewDecoder(r.getInternalReader())
defer r.Close()
err := jsonDecoder.Decode(&userStruct)
if err != nil && err != io.EOF {
return err
}
return nil
}
// XML is a method that will populate a struct that is provided
// `userStruct` with the XML returned within the response body.
func (r *Response) XML(userStruct interface{}, charsetReader utils.XMLCharDecoder) error {
if r.Error != nil {
return r.Error
}
xmlDecoder := xml.NewDecoder(r.getInternalReader())
if charsetReader != nil {
xmlDecoder.CharsetReader = charsetReader
}
defer r.Close()
if err := xmlDecoder.Decode(&userStruct); err != nil && err != io.EOF {
return err
}
return nil
}
// Bytes returns the response as a byte array.
func (r *Response) Bytes() []byte {
if r.Error != nil {
return nil
}
r.populateResponseByteBuffer()
// Are we still empty?
if r.buffer.Len() == 0 {
return nil
}
return r.buffer.Bytes()
}
// String returns the response as a string.
func (r *Response) String() string {
if r.Error != nil {
return ""
}
r.populateResponseByteBuffer()
return r.buffer.String()
}
// ClearInternalBuffer is a function that will clear the internal buffer that we
// use to hold the .String() and .Bytes() data.
// Once you have used these functions you may want to free up the memory.
func (r *Response) ClearInternalBuffer() {
if r.Error != nil {
return // This is a noop as we will be dereferencing a null pointer
}
r.buffer.Reset()
}
// createResponseBytesBuffer is a utility method that will populate
// the internal byte reader this is largely used for .String() and .Bytes()
func (r *Response) populateResponseByteBuffer() {
// Have I done this already?
if r.buffer.Len() != 0 {
return
}
defer r.Close()
// Is there any content?
if r.RawResponse.ContentLength == 0 {
return
}
// Did the server tell us how big the response is going to be?
if r.RawResponse.ContentLength > 0 {
r.buffer.Grow(int(r.RawResponse.ContentLength))
}
_, err := io.Copy(r.buffer, r)
if err != nil && err != io.EOF {
r.Error = err
r.RawResponse.Body.Close()
}
}
// getInternalReader because we implement io.ReadCloser and
// optionally hold a large buffer of the response (created by
// the user's request).
func (r *Response) getInternalReader() io.Reader {
if r.buffer.Len() != 0 {
return r.buffer
}
return r
}
// isChunkedResponse iterates over the response's transfer encodings
// and returns either true whether 'chunked' is found, or false, otherwise.
func isChunkedResponse(res *http.Response) bool {
for _, te := range res.TransferEncoding {
if te == "chunked" {
return true
}
}
return false
}

@ -1,24 +0,0 @@
The MIT License
Copyright (c) 2016-2017 Tomas Aparicio
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

@ -1,17 +0,0 @@
# gentleman/utils [![Build Status](https://travis-ci.org/h2non/gentleman.png)](https://travis-ci.org/h2non/gentleman) [![GoDoc](https://godoc.org/github.com/h2non/gentleman/utils?status.svg)](https://godoc.org/github.com/h2non/gentleman/utils) [![API](https://img.shields.io/badge/status-stable-green.svg?style=flat)](https://godoc.org/github.com/h2non/gentleman/utils) [![Go Report Card](https://goreportcard.com/badge/github.com/h2non/gentleman/utils)](https://goreportcard.com/report/github.com/h2non/gentleman/utils)
`utils` package provides a few HTTP specific utilities internally used by gentleman.
## Installation
```bash
go get -u gopkg.in/h2non/gentleman.v2/utils
```
## API
See [godoc](https://godoc.org/github.com/h2non/gentleman/utils) reference.
## License
MIT - Tomas Aparicio

@ -1,52 +0,0 @@
// Package utils provides a set of reusable HTTP client utilities used internally
// in gentleman for required functionality and testing.
package utils
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"strconv"
)
// XMLCharDecoder is a helper type that takes a stream of bytes (not encoded in
// UTF-8) and returns a reader that encodes the bytes into UTF-8. This is done
// because Go's XML library only supports XML encoded in UTF-8.
type XMLCharDecoder func(charset string, input io.Reader) (io.Reader, error)
// ReplyWithStatus helper to write the http.Response status code and text.
func ReplyWithStatus(res *http.Response, code int) {
res.StatusCode = code
res.Status = strconv.Itoa(code) + " " + http.StatusText(code)
}
// WriteBodyString writes a string based body in a given http.Response.
func WriteBodyString(res *http.Response, body string) {
res.Body = StringReader(body)
res.ContentLength = int64(len(body))
}
// StringReader creates an io.ReadCloser interface from a string.
func StringReader(body string) io.ReadCloser {
b := bytes.NewReader([]byte(body))
rc, ok := io.Reader(b).(io.ReadCloser)
if !ok && b != nil {
rc = ioutil.NopCloser(b)
}
return rc
}
type nopCloser struct {
io.Reader
}
func (nopCloser) Close() error { return nil }
// NopCloser returns a ReadCloser with a no-op Close
// method wrapping the provided Reader r.
func NopCloser() io.ReadCloser {
return nopCloser{bytes.NewBuffer([]byte{})}
}

@ -1,4 +0,0 @@
package gentleman
// Version defines the package semantic version
const Version = "2.0.5"

18
vendor/modules.txt vendored

@ -21,8 +21,6 @@ github.com/go-redis/redis/v8/internal/rand
github.com/go-redis/redis/v8/internal/util
# github.com/kr/pretty v0.2.1
## explicit; go 1.12
# github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32
## explicit
# github.com/nilorg/sdk v0.0.0-20210429091026-95b6cdc95c84
## explicit; go 1.12
github.com/nilorg/sdk/convert
@ -40,7 +38,6 @@ golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
# golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
## explicit; go 1.17
golang.org/x/net/publicsuffix
# golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
## explicit; go 1.17
golang.org/x/sys/cpu
@ -50,18 +47,3 @@ gopkg.in/alexcesaro/quotedprintable.v3
# gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
## explicit
gopkg.in/gomail.v2
# gopkg.in/h2non/gentleman.v2 v2.0.5
## explicit
gopkg.in/h2non/gentleman.v2
gopkg.in/h2non/gentleman.v2/context
gopkg.in/h2non/gentleman.v2/middleware
gopkg.in/h2non/gentleman.v2/mux
gopkg.in/h2non/gentleman.v2/plugin
gopkg.in/h2non/gentleman.v2/plugins/body
gopkg.in/h2non/gentleman.v2/plugins/bodytype
gopkg.in/h2non/gentleman.v2/plugins/cookies
gopkg.in/h2non/gentleman.v2/plugins/headers
gopkg.in/h2non/gentleman.v2/plugins/multipart
gopkg.in/h2non/gentleman.v2/plugins/query
gopkg.in/h2non/gentleman.v2/plugins/url
gopkg.in/h2non/gentleman.v2/utils

Loading…
Cancel
Save