• Stars
    star
    231
  • Rank 172,775 (Top 4 %)
  • Language
    Go
  • License
    Other
  • Created over 8 years ago
  • Updated about 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Functional HTTP Requests in Go

requests

GoDoc Build Status Coverage Status Donate

The simplest and functional HTTP requests in Go.

Introduction

Ever find yourself going back and forth between net/http and io docs and your code while making HTTP calls? Requests takes care of that by abstracting several types on both ends to just Request and Response--just the way it should be. Find out how or jump right in to examples.

Purpose

I need an HTTP request package that:

Usage

the following are the core differences from the standard net/http package.

Functional Options

requests employs functional options as optional parameters, this approach being idiomatic, clean, and makes a friendly, extensible API. This pattern is adopted after feedback from the Go community.

jsontype := func(r *requests.Request) {
        r.Header.Add("content-type", "application/json")
}
res, err := requests.Get("http://example.com", jsontype)

Embedded Standard Types

requests uses custom Request and Response types to embed standard http.Request, http.Response, and http.Client in order to insert helper methods, make configuring options atomic, and handle asynchronous errors.

The principle is, the caller should be able to set all the configurations on the "Request" instead of doing it on the client, transport, vice versa. For instance, Timeout can be set on the Request.

timeout := func(r *requests.Request) {

        // Set Timeout on *Request instead of *http.Client
        r.Timeout = time.Duration(5) * time.Second
}
res, err := requests.Get("http://example.com", timeout)
if err != nil {
        panic(err)
}

// Helper method
htmlStr := res.String()

Also, where http.Transport was normally needed to set control over proxies, TLS configuration, keep-alives, compression, and other settings, now everything is handled by Request.

tlsConfig := func(r *requests.Request) {
        r.TLSClientConfig = &tls.Config{RootCAs: x509.NewCertPool()}
        r.DisableCompression = true
}
res, _ := requests.Get("http://example.com", tlsConfig)

See Types and Methods for more information.

Asynchronous APIs

requests provides the following abstractions around sending HTTP requests in goroutines:

  • requests.GetAsync
  • requests.PostAsync
  • requests.Pool

All return a receive-only channel on which *requests.Response can be waited on.

rc, err := requests.GetAsync("http://httpbin.org/get")
if err != nil {
        panic(err)
}
res := <-rc

// Handle connection errors.
if res.Error != nil {
        panic(res.Error)
}

// Helper method
content := res.Bytes()

See Async and Handling Async Errors for more usage information.

Install

go get github.com/jochasinga/requests

Testing

requests uses Go standard testing package. Run this in the project's directory:

go test -v -cover

Examples

requests.Get

Sending a basic GET request is straightforward.

res, err := requests.Get("http://httpbin.org/get")
if err != nil {
        panic(err)
}

fmt.Println(res.StatusCode)  // 200

To send additional data, such as a query parameter, or set basic authorization header or content type, use functional options.

// Add a query parameter.
addFoo := func(r *requests.Request) {
        r.Params.Add("foo", "bar")
}

// Set basic username and password.
setAuth := func(r *requests.Request) {
        r.SetBasicAuth("user", "pass")
}

// Set the Content-Type.
setMime := func(r *requests.Request) {
        r.Header.Add("content-type", "application/json")
}

// Pass as parameters to the function.
res, err := requests.Get("http://httpbin.org/get", addFoo, setAuth, setMime)

Or configure everything in one function.

opts := func(r *requests.Request) {
        r.Params.Add("foo", "bar")
        r.SetBasicAuth("user", "pass")
        r.Header.Add("content-type", "application/json")
}
res, err := requests.Get("http://httpbin.org/get", opts)

requests.Post

Send POST requests with specified bodyType and body.

res, err := requests.Post("https://httpbin.org/post", "image/jpeg", &buf)

It also accepts variadic number of functional options:

notimeout := func(r *requests.Request) {
        r.Timeout = 0
}
res, err := requests.Post("https://httpbin.org/post", "application/json", &buf, notimeout)

requests.PostJSON

Encode your map or struct data as JSON and set bodyType to application/json implicitly.

first := map[string][]string{
        "foo": []string{"bar", "baz"},
}
second := struct {
        Foo []string `json:"foo"`
}{[]string{"bar", "baz"}}

payload := map[string][]interface{}{
        "twins": {first, second}
}

res, err := requests.PostJSON("https://httpbin.org/post", payload)

Other Verbs

HEAD, PUT, PATCH, DELETE, and OPTIONS are supported. See the doc for more info.

Async

requests.GetAsync

After parsing all the options, GetAsync spawns a goroutine to send a GET request and return <-chan *Response immediately on which *Response can be waited.

timeout := func(r *requests.Request) {
        r.Timeout = time.Duration(5) * time.Second
}

rc, err := requests.GetAsync("http://golang.org", timeout)
if err != nil {
        panic(err)
}

