Skip to content

Commit

Permalink
Add response wrapper
Browse files Browse the repository at this point in the history
While sending request is redious, so is processing response.
`gohttp` hopes to make the whole procedure as smooth as possible.
This commit is the first step, it helps to parse response body
while keeps everything the original `http.Response` provides.
  • Loading branch information
cizixs committed Nov 14, 2016
1 parent fd36dc9 commit 63b7a06
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 16 deletions.
72 changes: 56 additions & 16 deletions gohttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
Expand All @@ -24,12 +25,48 @@ type basicAuth struct {
password string
}

// fileForm wraps one file part of multiple files upload
type fileForm struct {
fieldName string
filename string
file *os.File
}

// GoResponse wraps the official `http.Response`, and provides more features.
// The main function is to parse resp body for users.
// In the future, it can gives more information, like request elapsed time,
// redirect history etc
type GoResponse struct {
*http.Response
}

// AsString returns the response data as string
func (resp *GoResponse) AsString() (string, error) {
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(data), nil
}

// AsBytes return the response body as byte slice
func (resp *GoResponse) AsBytes() ([]byte, error) {
return ioutil.ReadAll(resp.Body)
}

// AsJSON parses response body to a struct
// Usage:
// user := &User{}
// resp.AsJSON(user)
// fmt.Printf("%s\n", user.Name)
func (resp *GoResponse) AsJSON(v interface{}) error {
data, err := resp.AsBytes()
if err != nil {
return err
}
return json.Unmarshal(data, v)
}

// Client is the main struct that wraps net/http
type Client struct {
c *http.Client
Expand Down Expand Up @@ -187,48 +224,51 @@ func (c *Client) prepareRequest(method string) (*http.Request, error) {
// All other HTTP methods will call `Do` behind the scene, and
// it can be used directly to send the request
// Custom HTTP method can be sent with this method
func (c *Client) Do(method, url string) (*http.Response, error) {
func (c *Client) Do(method, url string) (*GoResponse, error) {
c.url = url
req, err := c.prepareRequest(method)
if err != nil {
return nil, err
}
return c.c.Do(req)
resp, err := c.c.Do(req)
return &GoResponse{resp}, err
}

// Get handles HTTP GET request, and return response to user
func (c *Client) Get(url string) (*http.Response, error) {
// Note that the response is not `http.Response`, but a thin wrapper which does
// exactly what it used to and a little more.
func (c *Client) Get(url string) (*GoResponse, error) {
return c.Do("GET", url)
}

// Post handles HTTP POST request
func (c *Client) Post(url string) (*http.Response, error) {
func (c *Client) Post(url string) (*GoResponse, error) {
return c.Do("POST", url)
}

// Head handles HTTP HEAD request
// HEAD request works the same way as GET, except the response body is empty.
func (c *Client) Head(url string) (*http.Response, error) {
func (c *Client) Head(url string) (*GoResponse, error) {
return c.Do("HEAD", url)
}

// Put handles HTTP PUT request
func (c *Client) Put(url string) (*http.Response, error) {
func (c *Client) Put(url string) (*GoResponse, error) {
return c.Do("PUT", url)
}

// Delete handles HTTP DELETE request
func (c *Client) Delete(url string) (*http.Response, error) {
func (c *Client) Delete(url string) (*GoResponse, error) {
return c.Do("DELETE", url)
}

// Patch handles HTTP PATCH request
func (c *Client) Patch(url string) (*http.Response, error) {
func (c *Client) Patch(url string) (*GoResponse, error) {
return c.Do("PATCH", url)
}

// Options handles HTTP OPTIONS request
func (c *Client) Options(url string) (*http.Response, error) {
func (c *Client) Options(url string) (*GoResponse, error) {
return c.Do("OPTIONS", url)
}

Expand Down Expand Up @@ -372,37 +412,37 @@ func (c *Client) File(f *os.File, fileName, fieldName string) *Client {
}

// Get provides a shortcut to send `GET` request
func Get(url string) (*http.Response, error) {
func Get(url string) (*GoResponse, error) {
return DefaultClient.Get(url)
}

// Head provides a shortcut to send `HEAD` request
func Head(url string) (*http.Response, error) {
func Head(url string) (*GoResponse, error) {
return DefaultClient.Head(url)
}

// Delete provides a shortcut to send `DELETE` request
// It is used to remove a resource from server
func Delete(url string) (*http.Response, error) {
func Delete(url string) (*GoResponse, error) {
return DefaultClient.Delete(url)
}

// Options provides a shortcut to send `OPTIONS` request
func Options(url string) (*http.Response, error) {
func Options(url string) (*GoResponse, error) {
return DefaultClient.Options(url)
}

// Post provides a shortcut to send `POST` request
func Post(url string, data io.Reader) (*http.Response, error) {
func Post(url string, data io.Reader) (*GoResponse, error) {
return DefaultClient.Body(data).Post(url)
}

// Put provides a shortcut to send `PUT` request
func Put(url string, data io.Reader) (*http.Response, error) {
func Put(url string, data io.Reader) (*GoResponse, error) {
return DefaultClient.Body(data).Put(url)
}

// Patch provides a shortcut to send `PATCH` request
func Patch(url string, data io.Reader) (*http.Response, error) {
func Patch(url string, data io.Reader) (*GoResponse, error) {
return DefaultClient.Body(data).Patch(url)
}
56 changes: 56 additions & 0 deletions gohttp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,62 @@ func TestGet(t *testing.T) {
assert.Equal(greeting, string(actualGreeting))
}

func TestResponseAsString(t *testing.T) {
assert := assert.New(t)

greeting := "hello, gohttp."
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, greeting)
}))
defer ts.Close()

resp, err := gohttp.Get(ts.URL)
assert.NoError(err, "A get request should cause no error.")
assert.Equal(http.StatusOK, resp.StatusCode)

actualGreeting, err := resp.AsString()
assert.NoError(err, "read the response body should not cause error.")
assert.Equal(greeting, actualGreeting)
}

func TestResponseAsBytes(t *testing.T) {
assert := assert.New(t)

greeting := "hello, gohttp."
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, greeting)
}))
defer ts.Close()

resp, err := gohttp.Get(ts.URL)
assert.NoError(err, "A get request should cause no error.")
assert.Equal(http.StatusOK, resp.StatusCode)

actualGreeting, err := resp.AsBytes()
assert.NoError(err, "read the response body should not cause error.")
assert.Equal([]byte(greeting), actualGreeting)
}

func TestResponseAsJSON(t *testing.T) {
assert := assert.New(t)

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.Copy(w, r.Body)
}))
defer ts.Close()

resp, err := gohttp.New().JSON(`{"Name":"gohttp"}`).Post(ts.URL)
assert.NoError(err, "A get request should cause no error.")
assert.Equal(http.StatusOK, resp.StatusCode)

repo := &struct {
Name string `json:"name,omitempty"`
}{}
err = resp.AsJSON(repo)
assert.NoError(err, "read the response body should not cause error.")
assert.Equal("gohttp", repo.Name)
}

func TestHead(t *testing.T) {
assert := assert.New(t)

Expand Down

0 comments on commit 63b7a06

Please sign in to comment.