Giter Site home page Giter Site logo

goreq's Introduction

Build Status GoDoc

GoReq

Simple and sane HTTP request library for Go language.

Table of Contents

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?

go get github.com/franela/goreq

What can I do with it?

Making requests with different methods

GET

res, err := goreq.Request{ Uri: "http://www.google.com" }.Do()

GoReq default method is GET.

You can also set value to GET method easily

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

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

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

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

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.

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.

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.

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

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 in the CookieJar request field or you can use goreq's one-liner WithCookie method as shown below

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:

goreq.SetConnectTimeout(100 * time.Millisecond)

And specify the request timeout doing:

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:

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:

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.

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.

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:
res, err := goreq.Request{
    Method: "POST",
    Uri: "http://www.google.com",
    Body: item,
    Compression: goreq.Gzip(),
}.Do()
Using deflate/zlib compression:
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:

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

res, err := goreq.Request{
    Method: "GET",
    Proxy: "http://myproxy:myproxyport",
    Uri: "http://www.google.com",
}.Do()

Proxy basic auth is also supported

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.

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:

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:

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 pending we'll be addressing soon. But feel free to contribute and send us PRs (with tests please 😄).

goreq's People

Contributors

bfontaine avatar bryant1410 avatar chilijung avatar defia avatar evalphobia avatar ezbercih avatar hernanm92 avatar j16sdiz avatar jefftexture avatar jimzhan avatar kilisima avatar laiwei avatar lvillani avatar marcosnils avatar mtsgrd avatar nemosupremo avatar oylenshpeegul avatar ryansb avatar sschepens avatar xetorthio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

goreq's Issues

error read on closed response body

When I try to do normal get request this error message appears:

http: read on closed response body 

my code is:

res, err := goreq.Request{Uri: "http://www.google.com"}.Do()
if err != nil {
    panic(err)
}
fmt.Println(res.Body.ToString())

i'm using go 1.51

Request Body Marshalling and Case Sensitivity

When marshalling structs to JSON for use in the request body case sensitivity can be an issue. Currently the field names are sent, in JSON using upper case:

{"Name":"Test","Key":"TST"}

The issue is that according to the JSON RPC specification parameter names are case sensitive. See http://jsonrpc.org/historical/json-rpc-1-1-alt.html#service-procedure-and-parameter-names for information

I believe in the majority of cases parameter names are lower case, so I think a conversion helper is required

Too many ReadAlls?

I had trouble getting FromJsonTo to work. Turns out it was because I had previously done a ToString. Since ToString had done an ioutil.ReadAll, the call to ioutil.ReadAll in FromJsonTo came up empty

unexpected end of JSON input

Are we really meant to choose between either ToString or FromJsonTo?

Nil pointer dereference with runtime.GOMAXPROCS more than 1

Hi Guys,

I set runtime.GOMAXPROCS(4) in a small program where I check dead links, and I got this when doing parallel requests using your library.
With GOMAXPROCS == 1 this problem does not occur.
(go version go1.4.2 linux/amd64)

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x0 pc=0x447496]

goroutine 130 [running]:
github.com/franela/goreq.Request.Do(0xc20801ec60, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc20802a5c0, 0x3d, ...)
/var/www/go/src/github.com/franela/goreq/goreq.go:368 +0xe26
main.processUrl(0xc20802a5c0, 0x3d, 0x83aab0)
/var/www/go/src/publicis/linkcheck/main.go:151 +0x33a
created by main.processLinksFile
/var/www/go/src/publicis/linkcheck/main.go:94 +0x616

The null pointer exception seems to derive from a nil res instance, after you perform
res, err := client.Do(req)

If I modify your part in goreq.go at line 367 or 368 (I think originally it was 367) and replace it with the condition below (to avoid a nil res) it will work, but I don't know enough specifics of your library at this point to comment further.

        if res != nil {
            response = &Response{res, resUri, &Body{reader: res.Body}, req}
        } else {
            response = &Response{res, resUri, nil, req}
        }

Regards
silviu

Error: cant assign address

Hi, I'm just start using goreq:

