parent
d1f38fbada
commit
b718272697
@ -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.
|
@ -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.
|
@ -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,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,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"
|
Loading…
Reference in new issue