// Do other things...

// Block and wait
res := <-rc

// Handle a "reject" with Error field.
if res.Error != nil {
	panic(res.Error)
}

fmt.Println(res.StatusCode)  // 200

select can be used to poll many channels asynchronously like normal.

res1, _ := requests.GetAsync("http://google.com")
res2, _ := requests.GetAsync("http://facebook.com")
res3, _ := requests.GetAsync("http://docker.com")

for i := 0; i < 3; i++ {
        select {
    	case r1 := <-res1:
    		fmt.Println(r1.StatusCode)
    	case r2 := <-res2:
    		fmt.Println(r2.StatusCode)
    	case r3 := <-res3:
    		fmt.Println(r3.StatusCode)
    	}
}

requests.Pool is recommended for collecting concurrent responses from multiple requests.

requests.PostAsync

An asynchronous counterpart of requests.Post.

query := bytes.NewBufferString(`{
        "query" : {
            "term" : { "user" : "poco" }
        }
}`)

// Sending query to Elasticsearch server
rc, err := PostAsync("http://localhost:9200/users/_search", "application/json", query)
if err != nil {
        panic(err)
}
resp := <-rc
if resp.Error != nil {
        panic(resp.Error)
}
result := resp.JSON()

requests.Pool

Contains a Responses field of type chan *Response with variable-sized buffer specified in the constructor. Pool is used to collect in-bound responses sent from numbers of goroutines corresponding to the number of URLs provided in the slice.

urls := []string{
        "http://golang.org",
        "http://google.com",
        "http://docker.com",
        "http://medium.com",
        "http://example.com",
        "http://httpbin.org/get",
        "https://en.wikipedia.org",
}

// Create a pool with the maximum buffer size.
p := requests.NewPool(10)
opts := func(r *requests.Request) {
        r.Header.Set("user-agent", "GoBot(http://example.org/"))
        r.Timeout = time.Duration(10) * time.Second
}

results, err := p.Get(urls, opts)

// An error is returned when an attempt to construct a
// request fails, probably from a malformed URL.
if err != nil {
        panic(err)
}
for res := range results {
        if res.Error != nil {
                panic(res.Error)
        }
        fmt.Println(res.StatusCode)
}

You may want to ignore errors from malformed URLs instead of handling each of them, for instance, when crawling mass URLs.

To suppress the errors from being returned, either thrown away the error with _ or set the IgnoreBadURL field to true, which suppress all internal errors from crashing the pool:

results, err := p.Get(urls, func(r *requests.Request) {
        r.IgnoreBadURL = true
})

Pool.Responses channel is closed internally when all the responses are sent.

Types and Methods

requests.Request

It has embedded types *http.Request and *http.Client, making it an atomic type to pass into a functional option. It also contains field Params, which has the type url.Values. Use this field to add query parameters to your URL. Currently, parameters in Params will replace all the existing query string in the URL.

addParams := func(r *requests.Request) {
        r.Params = url.Values{
	        "name" : { "Ava", "Sanchez", "Poco" },
        }
}

// "q=cats" will be replaced by the new query string
res, err := requests.Get("https://httpbin.org/get?q=cats", addParams)

requests.Response

It has embedded type *http.Response and provides extra byte-like helper methods such as:

  • Len() int
  • String() string
  • Bytes() []byte
  • JSON() []byte

These methods will return an equivalent of nil for each return type if a certain condition isn't met. For instance:

res, _ := requests.Get("http://somecoolsite.io")
fmt.Println(res.JSON())

If the response from the server does not specify Content-Type as "application/json", res.JSON() will return an empty bytes slice. It does not panic if the content type is empty.

These methods close the response's body automatically.

Another helper method, ContentType, is used to get the media type in the response's header, and can be used with the helper methods to determine the type before reading the output.

mime, _, err := res.ContentType()
if err != nil {
        panic(err)
}

switch mime {
case "application/json":
        fmt.Println(res.JSON())
case "text/html", "text/plain":
        fmt.Println(res.String())
default:
        fmt.Println(res.Bytes())
}

Handling Async Errors

requests.Response also has an Error field which will contain any error caused in the goroutine within requests.GetAsync and carries it downstream for proper handling (Think reject in Promise but more straightforward in Go-style).

rc, err := requests.GetAsync("http://www.docker.io")

// This error is returned before the goroutine i.e. malformed URL.
if err != nil {
        panic(err)
}

res := <-rc

// This connection error is "attached" to the response.
if res.Error != nil {
	panic(res.Error)
}

fmt.Println(res.StatusCode)

Response.Error is default to nil when there is no error or when the response is being retrieved from a synchronous function.

HTTP Test Servers

Check out my other project relay, useful test proxies and round-robin switchers for end-to-end HTTP tests.

Contribution

Yes, please fork away.

Disclaimer

To support my ends in NYC and help me push commits, please consider Donate to fuel me with quality 🍡 or 🌟 this repo for spiritual octane.
Reach me at @jochasinga.

