Skip to content

Commit 57e413e

Browse files
authored
Merge pull request #51 from deepsheth/master
Add POST functions
2 parents 374ccac + 09f2796 commit 57e413e

File tree

3 files changed

+217
-8
lines changed

3 files changed

+217
-8
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ Exported variables and functions implemented till now :
1111
```go
1212
var Headers map[string]string // Set headers as a map of key-value pairs, an alternative to calling Header() individually
1313
var Cookies map[string]string // Set cookies as a map of key-value pairs, an alternative to calling Cookie() individually
14-
func Get(string) (string,error){} // Takes the url as an argument, returns HTML string
15-
func GetWithClient(string, *http.Client){} // Takes the url and a custom HTTP client as arguments, returns HTML string
16-
func Header(string, string){} // Takes key,value pair to set as headers for the HTTP request made in Get()
17-
func Cookie(string, string){} // Takes key, value pair to set as cookies to be sent with the HTTP request in Get()
14+
func Get(string) (string,error) {} // Takes the url as an argument, returns HTML string
15+
func GetWithClient(string, *http.Client) {} // Takes the url and a custom HTTP client as arguments, returns HTML string
16+
func Post(string, string, interface{}) (string, error) {} // Takes the url, bodyType, and payload as an argument, returns HTML string
17+
func PostForm(string, url.Values) {} // Takes the url and body. bodyType is set to "application/x-www-form-urlencoded"
18+
func Header(string, string) {} // Takes key,value pair to set as headers for the HTTP request made in Get()
19+
func Cookie(string, string) {} // Takes key, value pair to set as cookies to be sent with the HTTP request in Get()
1820
func HTMLParse(string) Root {} // Takes the HTML string as an argument, returns a pointer to the DOM constructed
1921
func Find([]string) Root {} // Element tag,(attribute key-value pair) as argument, pointer to first occurence returned
2022
func FindAll([]string) []Root {} // Same as Find(), but pointers to all occurrences returned

soup.go

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ package soup
66

77
import (
88
"bytes"
9+
"encoding/json"
910
"fmt"
11+
"io"
1012
"io/ioutil"
1113
"net/http"
14+
"net/http/httputil"
15+
"net/url"
16+
netURL "net/url"
1217
"regexp"
1318
"strings"
1419

@@ -35,6 +40,10 @@ const (
3540
ErrCreatingGetRequest
3641
// ErrInGetRequest will be returned when there was an error during the get request
3742
ErrInGetRequest
43+
// ErrCreatingPostRequest will be returned when the post request couldn't be created
44+
ErrCreatingPostRequest
45+
// ErrMarshallingPostRequest will be returned when the body of a post request couldn't be serialized
46+
ErrMarshallingPostRequest
3847
// ErrReadingResponse will be returned if there was an error reading the response to our get request
3948
ErrReadingResponse
4049
)
@@ -99,10 +108,34 @@ func GetWithClient(url string, client *http.Client) (string, error) {
99108
req, err := http.NewRequest("GET", url, nil)
100109
if err != nil {
101110
if debug {
102-
panic("Couldn't perform GET request to " + url)
111+
panic("Couldn't create GET request to " + url)
103112
}
104113
return "", newError(ErrCreatingGetRequest, "error creating get request to "+url)
105114
}
115+
116+
setHeadersAndCookies(req)
117+
118+
// Perform request
119+
resp, err := client.Do(req)
120+
if err != nil {
121+
if debug {
122+
panic("Couldn't perform GET request to " + url)
123+
}
124+
return "", newError(ErrInGetRequest, "couldn't perform GET request to "+url)
125+
}
126+
defer resp.Body.Close()
127+
bytes, err := ioutil.ReadAll(resp.Body)
128+
if err != nil {
129+
if debug {
130+
panic("Unable to read the response body")
131+
}
132+
return "", newError(ErrReadingResponse, "unable to read the response body")
133+
}
134+
return string(bytes), nil
135+
}
136+
137+
// setHeadersAndCookies helps build a request
138+
func setHeadersAndCookies(req *http.Request) {
106139
// Set headers
107140
for hName, hValue := range Headers {
108141
req.Header.Set(hName, hValue)
@@ -114,13 +147,66 @@ func GetWithClient(url string, client *http.Client) (string, error) {
114147
Value: cValue,
115148
})
116149
}
150+
}
151+
152+
// getBodyReader serializes the body for a network request. See the test file for examples
153+
func getBodyReader(rawBody interface{}) (io.Reader, error) {
154+
var bodyReader io.Reader
155+
156+
if rawBody != nil {
157+
switch body := rawBody.(type) {
158+
case map[string]string:
159+
jsonBody, err := json.Marshal(body)
160+
if err != nil {
161+
if debug {
162+
panic("Unable to read the response body")
163+
}
164+
return nil, newError(ErrMarshallingPostRequest, "couldn't serialize map of strings to JSON.")
165+
}
166+
bodyReader = bytes.NewBuffer(jsonBody)
167+
case netURL.Values:
168+
bodyReader = strings.NewReader(body.Encode())
169+
case []byte: //expects JSON format
170+
bodyReader = bytes.NewBuffer(body)
171+
case string: //expects JSON format
172+
bodyReader = strings.NewReader(body)
173+
default:
174+
return nil, newError(ErrMarshallingPostRequest, fmt.Sprintf("Cannot handle body type %T", rawBody))
175+
}
176+
}
177+
178+
return bodyReader, nil
179+
}
180+
181+
// PostWithClient returns the HTML returned by the url using a provided HTTP client
182+
// The type of the body must conform to one of the types listed in func getBodyReader()
183+
func PostWithClient(url string, bodyType string, body interface{}, client *http.Client) (string, error) {
184+
bodyReader, err := getBodyReader(body)
185+
if err != nil {
186+
return "todo:", err
187+
}
188+
189+
req, err := http.NewRequest("POST", url, bodyReader)
190+
Header("Content-Type", bodyType)
191+
setHeadersAndCookies(req)
192+
193+
if debug {
194+
// Save a copy of this request for debugging.
195+
requestDump, err := httputil.DumpRequest(req, true)
196+
if err != nil {
197+
fmt.Println(err)
198+
}
199+
fmt.Println(string(requestDump))
200+
}
201+
117202
// Perform request
118203
resp, err := client.Do(req)
204+
119205
if err != nil {
120206
if debug {
121-
panic("Couldn't perform GET request to " + url)
207+
panic("Couldn't perform POST request to " + url)
122208
}
123-
return "", newError(ErrInGetRequest, "couldn't perform GET request to "+url)
209+
return "", newError(ErrCreatingPostRequest, "couldn't perform POST request to"+url)
124210
}
125211
defer resp.Body.Close()
126212
bytes, err := ioutil.ReadAll(resp.Body)
@@ -133,7 +219,17 @@ func GetWithClient(url string, client *http.Client) (string, error) {
133219
return string(bytes), nil
134220
}
135221

136-
// Get returns the HTML returned by the url in string using the default HTTP client
222+
// Post returns the HTML returned by the url as a string using the default HTTP client
223+
func Post(url string, bodyType string, body interface{}) (string, error) {
224+
return PostWithClient(url, bodyType, body, defaultClient)
225+
}
226+
227+
// PostForm is a convenience method for POST requests that
228+
func PostForm(url string, data url.Values) (string, error) {
229+
return PostWithClient(url, "application/x-www-form-urlencoded", data, defaultClient)
230+
}
231+
232+
// Get returns the HTML returned by the url as a string using the default HTTP client
137233
func Get(url string) (string, error) {
138234
return GetWithClient(url, defaultClient)
139235
}

soup_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package soup
22

33
import (
4+
"bytes"
5+
"io/ioutil"
6+
"net/http"
7+
"net/http/httptest"
8+
"net/url"
49
"strconv"
510
"strings"
611
"testing"
@@ -177,7 +182,113 @@ func TestFindReturnsInspectableError(t *testing.T) {
177182
assert.Equal(t, ErrElementNotFound, r.Error.(Error).Type)
178183
}
179184

185+
// Similar test: https://github.com/hashicorp/go-retryablehttp/blob/master/client_test.go#L616
186+
func TestClient_Post(t *testing.T) {
187+
// Mock server which always responds 200.
188+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
189+
if r.Method != "POST" {
190+
t.Fatalf("bad method: %s", r.Method)
191+
}
192+
if r.RequestURI != "/foo/bar" {
193+
t.Fatalf("bad uri: %s", r.RequestURI)
194+
}
195+
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
196+
t.Fatalf("bad content-type: %s", ct)
197+
}
198+
199+
// Check the payload
200+
body, err := ioutil.ReadAll(r.Body)
201+
if err != nil {
202+
t.Fatalf("err: %s", err)
203+
}
204+
expected := []byte(`{"hello":"world"}`)
205+
if !bytes.Equal(body, expected) {
206+
t.Fatalf("bad: %v", string(body))
207+
}
208+
209+
w.WriteHeader(200)
210+
}))
211+
defer ts.Close()
212+
213+
// Make the request with JSON payload
214+
_, err := Post(
215+
ts.URL+"/foo/bar", "application/json", `{"hello":"world"}`)
216+
if err != nil {
217+
t.Fatalf("err: %v", err)
218+
}
219+
220+
// Make the request with byte payload
221+
_, err = Post(
222+
ts.URL+"/foo/bar", "application/json", []byte(`{"hello":"world"}`))
223+
if err != nil {
224+
t.Fatalf("err: %v", err)
225+
}
226+
227+
// Make the request with string map payload
228+
_, err = Post(
229+
ts.URL+"/foo/bar",
230+
"application/json",
231+
map[string]string{
232+
"hello": "world",
233+
})
234+
if err != nil {
235+
t.Fatalf("err: %v", err)
236+
}
237+
}
238+
239+
// Similar test: https://github.com/hashicorp/go-retryablehttp/blob/add-circleci/client_test.go#L631
240+
func TestClient_PostForm(t *testing.T) {
241+
// Mock server which always responds 200.
242+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
243+
if r.Method != "POST" {
244+
t.Fatalf("bad method: %s", r.Method)
245+
}
246+
if r.RequestURI != "/foo/bar" {
247+
t.Fatalf("bad uri: %s", r.RequestURI)
248+
}
249+
if ct := r.Header.Get("Content-Type"); ct != "application/x-www-form-urlencoded" {
250+
t.Fatalf("bad content-type: %s", ct)
251+
}
252+
253+
// Check the payload
254+
body, err := ioutil.ReadAll(r.Body)
255+
if err != nil {
256+
t.Fatalf("err: %s", err)
257+
}
258+
expected := []byte(`hello=world`)
259+
if !bytes.Equal(body, expected) {
260+
t.Fatalf("bad: %v", string(body))
261+
}
262+
263+
w.WriteHeader(200)
264+
}))
265+
defer ts.Close()
266+
267+
// Create the form data.
268+
form1, err := url.ParseQuery("hello=world")
269+
if err != nil {
270+
t.Fatalf("err: %v", err)
271+
}
272+
273+
form2 := url.Values{
274+
"hello": []string{"world"},
275+
}
276+
277+
// Make the request.
278+
_, err = PostForm(ts.URL+"/foo/bar", form1)
279+
if err != nil {
280+
t.Fatalf("err: %v", err)
281+
}
282+
283+
// Make the request.
284+
_, err = PostForm(ts.URL+"/foo/bar", form2)
285+
if err != nil {
286+
t.Fatalf("err: %v", err)
287+
}
288+
}
289+
180290
func TestHTML(t *testing.T) {
181291
li := doc.Find("ul").Find("li")
182292
assert.Equal(t, "<li>To a <a href=\"hello.jsp\">JSP page</a> right?</li>", li.HTML())
293+
183294
}

0 commit comments

Comments
 (0)