func GeoCoder(lat, lon float64) (string, error) {
    url := fmt.Sprintf("http://10.0.0.29/reverse?format=json&lat=%v&lon=%v&zoom=18", lat, lon)
    res, err := goreq.Request{
        Uri:     url,
        Timeout: 3 * time.Second,
    }.Do()
    if err != nil {
        return "", err
    }

    var data GeoResponse
    err = res.Body.FromJsonTo(&data)
    if err != nil {
        fmt.Println(err.Error())
        return "", err
    }
    return data.DisplayName, nil
}

Before using goreq I was using a raw http.Get and never got an error:

func GetAdress(lat, lon float64) (string, error) {
    url := fmt.Sprintf("http://10.0.0.29/reverse?format=json&lat=%v&lon=%v&zoom=18", lat, lon)
    res, err := http.Get(url)
    if err != nil {
        return "", err

    }
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println(err.Error())
        return "", err
    }
    var data GeoResponse
    err = json.Unmarshal(body, &data)
    if err != nil {
        fmt.Println(err.Error())
        return "", err
    }
    return data.DisplayName, nil
}

After the change I began having problems requesting an internal http service :
dial tcp 10.0.0.29:80: cannot assign requested address

The requests are running into goroutines 5-10 by sec using golang 1.4.1

Thanks in advance.

Goreq *should* send empty structfields in query string

21edc82 prevents sending empty structfields in query string, but an empty field and no field is not the same. I’m working on a project where we have to deal with an API for which we need a text field. It’s empty most of the time, so our API call look like:

GET /the/api?foo=2&bar=3&desc=

The desc param above is an empty string, but that’s not the same as the following query:

GET /the/api?foo=2&bar=3

The first one says “here’s my desc param and it’s empty” and the second is “I don’t send you my desc param”. In our case the remote server give us an error saying a parameter is missing.

Connection pool

Hello,

I have a request-intensive application who is facing an open file descriptor problem (see here for a related issue). Every few tries, my server starts telling that there's an error saying that no such host exists.

I know it should be the caller issue the one that should be limiting the amount of requests, but it could also be nice if goreq could implement a connection pool (pre-configurable) like other libraries uses, such as redis libs who are able to limit a maximum amount of connections to that database and reuse those who are left by the application.

Thoughts?

Is there any possibility for Request{} to be used as the key for map[interface{}]interface{}

I'm writing test code for my project using the qur/withmock tool, under which circumstance I need to use the goreq.Request{} data as the key for map[interface{}]interface{}. However, I got an error "panic: runtime error: hash of unhashable type goreq.Request" indicates that goreq.Request is unhashable. I have tried to add methods of the hash.Hash interface to the Request type, but still won't work. My code an test code is listed as following:
1.(github.com/franela/goreq/goreq_hash.go)
package goreq

const Size = 16
const BlockSize = 64

func (r *Request) Reset() {
r.MaxRedirects = 0
}

func (r *Request) Size() int { return Size }

func (r *Request) BlockSize() int { return BlockSize }

func (r *Request) Write(p []byte) (nn int, err error) {
nn = len(p)
return
}

func (r0 *Request) Sum(in []byte) []byte {
r := *r0
hash := r.checkSum()
return append(in, hash[:]...)
}

func (r *Request) checkSum() [Size]byte {
var init [4]uint32
init[0] = 0x67452301
init[1] = 0xEFCDAB89
init[2] = 0x98BADCFE
init[3] = 0x10325476

var digest [Size]byte
for i, s := range init {
    digest[i*4] = byte(s)
    digest[i*4+1] = byte(s >> 8)
    digest[i*4+2] = byte(s >> 16)
    digest[i*4+3] = byte(s >> 24)
}
return digest

}

type Request_Rec struct {
mock Request
}

func (m Request) EXPECT() *Request_Rec {
return &Request_Rec{m}
}

func (mr *Request_Rec) Do() {
cs := make(map[interface{}]string)
cs[mr.mock] = "Do"
}

2.(test.go)
package unittest

import (
"testing"
. "github.com/franela/goreq" //mock
)

func TestGet(t *testing.T) {
req := Request{Uri: "http://www.google.com"}
req.EXPECT().Do()
}

