mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(cli): add grafana version header to all request against grafana.net
This commit is contained in:
parent
3c966caa23
commit
3c92f78ee7
@ -8,6 +8,7 @@ import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||
)
|
||||
|
||||
@ -16,6 +17,8 @@ var version = "master"
|
||||
func main() {
|
||||
setupLogging()
|
||||
|
||||
services.Init(version)
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "Grafana cli"
|
||||
app.Usage = ""
|
||||
|
@ -1,35 +1,59 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/franela/goreq"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
||||
)
|
||||
|
||||
var IoHelper m.IoUtil = IoUtilImp{}
|
||||
var (
|
||||
IoHelper m.IoUtil = IoUtilImp{}
|
||||
HttpClient http.Client
|
||||
grafanaVersion string
|
||||
)
|
||||
|
||||
func Init(version string) {
|
||||
grafanaVersion = version
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
|
||||
}
|
||||
|
||||
HttpClient = http.Client{
|
||||
Timeout: time.Duration(10 * time.Second),
|
||||
Transport: tr,
|
||||
}
|
||||
}
|
||||
|
||||
func ListAllPlugins(repoUrl string) (m.PluginRepo, error) {
|
||||
fullUrl := repoUrl + "/repo"
|
||||
res, err := goreq.Request{Uri: fullUrl, MaxRedirects: 3}.Do()
|
||||
body, err := createRequest(repoUrl, "repo")
|
||||
|
||||
if err != nil {
|
||||
logger.Info("Failed to create request", "error", err)
|
||||
return m.PluginRepo{}, fmt.Errorf("Failed to create request. error: %v", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return m.PluginRepo{}, err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return m.PluginRepo{}, fmt.Errorf("Could not access %s statuscode %v", fullUrl, res.StatusCode)
|
||||
}
|
||||
|
||||
var resp m.PluginRepo
|
||||
err = res.Body.FromJsonTo(&resp)
|
||||
var data m.PluginRepo
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return m.PluginRepo{}, errors.New("Could not load plugin data")
|
||||
logger.Info("Failed to unmarshal graphite response error: %v", err)
|
||||
return m.PluginRepo{}, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func ReadPlugin(pluginDir, pluginName string) (m.InstalledPlugin, error) {
|
||||
@ -88,21 +112,48 @@ func RemoveInstalledPlugin(pluginPath, pluginName string) error {
|
||||
}
|
||||
|
||||
func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) {
|
||||
fullUrl := repoUrl + "/repo/" + pluginId
|
||||
body, err := createRequest(repoUrl, "repo", pluginId)
|
||||
|
||||
if err != nil {
|
||||
logger.Info("Failed to create request", "error", err)
|
||||
return m.Plugin{}, fmt.Errorf("Failed to create request. error: %v", err)
|
||||
}
|
||||
|
||||
res, err := goreq.Request{Uri: fullUrl, MaxRedirects: 3}.Do()
|
||||
if err != nil {
|
||||
return m.Plugin{}, err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return m.Plugin{}, fmt.Errorf("Could not access %s statuscode %v", fullUrl, res.StatusCode)
|
||||
}
|
||||
|
||||
var resp m.Plugin
|
||||
err = res.Body.FromJsonTo(&resp)
|
||||
var data m.Plugin
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return m.Plugin{}, errors.New("Could not load plugin data")
|
||||
logger.Info("Failed to unmarshal graphite response error: %v", err)
|
||||
return m.Plugin{}, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func createRequest(repoUrl string, subPaths ...string) ([]byte, error) {
|
||||
u, _ := url.Parse(repoUrl)
|
||||
for _, v := range subPaths {
|
||||
u.Path = path.Join(u.Path, v)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
|
||||
logger.Info("grafanaVersion ", grafanaVersion)
|
||||
|
||||
req.Header.Set("grafana-version", grafanaVersion)
|
||||
req.Header.Set("User-Agent", "grafana "+grafanaVersion)
|
||||
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
res, err := HttpClient.Do(req)
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
defer res.Body.Close()
|
||||
|
||||
return body, err
|
||||
}
|
||||
|
24
vendor/github.com/franela/goreq/.gitignore
generated
vendored
24
vendor/github.com/franela/goreq/.gitignore
generated
vendored
@ -1,24 +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
|
||||
|
||||
src
|
8
vendor/github.com/franela/goreq/.travis.yml
generated
vendored
8
vendor/github.com/franela/goreq/.travis.yml
generated
vendored
@ -1,8 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.5.3
|
||||
- tip
|
||||
notifications:
|
||||
email:
|
||||
- ionathan@gmail.com
|
||||
- marcosnils@gmail.com
|
20
vendor/github.com/franela/goreq/LICENSE
generated
vendored
20
vendor/github.com/franela/goreq/LICENSE
generated
vendored
@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Jonathan Leibiusky and Marcos Lilljedahl
|
||||
|
||||
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.
|
3
vendor/github.com/franela/goreq/Makefile
generated
vendored
3
vendor/github.com/franela/goreq/Makefile
generated
vendored
@ -1,3 +0,0 @@
|
||||
test:
|
||||
go get -v -d -t ./...
|
||||
go test -v
|
444
vendor/github.com/franela/goreq/README.md
generated
vendored
444
vendor/github.com/franela/goreq/README.md
generated
vendored
@ -1,444 +0,0 @@
|
||||
[](https://travis-ci.org/franela/goreq)
|
||||
[](https://godoc.org/github.com/franela/goreq)
|
||||
|
||||
GoReq
|
||||
=======
|
||||
|
||||
Simple and sane HTTP request library for Go language.
|
||||
|
||||
|
||||
|
||||
**Table of Contents**
|
||||
|
||||
- [Why GoReq?](#user-content-why-goreq)
|
||||
- [How do I install it?](#user-content-how-do-i-install-it)
|
||||
- [What can I do with it?](#user-content-what-can-i-do-with-it)
|
||||
- [Making requests with different methods](#user-content-making-requests-with-different-methods)
|
||||
- [GET](#user-content-get)
|
||||
- [Tags](#user-content-tags)
|
||||
- [POST](#user-content-post)
|
||||
- [Sending payloads in the Body](#user-content-sending-payloads-in-the-body)
|
||||
- [Specifiying request headers](#user-content-specifiying-request-headers)
|
||||
- [Sending Cookies](#cookie-support)
|
||||
- [Setting timeouts](#user-content-setting-timeouts)
|
||||
- [Using the Response and Error](#user-content-using-the-response-and-error)
|
||||
- [Receiving JSON](#user-content-receiving-json)
|
||||
- [Sending/Receiving Compressed Payloads](#user-content-sendingreceiving-compressed-payloads)
|
||||
- [Using gzip compression:](#user-content-using-gzip-compression)
|
||||
- [Using deflate compression:](#user-content-using-deflate-compression)
|
||||
- [Using compressed responses:](#user-content-using-compressed-responses)
|
||||
- [Proxy](#proxy)
|
||||
- [Debugging requests](#debug)
|
||||
- [Getting raw Request & Response](#getting-raw-request--response)
|
||||
- [TODO:](#user-content-todo)
|
||||
|
||||
|
||||
|
||||
Why GoReq?
|
||||
==========
|
||||
|
||||
Go has very nice native libraries that allows you to do lots of cool things. But sometimes those libraries are too low level, which means that to do a simple thing, like an HTTP Request, it takes some time. And if you want to do something as simple as adding a timeout to a request, you will end up writing several lines of code.
|
||||
|
||||
This is why we think GoReq is useful. Because you can do all your HTTP requests in a very simple and comprehensive way, while enabling you to do more advanced stuff by giving you access to the native API.
|
||||
|
||||
How do I install it?
|
||||
====================
|
||||
|
||||
```bash
|
||||
go get github.com/franela/goreq
|
||||
```
|
||||
|
||||
What can I do with it?
|
||||
======================
|
||||
|
||||
## Making requests with different methods
|
||||
|
||||
#### GET
|
||||
```go
|
||||
res, err := goreq.Request{ Uri: "http://www.google.com" }.Do()
|
||||
```
|
||||
|
||||
GoReq default method is GET.
|
||||
|
||||
You can also set value to GET method easily
|
||||
|
||||
```go
|
||||
type Item struct {
|
||||
Limit int
|
||||
Skip int
|
||||
Fields string
|
||||
}
|
||||
|
||||
item := Item {
|
||||
Limit: 3,
|
||||
Skip: 5,
|
||||
Fields: "Value",
|
||||
}
|
||||
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://localhost:3000/",
|
||||
QueryString: item,
|
||||
}.Do()
|
||||
```
|
||||
The sample above will send `http://localhost:3000/?limit=3&skip=5&fields=Value`
|
||||
|
||||
Alternatively the `url` tag can be used in struct fields to customize encoding properties
|
||||
|
||||
```go
|
||||
type Item struct {
|
||||
TheLimit int `url:"the_limit"`
|
||||
TheSkip string `url:"the_skip,omitempty"`
|
||||
TheFields string `url:"-"`
|
||||
}
|
||||
|
||||
item := Item {
|
||||
TheLimit: 3,
|
||||
TheSkip: "",
|
||||
TheFields: "Value",
|
||||
}
|
||||
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://localhost:3000/",
|
||||
QueryString: item,
|
||||
}.Do()
|
||||
```
|
||||
The sample above will send `http://localhost:3000/?the_limit=3`
|
||||
|
||||
|
||||
QueryString also support url.Values
|
||||
|
||||
```go
|
||||
item := url.Values{}
|
||||
item.Set("Limit", 3)
|
||||
item.Add("Field", "somefield")
|
||||
item.Add("Field", "someotherfield")
|
||||
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://localhost:3000/",
|
||||
QueryString: item,
|
||||
}.Do()
|
||||
```
|
||||
|
||||
The sample above will send `http://localhost:3000/?limit=3&field=somefield&field=someotherfield`
|
||||
|
||||
### Tags
|
||||
|
||||
Struct field `url` tag is mainly used as the request parameter name.
|
||||
Tags can be comma separated multiple values, 1st value is for naming and rest has special meanings.
|
||||
|
||||
- special tag for 1st value
|
||||
- `-`: value is ignored if set this
|
||||
|
||||
- special tag for rest 2nd value
|
||||
- `omitempty`: zero-value is ignored if set this
|
||||
- `squash`: the fields of embedded struct is used for parameter
|
||||
|
||||
#### Tag Examples
|
||||
|
||||
```go
|
||||
type Place struct {
|
||||
Country string `url:"country"`
|
||||
City string `url:"city"`
|
||||
ZipCode string `url:"zipcode,omitempty"`
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Place `url:",squash"`
|
||||
|
||||
FirstName string `url:"first_name"`
|
||||
LastName string `url:"last_name"`
|
||||
Age string `url:"age,omitempty"`
|
||||
Password string `url:"-"`
|
||||
}
|
||||
|
||||
johnbull := Person{
|
||||
Place: Place{ // squash the embedded struct value
|
||||
Country: "UK",
|
||||
City: "London",
|
||||
ZipCode: "SW1",
|
||||
},
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Age: "35",
|
||||
Password: "my-secret", // ignored for parameter
|
||||
}
|
||||
|
||||
goreq.Request{
|
||||
Uri: "http://localhost/",
|
||||
QueryString: johnbull,
|
||||
}.Do()
|
||||
// => `http://localhost/?first_name=John&last_name=Doe&age=35&country=UK&city=London&zip_code=SW1`
|
||||
|
||||
|
||||
// age and zipcode will be ignored because of `omitempty`
|
||||
// but firstname isn't.
|
||||
samurai := Person{
|
||||
Place: Place{ // squash the embedded struct value
|
||||
Country: "Japan",
|
||||
City: "Tokyo",
|
||||
},
|
||||
LastName: "Yagyu",
|
||||
}
|
||||
|
||||
goreq.Request{
|
||||
Uri: "http://localhost/",
|
||||
QueryString: samurai,
|
||||
}.Do()
|
||||
// => `http://localhost/?first_name=&last_name=yagyu&country=Japan&city=Tokyo`
|
||||
```
|
||||
|
||||
|
||||
#### POST
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{ Method: "POST", Uri: "http://www.google.com" }.Do()
|
||||
```
|
||||
|
||||
## Sending payloads in the Body
|
||||
|
||||
You can send ```string```, ```Reader``` or ```interface{}``` in the body. The first two will be sent as text. The last one will be marshalled to JSON, if possible.
|
||||
|
||||
```go
|
||||
type Item struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
item := Item{ Id: 1111, Name: "foobar" }
|
||||
|
||||
res, err := goreq.Request{
|
||||
Method: "POST",
|
||||
Uri: "http://www.google.com",
|
||||
Body: item,
|
||||
}.Do()
|
||||
```
|
||||
|
||||
## Specifiying request headers
|
||||
|
||||
We think that most of the times the request headers that you use are: ```Host```, ```Content-Type```, ```Accept``` and ```User-Agent```. This is why we decided to make it very easy to set these headers.
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://www.google.com",
|
||||
Host: "foobar.com",
|
||||
Accept: "application/json",
|
||||
ContentType: "application/json",
|
||||
UserAgent: "goreq",
|
||||
}.Do()
|
||||
```
|
||||
|
||||
But sometimes you need to set other headers. You can still do it.
|
||||
|
||||
```go
|
||||
req := goreq.Request{ Uri: "http://www.google.com" }
|
||||
|
||||
req.AddHeader("X-Custom", "somevalue")
|
||||
|
||||
req.Do()
|
||||
```
|
||||
|
||||
Alternatively you can use the `WithHeader` function to keep the syntax short
|
||||
|
||||
```go
|
||||
res, err = goreq.Request{ Uri: "http://www.google.com" }.WithHeader("X-Custom", "somevalue").Do()
|
||||
```
|
||||
|
||||
## Cookie support
|
||||
|
||||
Cookies can be either set at the request level by sending a [CookieJar](http://golang.org/pkg/net/http/cookiejar/) in the `CookieJar` request field
|
||||
or you can use goreq's one-liner WithCookie method as shown below
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://www.google.com",
|
||||
}.
|
||||
WithCookie(&http.Cookie{Name: "c1", Value: "v1"}).
|
||||
Do()
|
||||
```
|
||||
|
||||
## Setting timeouts
|
||||
|
||||
GoReq supports 2 kind of timeouts. A general connection timeout and a request specific one. By default the connection timeout is of 1 second. There is no default for request timeout, which means it will wait forever.
|
||||
|
||||
You can change the connection timeout doing:
|
||||
|
||||
```go
|
||||
goreq.SetConnectTimeout(100 * time.Millisecond)
|
||||
```
|
||||
|
||||
And specify the request timeout doing:
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Uri: "http://www.google.com",
|
||||
Timeout: 500 * time.Millisecond,
|
||||
}.Do()
|
||||
```
|
||||
|
||||
## Using the Response and Error
|
||||
|
||||
GoReq will always return 2 values: a ```Response``` and an ```Error```.
|
||||
If ```Error``` is not ```nil``` it means that an error happened while doing the request and you shouldn't use the ```Response``` in any way.
|
||||
You can check what happened by getting the error message:
|
||||
|
||||
```go
|
||||
fmt.Println(err.Error())
|
||||
```
|
||||
And to make it easy to know if it was a timeout error, you can ask the error or return it:
|
||||
|
||||
```go
|
||||
if serr, ok := err.(*goreq.Error); ok {
|
||||
if serr.Timeout() {
|
||||
...
|
||||
}
|
||||
}
|
||||
return err
|
||||
```
|
||||
|
||||
If you don't get an error, you can safely use the ```Response```.
|
||||
|
||||
```go
|
||||
res.Uri // return final URL location of the response (fulfilled after redirect was made)
|
||||
res.StatusCode // return the status code of the response
|
||||
res.Body // gives you access to the body
|
||||
res.Body.ToString() // will return the body as a string
|
||||
res.Header.Get("Content-Type") // gives you access to all the response headers
|
||||
```
|
||||
Remember that you should **always** close `res.Body` if it's not `nil`
|
||||
|
||||
## Receiving JSON
|
||||
|
||||
GoReq will help you to receive and unmarshal JSON.
|
||||
|
||||
```go
|
||||
type Item struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
var item Item
|
||||
|
||||
res.Body.FromJsonTo(&item)
|
||||
```
|
||||
|
||||
## Sending/Receiving Compressed Payloads
|
||||
GoReq supports gzip, deflate and zlib compression of requests' body and transparent decompression of responses provided they have a correct `Content-Encoding` header.
|
||||
|
||||
#####Using gzip compression:
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "POST",
|
||||
Uri: "http://www.google.com",
|
||||
Body: item,
|
||||
Compression: goreq.Gzip(),
|
||||
}.Do()
|
||||
```
|
||||
#####Using deflate/zlib compression:
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "POST",
|
||||
Uri: "http://www.google.com",
|
||||
Body: item,
|
||||
Compression: goreq.Deflate(),
|
||||
}.Do()
|
||||
```
|
||||
#####Using compressed responses:
|
||||
If servers replies a correct and matching `Content-Encoding` header (gzip requires `Content-Encoding: gzip` and deflate `Content-Encoding: deflate`) goreq transparently decompresses the response so the previous example should always work:
|
||||
```go
|
||||
type Item struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
res, err := goreq.Request{
|
||||
Method: "POST",
|
||||
Uri: "http://www.google.com",
|
||||
Body: item,
|
||||
Compression: goreq.Gzip(),
|
||||
}.Do()
|
||||
var item Item
|
||||
res.Body.FromJsonTo(&item)
|
||||
```
|
||||
If no `Content-Encoding` header is replied by the server GoReq will return the crude response.
|
||||
|
||||
## Proxy
|
||||
If you need to use a proxy for your requests GoReq supports the standard `http_proxy` env variable as well as manually setting the proxy for each request
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "GET",
|
||||
Proxy: "http://myproxy:myproxyport",
|
||||
Uri: "http://www.google.com",
|
||||
}.Do()
|
||||
```
|
||||
|
||||
### Proxy basic auth is also supported
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "GET",
|
||||
Proxy: "http://user:pass@myproxy:myproxyport",
|
||||
Uri: "http://www.google.com",
|
||||
}.Do()
|
||||
```
|
||||
|
||||
## Debug
|
||||
If you need to debug your http requests, it can print the http request detail.
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "GET",
|
||||
Uri: "http://www.google.com",
|
||||
Compression: goreq.Gzip(),
|
||||
ShowDebug: true,
|
||||
}.Do()
|
||||
fmt.Println(res, err)
|
||||
```
|
||||
|
||||
and it will print the log:
|
||||
```
|
||||
GET / HTTP/1.1
|
||||
Host: www.google.com
|
||||
Accept:
|
||||
Accept-Encoding: gzip
|
||||
Content-Encoding: gzip
|
||||
Content-Type:
|
||||
```
|
||||
|
||||
|
||||
### Getting raw Request & Response
|
||||
|
||||
To get the Request:
|
||||
|
||||
```go
|
||||
req := goreq.Request{
|
||||
Host: "foobar.com",
|
||||
}
|
||||
|
||||
//req.Request will return a new instance of an http.Request so you can safely use it for something else
|
||||
request, _ := req.NewRequest()
|
||||
|
||||
```
|
||||
|
||||
|
||||
To get the Response:
|
||||
|
||||
```go
|
||||
res, err := goreq.Request{
|
||||
Method: "GET",
|
||||
Uri: "http://www.google.com",
|
||||
Compression: goreq.Gzip(),
|
||||
ShowDebug: true,
|
||||
}.Do()
|
||||
|
||||
// res.Response will contain the original http.Response structure
|
||||
fmt.Println(res.Response, err)
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
TODO:
|
||||
-----
|
||||
|
||||
We do have a couple of [issues](https://github.com/franela/goreq/issues) pending we'll be addressing soon. But feel free to
|
||||
contribute and send us PRs (with tests please :smile:).
|
491
vendor/github.com/franela/goreq/goreq.go
generated
vendored
491
vendor/github.com/franela/goreq/goreq.go
generated
vendored
@ -1,491 +0,0 @@
|
||||
package goreq
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type itimeout interface {
|
||||
Timeout() bool
|
||||
}
|
||||
type Request struct {
|
||||
headers []headerTuple
|
||||
cookies []*http.Cookie
|
||||
Method string
|
||||
Uri string
|
||||
Body interface{}
|
||||
QueryString interface{}
|
||||
Timeout time.Duration
|
||||
ContentType string
|
||||
Accept string
|
||||
Host string
|
||||
UserAgent string
|
||||
Insecure bool
|
||||
MaxRedirects int
|
||||
RedirectHeaders bool
|
||||
Proxy string
|
||||
Compression *compression
|
||||
BasicAuthUsername string
|
||||
BasicAuthPassword string
|
||||
CookieJar http.CookieJar
|
||||
ShowDebug bool
|
||||
OnBeforeRequest func(goreq *Request, httpreq *http.Request)
|
||||
}
|
||||
|
||||
type compression struct {
|
||||
writer func(buffer io.Writer) (io.WriteCloser, error)
|
||||
reader func(buffer io.Reader) (io.ReadCloser, error)
|
||||
ContentEncoding string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
*http.Response
|
||||
Uri string
|
||||
Body *Body
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
func (r Response) CancelRequest() {
|
||||
cancelRequest(DefaultTransport, r.req)
|
||||
|
||||
}
|
||||
|
||||
func cancelRequest(transport interface{}, r *http.Request) {
|
||||
if tp, ok := transport.(transportRequestCanceler); ok {
|
||||
tp.CancelRequest(r)
|
||||
}
|
||||
}
|
||||
|
||||
type headerTuple struct {
|
||||
name string
|
||||
value string
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
reader io.ReadCloser
|
||||
compressedReader io.ReadCloser
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
timeout bool
|
||||
Err error
|
||||
}
|
||||
|
||||
type transportRequestCanceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
|
||||
func (e *Error) Timeout() bool {
|
||||
return e.timeout
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (b *Body) Read(p []byte) (int, error) {
|
||||
if b.compressedReader != nil {
|
||||
return b.compressedReader.Read(p)
|
||||
}
|
||||
return b.reader.Read(p)
|
||||
}
|
||||
|
||||
func (b *Body) Close() error {
|
||||
err := b.reader.Close()
|
||||
if b.compressedReader != nil {
|
||||
return b.compressedReader.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Body) FromJsonTo(o interface{}) error {
|
||||
return json.NewDecoder(b).Decode(o)
|
||||
}
|
||||
|
||||
func (b *Body) ToString() (string, error) {
|
||||
body, err := ioutil.ReadAll(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func Gzip() *compression {
|
||||
reader := func(buffer io.Reader) (io.ReadCloser, error) {
|
||||
return gzip.NewReader(buffer)
|
||||
}
|
||||
writer := func(buffer io.Writer) (io.WriteCloser, error) {
|
||||
return gzip.NewWriter(buffer), nil
|
||||
}
|
||||
return &compression{writer: writer, reader: reader, ContentEncoding: "gzip"}
|
||||
}
|
||||
|
||||
func Deflate() *compression {
|
||||
reader := func(buffer io.Reader) (io.ReadCloser, error) {
|
||||
return zlib.NewReader(buffer)
|
||||
}
|
||||
writer := func(buffer io.Writer) (io.WriteCloser, error) {
|
||||
return zlib.NewWriter(buffer), nil
|
||||
}
|
||||
return &compression{writer: writer, reader: reader, ContentEncoding: "deflate"}
|
||||
}
|
||||
|
||||
func Zlib() *compression {
|
||||
return Deflate()
|
||||
}
|
||||
|
||||
func paramParse(query interface{}) (string, error) {
|
||||
switch query.(type) {
|
||||
case url.Values:
|
||||
return query.(url.Values).Encode(), nil
|
||||
case *url.Values:
|
||||
return query.(*url.Values).Encode(), nil
|
||||
default:
|
||||
var v = &url.Values{}
|
||||
err := paramParseStruct(v, query)
|
||||
return v.Encode(), err
|
||||
}
|
||||
}
|
||||
|
||||
func paramParseStruct(v *url.Values, query interface{}) error {
|
||||
var (
|
||||
s = reflect.ValueOf(query)
|
||||
t = reflect.TypeOf(query)
|
||||
)
|
||||
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Interface {
|
||||
s = s.Elem()
|
||||
t = s.Type()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Struct {
|
||||
return errors.New("Can not parse QueryString.")
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
var name string
|
||||
|
||||
field := s.Field(i)
|
||||
typeField := t.Field(i)
|
||||
|
||||
if !field.CanInterface() {
|
||||
continue
|
||||
}
|
||||
|
||||
urlTag := typeField.Tag.Get("url")
|
||||
if urlTag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
name, opts := parseTag(urlTag)
|
||||
|
||||
var omitEmpty, squash bool
|
||||
omitEmpty = opts.Contains("omitempty")
|
||||
squash = opts.Contains("squash")
|
||||
|
||||
if squash {
|
||||
err := paramParseStruct(v, field.Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if urlTag == "" {
|
||||
name = strings.ToLower(typeField.Name)
|
||||
}
|
||||
|
||||
if val := fmt.Sprintf("%v", field.Interface()); !(omitEmpty && len(val) == 0) {
|
||||
v.Add(name, val)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareRequestBody(b interface{}) (io.Reader, error) {
|
||||
switch b.(type) {
|
||||
case string:
|
||||
// treat is as text
|
||||
return strings.NewReader(b.(string)), nil
|
||||
case io.Reader:
|
||||
// treat is as text
|
||||
return b.(io.Reader), nil
|
||||
case []byte:
|
||||
//treat as byte array
|
||||
return bytes.NewReader(b.([]byte)), nil
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
// try to jsonify it
|
||||
j, err := json.Marshal(b)
|
||||
if err == nil {
|
||||
return bytes.NewReader(j), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var DefaultDialer = &net.Dialer{Timeout: 1000 * time.Millisecond}
|
||||
var DefaultTransport http.RoundTripper = &http.Transport{Dial: DefaultDialer.Dial, Proxy: http.ProxyFromEnvironment}
|
||||
var DefaultClient = &http.Client{Transport: DefaultTransport}
|
||||
|
||||
var proxyTransport http.RoundTripper
|
||||
var proxyClient *http.Client
|
||||
|
||||
func SetConnectTimeout(duration time.Duration) {
|
||||
DefaultDialer.Timeout = duration
|
||||
}
|
||||
|
||||
func (r *Request) AddHeader(name string, value string) {
|
||||
if r.headers == nil {
|
||||
r.headers = []headerTuple{}
|
||||
}
|
||||
r.headers = append(r.headers, headerTuple{name: name, value: value})
|
||||
}
|
||||
|
||||
func (r Request) WithHeader(name string, value string) Request {
|
||||
r.AddHeader(name, value)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) AddCookie(c *http.Cookie) {
|
||||
r.cookies = append(r.cookies, c)
|
||||
}
|
||||
|
||||
func (r Request) WithCookie(c *http.Cookie) Request {
|
||||
r.AddCookie(c)
|
||||
return r
|
||||
|
||||
}
|
||||
|
||||
func (r Request) Do() (*Response, error) {
|
||||
var client = DefaultClient
|
||||
var transport = DefaultTransport
|
||||
var resUri string
|
||||
var redirectFailed bool
|
||||
|
||||
r.Method = valueOrDefault(r.Method, "GET")
|
||||
|
||||
// use a client with a cookie jar if necessary. We create a new client not
|
||||
// to modify the default one.
|
||||
if r.CookieJar != nil {
|
||||
client = &http.Client{
|
||||
Transport: transport,
|
||||
Jar: r.CookieJar,
|
||||
}
|
||||
}
|
||||
|
||||
if r.Proxy != "" {
|
||||
proxyUrl, err := url.Parse(r.Proxy)
|
||||
if err != nil {
|
||||
// proxy address is in a wrong format
|
||||
return nil, &Error{Err: err}
|
||||
}
|
||||
|
||||
//If jar is specified new client needs to be built
|
||||
if proxyTransport == nil || client.Jar != nil {
|
||||
proxyTransport = &http.Transport{Dial: DefaultDialer.Dial, Proxy: http.ProxyURL(proxyUrl)}
|
||||
proxyClient = &http.Client{Transport: proxyTransport, Jar: client.Jar}
|
||||
} else if proxyTransport, ok := proxyTransport.(*http.Transport); ok {
|
||||
proxyTransport.Proxy = http.ProxyURL(proxyUrl)
|
||||
}
|
||||
transport = proxyTransport
|
||||
client = proxyClient
|
||||
}
|
||||
|
||||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
|
||||
if len(via) > r.MaxRedirects {
|
||||
redirectFailed = true
|
||||
return errors.New("Error redirecting. MaxRedirects reached")
|
||||
}
|
||||
|
||||
resUri = req.URL.String()
|
||||
|
||||
//By default Golang will not redirect request headers
|
||||
// https://code.google.com/p/go/issues/detail?id=4800&q=request%20header
|
||||
if r.RedirectHeaders {
|
||||
for key, val := range via[0].Header {
|
||||
req.Header[key] = val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if transport, ok := transport.(*http.Transport); ok {
|
||||
if r.Insecure {
|
||||
if transport.TLSClientConfig != nil {
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
} else {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
} else if transport.TLSClientConfig != nil {
|
||||
// the default TLS client (when transport.TLSClientConfig==nil) is
|
||||
// already set to verify, so do nothing in that case
|
||||
transport.TLSClientConfig.InsecureSkipVerify = false
|
||||
}
|
||||
}
|
||||
|
||||
req, err := r.NewRequest()
|
||||
|
||||
if err != nil {
|
||||
// we couldn't parse the URL.
|
||||
return nil, &Error{Err: err}
|
||||
}
|
||||
|
||||
timeout := false
|
||||
if r.Timeout > 0 {
|
||||
client.Timeout = r.Timeout
|
||||
}
|
||||
|
||||
if r.ShowDebug {
|
||||
dump, err := httputil.DumpRequest(req, true)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
log.Println(string(dump))
|
||||
}
|
||||
|
||||
if r.OnBeforeRequest != nil {
|
||||
r.OnBeforeRequest(&r, req)
|
||||
}
|
||||
res, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
if !timeout {
|
||||
if t, ok := err.(itimeout); ok {
|
||||
timeout = t.Timeout()
|
||||
}
|
||||
if ue, ok := err.(*url.Error); ok {
|
||||
if t, ok := ue.Err.(itimeout); ok {
|
||||
timeout = t.Timeout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var response *Response
|
||||
//If redirect fails we still want to return response data
|
||||
if redirectFailed {
|
||||
if res != nil {
|
||||
response = &Response{res, resUri, &Body{reader: res.Body}, req}
|
||||
} else {
|
||||
response = &Response{res, resUri, nil, req}
|
||||
}
|
||||
}
|
||||
|
||||
//If redirect fails and we haven't set a redirect count we shouldn't return an error
|
||||
if redirectFailed && r.MaxRedirects == 0 {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
return response, &Error{timeout: timeout, Err: err}
|
||||
}
|
||||
|
||||
if r.Compression != nil && strings.Contains(res.Header.Get("Content-Encoding"), r.Compression.ContentEncoding) {
|
||||
compressedReader, err := r.Compression.reader(res.Body)
|
||||
if err != nil {
|
||||
return nil, &Error{Err: err}
|
||||
}
|
||||
return &Response{res, resUri, &Body{reader: res.Body, compressedReader: compressedReader}, req}, nil
|
||||
}
|
||||
|
||||
return &Response{res, resUri, &Body{reader: res.Body}, req}, nil
|
||||
}
|
||||
|
||||
func (r Request) addHeaders(headersMap http.Header) {
|
||||
if len(r.UserAgent) > 0 {
|
||||
headersMap.Add("User-Agent", r.UserAgent)
|
||||
}
|
||||
if r.Accept != "" {
|
||||
headersMap.Add("Accept", r.Accept)
|
||||
}
|
||||
if r.ContentType != "" {
|
||||
headersMap.Add("Content-Type", r.ContentType)
|
||||
}
|
||||
}
|
||||
|
||||
func (r Request) NewRequest() (*http.Request, error) {
|
||||
|
||||
b, e := prepareRequestBody(r.Body)
|
||||
if e != nil {
|
||||
// there was a problem marshaling the body
|
||||
return nil, &Error{Err: e}
|
||||
}
|
||||
|
||||
if r.QueryString != nil {
|
||||
param, e := paramParse(r.QueryString)
|
||||
if e != nil {
|
||||
return nil, &Error{Err: e}
|
||||
}
|
||||
r.Uri = r.Uri + "?" + param
|
||||
}
|
||||
|
||||
var bodyReader io.Reader
|
||||
if b != nil && r.Compression != nil {
|
||||
buffer := bytes.NewBuffer([]byte{})
|
||||
readBuffer := bufio.NewReader(b)
|
||||
writer, err := r.Compression.writer(buffer)
|
||||
if err != nil {
|
||||
return nil, &Error{Err: err}
|
||||
}
|
||||
_, e = readBuffer.WriteTo(writer)
|
||||
writer.Close()
|
||||
if e != nil {
|
||||
return nil, &Error{Err: e}
|
||||
}
|
||||
bodyReader = buffer
|
||||
} else {
|
||||
bodyReader = b
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.Method, r.Uri, bodyReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// add headers to the request
|
||||
req.Host = r.Host
|
||||
|
||||
r.addHeaders(req.Header)
|
||||
if r.Compression != nil {
|
||||
req.Header.Add("Content-Encoding", r.Compression.ContentEncoding)
|
||||
req.Header.Add("Accept-Encoding", r.Compression.ContentEncoding)
|
||||
}
|
||||
if r.headers != nil {
|
||||
for _, header := range r.headers {
|
||||
req.Header.Add(header.name, header.value)
|
||||
}
|
||||
}
|
||||
|
||||
//use basic auth if required
|
||||
if r.BasicAuthUsername != "" {
|
||||
req.SetBasicAuth(r.BasicAuthUsername, r.BasicAuthPassword)
|
||||
}
|
||||
|
||||
for _, c := range r.cookies {
|
||||
req.AddCookie(c)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// Return value if nonempty, def otherwise.
|
||||
func valueOrDefault(value, def string) string {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
return def
|
||||
}
|
64
vendor/github.com/franela/goreq/tags.go
generated
vendored
64
vendor/github.com/franela/goreq/tags.go
generated
vendored
@ -1,64 +0,0 @@
|
||||
// Copyright 2011 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.
|
||||
|
||||
package goreq
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// tagOptions is the string following a comma in a struct field's "json"
|
||||
// tag, or the empty string. It does not include the leading comma.
|
||||
type tagOptions string
|
||||
|
||||
// parseTag splits a struct field's json tag into its name and
|
||||
// comma-separated options.
|
||||
func parseTag(tag string) (string, tagOptions) {
|
||||
if idx := strings.Index(tag, ","); idx != -1 {
|
||||
return tag[:idx], tagOptions(tag[idx+1:])
|
||||
}
|
||||
return tag, tagOptions("")
|
||||
}
|
||||
|
||||
// Contains reports whether a comma-separated list of options
|
||||
// contains a particular substr flag. substr must be surrounded by a
|
||||
// string boundary or commas.
|
||||
func (o tagOptions) Contains(optionName string) bool {
|
||||
if len(o) == 0 {
|
||||
return false
|
||||
}
|
||||
s := string(o)
|
||||
for s != "" {
|
||||
var next string
|
||||
i := strings.Index(s, ",")
|
||||
if i >= 0 {
|
||||
s, next = s[:i], s[i+1:]
|
||||
}
|
||||
if s == optionName {
|
||||
return true
|
||||
}
|
||||
s = next
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isValidTag(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
switch {
|
||||
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
|
||||
// Backslash and quote chars are reserved, but
|
||||
// otherwise any punctuation chars are allowed
|
||||
// in a tag name.
|
||||
default:
|
||||
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
Loading…
Reference in New Issue
Block a user