More Repositories

1

py-fbx

A simple-to-use wrapper for Autodesk Python FBX SDK.
Python
43
star
2

relay

Powered up Go HTTP server for comprehensive end-to-end HTTP tests.
Go
42
star
3

pluto

Arduino framework in Python for easy prototyping.
Python
38
star
4

flowwow

NFT petshop built on Flow blockchain and IPFS
TypeScript
27
star
5

subhuman

Chrome extension that exposes pixel trackers in your email and retaliate πŸ€“
JavaScript
22
star
6

golang-book

Chapter-by-chapter examples and problems from `An Introduction to Programming in Go` by Caleb Doxsey
Go
17
star
7

go-rest

Simple RESTful JSON API in Go
Go
16
star
8

firepot

Real-time RGB color adjuster with a pot on Arduino, Firebase and Angular
JavaScript
13
star
9

hog

Wrapper around Go `crypto` package for quick hashing of passwords with salts appended.
Go
11
star
10

tinyevm

A tiny stack machine to learn Ethereum bytecode.
Rust
8
star
11

rod

Experiment with porting GUN to Rust
Rust
7
star
12

systemd-parser

A minimal Systemd unit file parser
Rust
6
star
13

firma

Simple Merkle tree implementation based on the Bitcoin white paper.
OCaml
6
star
14

ml-brainfuck

Brainfuck interpreter written in Ocaml
OCaml
5
star
15

flow-react

Starting template to build a React NFT marketplace
JavaScript
4
star
16

xcurl

Curl-like tool with asynchronous calls, written in Go
Go
4
star
17

gtime

Deal with Go time in style.
Go
4
star
18

gevent-intro

Introductory guide to asynchronous programming in Gevent
Python
4
star
19

startups

Compiled note on starting a company from scratch.
4
star
20

gscrapy

A scrapy implementation in Go.
Go
4
star
21

alphaseek.io

Developer-friendly, customizable cloud referral engine
3
star
22

jiji

A nine-block quilt pattern generator
Racket
3
star
23

go-interviews

Prepare to cram for technical interviews in Go.
Go
3
star
24

miniblock

A light-weight blockchain engine
Python
3
star
25

referkit-node

TypeScript/JavaScript adapter for Referkit
TypeScript
3
star
26

ng-elemental

AngularJS pattern for unidirectional flow and predictable state
JavaScript
2
star
27

tooth

Experimental pub-sub style abstraction of channel operations in Go.
Go
2
star
28

logan

Save logcat to log files automatically
Nim
2
star
29

bytes-dao

An experimental DAO for data store
2
star
30

go-promise

Light-weight channel-compatible Promise
Go
2
star
31

ecc

Finite field and elliptic curve implementation
Rust
2
star
32

gibran

A minimal Go web framework that promises nothing but love and abstraction.
Go
2
star
33

hubot-clarifai

A hubot script that learns from images and videos
CoffeeScript
1
star
34

keymaker

BIP39 implementation of 128-bit Mnemonic seed generator.
Rust
1
star
35

faring

HTML
1
star
36

calc

a simple command line calculator
Yacc
1
star
37

nft-storage

Rust client for nft.storage service.
Rust
1
star
38

matlab-chops

Quick Matlab chops
1
star
39

super-fat

Code for "Super Fat" Toy Project
JavaScript
1
star
40

webhook-play

A repo to toy with github webhook
Python
1
star
41

web3-starter-kit

Minimal project to learn the SEEN stack
JavaScript
1
star
42

gulf

Gulf is a light-weight web and REST api framework written in Go
Go
1
star
43

no-docker

A simple 5-minute setup for HTTPS Python API server on EC2
Python
1
star
44

rxgo-lite

An alternative reactiveX implementation in Go
Go
1
star
45

ratchet

Pretty utilities for breezy Racket.
Racket
1
star
46

solana-testbed

Rust
1
star
47

lox

Interpreter for Lox Language written in Rust.
Rust
1
star
48

quicksock

A quick example Go package to quickly create an instance of TCP socket client and server.
Go
1
star
49

ox-interpreter

Just another toy lisp interpreter
Rust
1
star
50

awesome-rc

A list of awesome projects / tech for clueless Recursers
1
star
51

react-amplified

A react amplify tutorial
JavaScript
1
star
52

comet-guide

Study guide to the "Comet" book with code examples in Rust
Rust
1
star
53

rpg-x

Web-based RPG-style questionnaire game exercise
CSS
1
star
54

ff

Finite field library in Rust
Rust
1
star
55

spark-test

Test driving a motor remotely through Spark Cloud with Socket.io chat
JavaScript
1
star
56

go-weather

Simple Go client package for interfacing with Yahoo Weather API
Go
1
star
57

mini-hashmap

A small implementation of hashmap
Python
1
star
58

jochasinga

My public profile
1
star