3.(error message)
--- FAIL: TestGet-4 (0.00 seconds)
exit status 2
FAIL github.com/controllers/test/ut 0.768s
panic: runtime error: hash of unhashable type goreq.Request [recovered]
panic: runtime error: hash of unhashable type goreq.Request

The testing work has been troubling me for several weeks, so I'm desperate to know if someone can help me with the problem. Endless gratitude!

Data race warning

I use goreq to send concurrent requests:

resp, err := goreq.Request{
    Uri: "https://en.wikipedia.org/w/api.php",
    QueryString: url.Values{
        "action": {"opensearch"},
        "search": {query},
    },
}.Do()

But when I checked the program for races, I got this:

WARNING: DATA RACE
Write by goroutine 14:
  github.com/franela/goreq.Request.Do()
      /home/ubuntu/.local/go/src/github.com/franela/goreq/goreq.go:314 +0x6ff

So race detector warns me that goroutines write unsafely into CheckRedirect of DefaultClient.

Issue with Body.FromJsonTo()

I can't figure out if this is an error, or if I'm doing it wrong.

When I run this code:

type FreckleEntries []struct {
    ID int `json:"id"`
    Date string `json:"date"`
    User struct {
        ID int `json:"id"`
        Email string `json:"email"`
        FirstName string `json:"first_name"`
        LastName string `json:"last_name"`
        ProfileImageURL string `json:"profile_image_url"`
        URL string `json:"url"`
    } `json:"user"`
    Billable bool `json:"billable"`
    Minutes int `json:"minutes"`
    Description string `json:"description"`
    Project struct {
        ID int `json:"id"`
        Name string `json:"name"`
        BillingIncrement int `json:"billing_increment"`
        Enabled bool `json:"enabled"`
        Billable bool `json:"billable"`
        Color string `json:"color"`
        URL string `json:"url"`
    } `json:"project"`
    Tags []struct {
        ID int `json:"id"`
        Name string `json:"name"`
        Billable bool `json:"billable"`
        FormattedName string `json:"formatted_name"`
        URL string `json:"url"`
    } `json:"tags"`
    SourceURL string `json:"source_url"`
    InvoicedAt time.Time `json:"invoiced_at"`
    Invoice struct {
        ID int `json:"id"`
        Reference string `json:"reference"`
        InvoiceDate string `json:"invoice_date"`
        State string `json:"state"`
        TotalAmount float64 `json:"total_amount"`
        URL string `json:"url"`
    } `json:"invoice"`
    Import struct {
        ID int `json:"id"`
        URL string `json:"url"`
    } `json:"import"`
    ApprovedAt time.Time `json:"approved_at"`
    ApprovedBy struct {
        ID int `json:"id"`
        Email string `json:"email"`
        FirstName string `json:"first_name"`
        LastName string `json:"last_name"`
        ProfileImageURL string `json:"profile_image_url"`
        URL string `json:"url"`
    } `json:"approved_by"`
    URL string `json:"url"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

var entries FreckleEntries
res.Body.FromJsonTo(&entries)

using this payload:

[
  {
    "id": 1,
    "date": "2012-01-09",
    "user": {
      "id": 5538,
      "email": "[email protected]",
      "first_name": "John",
      "last_name": "Test",
      "profile_image_url": "https://api.letsfreckle.com/images/avatars/0000/0001/avatar.jpg",
      "url": "https://api.letsfreckle.com/v2/users/5538"
    },
    "billable": true,
    "minutes": 60,
    "description": "freckle",
    "project": {
      "id": 37396,
      "name": "Gear GmbH",
      "billing_increment": 10,
      "enabled": true,
      "billable": true,
      "color": "#ff9898",
      "url": "https://api.letsfreckle.com/v2/projects/37396"
    },
    "tags": [
      {
        "id": 249397,
        "name": "freckle",
        "billable": true,
        "formatted_name": "#freckle",
        "url": "https://api.letsfreckle.com/v2/tags/249397"
      }
    ],
    "source_url": "http://someapp.com/special/url/",
    "invoiced_at": "2012-01-10T08:33:29Z",
    "invoice": {
      "id": 12345678,
      "reference": "AA001",
      "invoice_date": "2013-07-09",
      "state": "unpaid",
      "total_amount": 189.33,
      "url": "https://api.letsfreckle.com/v2/invoices/12345678"
    },
    "import": {
      "id": 8910,
      "url": "https://api.letsfreckle.com/v2/imports/8910"
    },
    "approved_at": "2012-01-10T08:33:29Z",
    "approved_by": {
      "id": 5538,
      "email": "[email protected]",
      "first_name": "John",
      "last_name": "Test",
      "profile_image_url": "https://api.letsfreckle.com/images/avatars/0000/0001/avatar.jpg",
      "url": "https://api.letsfreckle.com/v2/users/5538"
    },
    "url": "https://api.letsfreckle.com/v2/entries/1711626",
    "created_at": "2012-01-09T08:33:29Z",
    "updated_at": "2012-01-09T08:33:29Z"
  },
  {
    "id": 2,
    "date": "2012-01-09",
    "user": {
      "id": 5538,
      "email": "[email protected]",
      "first_name": "John",
      "last_name": "Test",
      "profile_image_url": "https://api.letsfreckle.com/images/avatars/0000/0001/avatar.jpg",
      "url": "https://api.letsfreckle.com/v2/users/5538"
    },
    "billable": true,
    "minutes": 60,
    "description": "freckle",
    "project": {
      "id": 37396,
      "name": "Gear GmbH",
      "billing_increment": 10,
      "enabled": true,
      "billable": true,
      "color": "#ff9898",
      "url": "https://api.letsfreckle.com/v2/projects/37396"
    },
    "tags": [
      {
        "id": 249397,
        "name": "freckle",
        "billable": true,
        "formatted_name": "#freckle",
        "url": "https://api.letsfreckle.com/v2/tags/249397"
      }
    ],
    "source_url": "http://someapp.com/special/url/",
    "invoiced_at": "2012-01-10T08:33:29Z",
    "invoice": {
      "id": 12345678,
      "reference": "AA001",
      "invoice_date": "2013-07-09",
      "state": "unpaid",
      "total_amount": 189.33,
      "url": "https://api.letsfreckle.com/v2/invoices/12345678"
    },
    "import": {
      "id": 8910,
      "url": "https://api.letsfreckle.com/v2/imports/8910"
    },
    "approved_at": "2012-01-10T08:33:29Z",
    "approved_by": {
      "id": 5538,
      "email": "[email protected]",
      "first_name": "John",
      "last_name": "Test",
      "profile_image_url": "https://api.letsfreckle.com/images/avatars/0000/0001/avatar.jpg",
      "url": "https://api.letsfreckle.com/v2/users/5538"
    },
    "url": "https://api.letsfreckle.com/v2/entries/1711626",
    "created_at": "2012-01-09T08:33:29Z",
    "updated_at": "2012-01-09T08:33:29Z"
  }
]

I only get the first record in the resulting entries var.

ReadMe documentation change suggestion

In order for the FromJsonTo method to work for me, I had to explicitly pass in pointer reference to struct, otherwise I receive "Unmarshall(non-pointer ...)" error.

I.E in your documenation, under the "Receiving JSON" section, I recommend changing from:
var item Item

res.Body.FromJsonTo(item)

to:
var item Item

res.Body.FromJsonTo(&item)

Does goreq supports JSON umarshalling from compressed HTTP body?

First I would like to thank you for a very nice library providing convenient level of abstraction. However I faced with a strange behavior when I tried to convert deflate compressed HTTP body into JSON object. I apologize for a verbose logs and I will very appreciate anyone who can explain how to do such things properly:

JSON input data

You can see that the body is encoded:

$ curl -v http://172.17.0.9:20000/io
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 286
< Content-Encoding: deflate
< Connection: close
< 
x�}Q�n� 
        |�ʿ�
* Closing connection 0
�B�^�фNh)T�Tꪽ�
              ��v��凹;��q�t'�МΠn�������f�C/GVel�@��N~V�0:���2�a����ٰ�L�Aq���P�)x�!j���Hm�9,v&ʅl{X��N%s<�1Z�
                                                                                                          �����AX����\���y��T![u4K��0��s���F6@;��-y-l)��y
                                                                                                                                                         ��L�.�so����̓c�+N�aMqIJ��$���r��
                                                                                                                                                                                       ���gc�qɻ�[����R��ې���X��n��iU7ҿ���҃�1�֣5�~��ו�[�5o��

But both browser and Python's requests are able to decode this HTTP by default:

In [1]: import requests

In [2]: url = "http://172.17.0.9:20000/commands"

In [3]: requests.get(url).text
Out[3]: u'{"timestamp":{"tv_sec":1428487862,"tv_usec":587520},"string_timestamp":"2015-04-08 10:11:02.587520","monitor_status":"enabled","commands":{"REVERSE_LOOKUP":{"cache":{"outside":{"successes":0,"failures":0,"size":0,"time":0},"internal":{"successes":0,"failures":0,"size":0,"time":0}},"disk":{"outside":{"successes":0,"failures":0,"size":0,"time":0},"internal":{"successes":11200,"failures":0,"size":0,"time":420484}},"total":{"storage":{"successes":0,"failures":0},"proxy":{"successes":11200,"failures":0}}},"clients":{}}}'

So actually we are dealing with this object:

{
    "timestamp": {
        "tv_sec": 1428488022, 
        "tv_usec": 425683
    }, 
    "commands": {
        "REVERSE_LOOKUP": {
            "disk": {
                "outside": {
                    "successes": 0, 
                    "failures": 0, 
                    "time": 0, 
                    "size": 0
                }, 
                "internal": {
                    "successes": 11616, 
                    "failures": 0, 
                    "time": 436248, 
                    "size": 0
                }
            }, 
            "total": {
                "storage": {
                    "successes": 0, 
                    "failures": 0
                }, 
                "proxy": {
                    "successes": 11616, 
                    "failures": 0
                }
            }, 
            "cache": {
                "outside": {
                    "successes": 0, 
                    "failures": 0, 
                    "time": 0, 
                    "size": 0
                }, 
                "internal": {
                    "successes": 0, 
                    "failures": 0, 
                    "time": 0, 
                    "size": 0
                }
            }
        }, 
        "clients": {}
    }, 
    "string_timestamp": "2015-04-08 10:13:42.425683", 
    "monitor_status": "enabled"
}

Source code

I wrote a simple program that interacts with a REST API on 172.17.0.9:20000 and prints the requested JSON. Unfortunately it doesn't work:

package main

import (
    "fmt"
    "github.com/franela/goreq"
)

type Timestamp struct {
    Tv_sec  int `json:"tv_sec"`
    Tv_usec int `json:"tv_usec"`
}

type StatsSmall struct {
    Failures  int `json:"failures"`
    Successes int `json:"successes"`
}

type StatsLarge struct {
    Failures  int `json:"failures"`
    Size      int `json:"size"`
    Successes int `json:"successes"`
    Time      int `json:"time"`
}

type DiskCache struct {
    Internal StatsLarge `json:"internal"`
    Outside  StatsLarge `json:"outside"`
}

type Total struct {
    Proxy   StatsSmall `json:"proxy"`
    Storage StatsSmall `json:"storage"`
}

type Empty struct {
}

type ReverseLookup struct {
    Cache DiskCache `json:"cache"`
    Disk  DiskCache `json:"disk"`
    Total Total     `json:"total"`
}

type Commands struct {
    ReverseLookup ReverseLookup `json:"REVERSE_LOOKUP"`
    Clients       Empty         `json:"clients"`
}

type Data struct {
    Commands         Commands  `json:"commands"`
    Timestamp        Timestamp `json:"timestamp"`
    String_timestamp string    `json:"string_timestamp"`
    Monitor_status   string    `json:"monitor_status"`
}

func get_content() {

    url := "http://172.17.0.9:20000/commands"

    res, err := goreq.Request{Uri: url}.Do()
    if err != nil {
        panic(err.Error())
    }
    fmt.Println("Response:\n", res)

    s, err := res.Body.ToString()
    if err != nil {
        panic(err.Error())
    }
    fmt.Println("Body:\n", s)
    fmt.Println("Body:\n", []byte(s))

    var jsondata Data
    res.Body.FromJsonTo(&jsondata)
    fmt.Println("Resulting JSON:\n", jsondata)
}

func main() {
    get_content()
}

Error

But all i got is just strange object filled with zeroes:

isaev@wks-isaev:~/side-projects/elliptics-manager$ go run main.go 
Response:
 &{ 200 235 0xc21004a6e0 map[Content-Type:[application/json] Content-Length:[235] Content-Encoding:[deflate]]}
Body:
 x��PAN�0��
�9E���I�9�TT���8K���ʻF@տc��8�q�6;;�3�##�I��돌�F��z�d��v�5U��B��ި�T1�h�~��d��z�Պ�W�������[�*悷���Ŕ0�������2�9�',���q����v{�pW���
                                                                                                                              ���������@�l��{�vN�<����f�����D���:�u�����B6���V��沔�@����z��E��bx��5�TN�ق���<~�x��>
Body:
 [120 156 181 80 65 78 195 48 16 252 10 242 57 69 182 235 164 73 238 57 129 84 84 4 215 200 56 75 177 136 237 202 187 70 64 213 191 99 135 30 56 21 113 224 54 59 59 179 51 218 35 35 235 0 73 187 3 235 143 140 222 70 4 195 122 161 100 171 218 118 221 53 85 225 210 66 170 166 222 168 238 84 49 164 104 253 126 252 225 100 146 139 122 197 213 138 183 87 130 247 162 235 235 230 250 91 207 42 230 130 183 20 226 152 197 148 48 171 193 235 167 25 166 188 50 193 57 237 39 44 225 187 225 113 216 221 15 227 237 118 123 243 112 87 24 163 205 11 20 16 18 161 157 22 136 201 24 64 132 108 225 21 123 214 118 78 241 60 160 253 132 5 148 102 25 228 170 214 19 68 175 231 191 58 179 117 178 248 250 31 217 66 54 178 185 236 86 27 181 230 178 148 160 64 231 3 249 129 122 127 185 69 214 31 98 120 255 248 53 240 84 78 155 217 130 167 242 249 60 126 1 120 178 167 62]
Resulting JSON:
 {{{{{0 0 0 0} {0 0 0 0}} {{0 0 0 0} {0 0 0 0}} {{0 0} {0 0}}} {}} {0 0}  }

Can anyone clarify this issue? Can such JSON object be unmarshalled with goreq?

leaking CLOSE_WAIT

I'm running into a weird problem where Goreq seems to leak CLOSE_WAIT sockets. If you do many of these inside a loop eventually you'll run into a OS error "too many open files". Any suggestions appreciated. Thanks.

Example -

package main

import (
        "github.com/franela/goreq"
        "time"
)

func main() {

        type Item struct {
                Id   int
                Name string
        }

        goreq.SetConnectTimeout(500 * time.Millisecond)

        item := Item{Id: 1111, Name: "foobar"}

        goreq.Request{
                Method:  "POST",
                Uri:     "http://xxx.xxx.xxx.xxx",
                Body:    item,
                Timeout: 500 * time.Millisecond,
        }.Do()

        goreq.Request{
                Method:  "POST",
               Uri:     "http://xxx.xxx.xxx.xxx",
                Body:    item,
                Timeout: 500 * time.Millisecond,
        }.Do()

        goreq.Request{
                Method:  "POST",
                Uri:     "http://xxx.xxx.xxx.xxx",
                Body:    item,
                Timeout: 500 * time.Millisecond,
        }.Do()

        goreq.Request{
                Method:  "POST",
                Uri:     "http://xxx.xxx.xxx.xxx",
                Body:    item,
                Timeout: 500 * time.Millisecond,
        }.Do()

        time.Sleep(1000000 * time.Millisecond)

}

As the process is running we check how many CLOSE_WAIT are opened. We see one for each request made. These will stick around as long as the process is running.

lsof -p 31922 | grep CLOSE_WAIT

test2 31922 scanner 3u IPv4 9394858 0t0 TCP localhost:40026->xxx.xxx.xxx.xxx:http (CLOSE_WAIT)
test2 31922 scanner 4u 0000 0,9 0 6269 anon_inode
test2 31922 scanner 5u IPv4 9397699 0t0 TCP localhost:40027->xxx.xxx.xxx.xxx:http (CLOSE_WAIT)
test2 31922 scanner 6u IPv4 9397700 0t0 TCP localhost:40028->xxx.xxx.xxx.xxx:http (CLOSE_WAIT)
test2 31922 scanner 7u IPv4 9397701 0t0 TCP localhost:40029->xxx.xxx.xxx.xxx:http (CLOSE_WAIT)

TravisCI Build Failing

The build for master is currently failing. I think this is because the "go get -t" idiom isn't available in go 1.1, which is what travis runs our tests in.

Support better response body

We should allow to return objects (unmarshalling from json), and a reader.
So my proposal is to leave Body as io.ReaderCloser and add to it a ToString() and FromJson(interface{})

So basically we can solve the following use cases:

Get text of the body

res, err := Request{ Method: "GET", Uri: "http://foobar.com" }.Do()
info := res.Body.ToString()
fmt.Println(info)

Unmarshal json in the body to my object

type User struct {
    Id int
    Name string
}

var user User
res, err := Request{ Method: "GET", Uri: "http://foobar.com" }.Do()
res.Body.FromJsonTo(&user)
fmt.Println(user.Name)

We should discuss this further.

How to get a standard http.Request form goreq.Request

First, thanks for creating the library.

Second, some of the libraries I'm using requires the standard http Request as a parameter. I like how easy it is to construct a JSON request with goreq. How would I go about creating a goreq.Request object then having that return a standard http Request? I would then passed the return object to the said library.

Unexpected "<nil>" at the end of JSON response

As titled, we found this when testing on our internal server while other libraries (like gorequest) works.

{"href":"https://api.example.com/v1/servers/mm8k7dAtvhC7nxAvIma238M1","name":"shadow","key":"shadow"} <nil>

Split Compressors into request / accept compressions.

Transparent compression on request and decompression on response should be two independent options.

Currently, the Accept-Encoding: gzip and Content-Encoding: gzip are both controlled by the Compression option.

A survey of desktop browsers found none of them are sending gzipped content in request, but all of them are accepting gzipped content in response. PHP, Apache and Nginx have build-in constructs to do gzip on response. Only Apache can do decompression on request.
To have maximum compatibility, two options should be controlled separately.

Export Error.Timeout for cleaner code

See: https://github.com/Stotz-Equipment/goreq/commit/94bbc8ac607112560b7e82b9dc1b0b09885d2bb7

I don't see the benefit of making timeout a private variable in the Error struct. We can simplify the code by exporting Timeout and removing the getter method Timeout()

I also modified the grouping of variables per what is being done in the golang project: https://github.com/golang/go/blob/master/src/bufio/bufio.go#L21-26 but I don't how strictly they adhere to that coding style.

Request Query Strings are normalized to small letters

Hello,

I am using your library to make some simple requests. I am building a requests query string like in the documentation, like this:

type RequestItem struct {
    SecretKey  string
    AccessKey  string
    CustomerID string
}

I then proceed to initialize a struct variable, and construct a request as per the following code:

res, err := goreq.Request{
    Uri: RancherUri,
    QueryString: queryItem,
    ShowDebug: true,
}.Do()

The string it produces however is something along these lines:

accesskey=<redacted>&customerid=a_random_string&secretkey=<redacted>

when I what I want instead is the following:

accessKey=<redacted>&customerId=a_random_string&secretKey=<redacted>

(with every second word capitalized, due to the design of the API I am programming against atm).

I found out that it's not possible to do that (at least, I poked inside goreq.go and couldn't get it to work, no matter how much I poked around parseParam and co), and I resolved to constructing the query string by hand, like the following code:

s := []string{"secretKey", "=", ZecretKey, "&", "accessKey", "=", ZAccessKey, "&", "customerId", "=", "a_random_client"}
queryString := strings.Join(s, "")
final := []string{RancherURI, "?", queryString}
finalUri := strings.Join(final, "")

and then proceed to create a request using only the URI:

res, err := goreq.Request{
    Uri: finalUri,
    ShowDebug: true,
}.Do()

But I find my last way of doing things non-optimal. Is there a better way of doing the same thing? Is the behavior I described a bug, or the lib's design? Is there some better way you could see to work around it?

Thank you for your time and the effort to build this otherwise magnificent software.

Support for url.Values in QueryString

The QueryString option doesn't accept variables of type url.Values, although it's Go's native realization of that functionality, and paramParse() function converts QS to url.Values before outputing the string. So I think it would be reasonable to add support for url.Values type as a QueryString option. It can be done very easy:

func paramParse(query interface{}) (string, error) {
    var (
        v = url.Values{}
        s = reflect.ValueOf(query)
        t = reflect.TypeOf(query)
    )

    if t == reflect.TypeOf(v) {
        return query.(url.Values).Encode(), nil
    }

    for i := 0; i < s.NumField(); i++ {
        v.Add(strings.ToLower(t.Field(i).Name), fmt.Sprintf("%v", s.Field(i).Interface()))
    }

    return v.Encode(), nil
}

Unable to complete oauth web workflow for GitHub in golang

I'm trying to implement oauth-workflow for GitHub in golang and using https://github.com/franela/goreq to perform http(s) requests.

There is a section in which GitHub returns a code and you have to make a POST request to https://github.com/login/oauth/access_token with code, client_id and client_secret.

package main

import "fmt"
import "github.com/franela/goreq"

type param struct {
  code string
  client_id string
  client_secret string
}

func main() {
  params := param {code: "XX", client_id:"XX", client_secret: "XX"}
  req := goreq.Request{
    Method : "POST",
    Uri : "https://github.com/login/oauth/access_token",
    Body : params,
  }
  req.AddHeader("Content-Type", "application/json")
  req.AddHeader("Accept", "application/json")
  res, _ := req.Do()
  fmt.Println(res.Body.ToString())
}

It is giving 404 with {"error":"Not Found"} message always.

Undefined net.Dialer

/usr/lib/go/src/pkg/github.com/franela/goreq/goreq.go:181: undefined: net.Dialer

Is this related to Go Version?

Case Sensitive Header

goreq use the request.Header.Add() function in underlying request. It normalize the case.

If I do ``req.AddHeader("X-APIKey", "abc" )`, It give

X-Apikey: abc

in header.

To do case sensitive header, I need to use this in http.Request:

    req.Header["X-APIKey"] = []string{ "abc" }

There seems not equivalence in goreq.

failed to handle 302 redirect

http.Get works without exception. but goreq always fail

 res, err := goreq.Request{Uri: "http://page.1688.com/shtml/static/431246723.html"}.Do()

log

 2015/01/09 14:19:15 Get http://127.0.0.1/: Error redirecting. MaxRedirects reached

then

res, err := goreq.Request{
    Method:       "GET",
    Uri:          "http://detail.1688.com/offer/431246723.html",
    MaxRedirects: 2,
}.Do()

log:

2015/01/09 14:25:29 Get http://127.0.0.1/: dial tcp 127.0.0.1:80: connection refused

Return better errors

Beside ConnectionTimeout and RequestTimeout, there are other types of errors that can be returned to make the user experience much much better.

So far I've identified:

  • net.AddrError
  • net.DNSConfigError
  • net.DNSError
  • net.InvalidAddrError
  • net.OpError
  • net.UnknownNetworkError

My proposal is to expose the following in Error:

Timeout() (maybe it is not necessary to distinguish between Request and Connection.
Connection() Any other connection error (dns, unknown network error, etc.).
Url() Url is wrong or something similar. This usually means that there was no network activity.

Would that be enough?

Access response cookies

Hello again. Currently I'm attempting to dive deeper in golang network programming and I would like to continue the use of your library. Now I'm stuck on getting the Cookies from the Response struct (I believe that standard http.Response lies somewhere beneath it).

    res, err := goreq.Request{
        Method: "POST",
        Uri:    *restAPI,
        Body:   providersRequest,
    }.Do()

       // This will give me a string, but I need http.Cookie
       cookieString := res.Header.Get("set-cookie")

       // This wil not compile
       cookieStruct := res.Cookies()

Thank you in advance!

How to set TLSClientConfig

use net/http package

I can use

tr := &http.Transport{
    TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}

client := &http.Client{Transport: tr}

to skip verify

so how to do use goreq.

thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.