Skip to content
Snippets Groups Projects
Commit 7eb054a5 authored by Will McCutchen's avatar Will McCutchen
Browse files

Add /post endpoint

parent d61e3d32
No related branches found
No related tags found
No related merge requests found
......@@ -2,8 +2,10 @@ package main
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
func getOrigin(r *http.Request) string {
......@@ -56,3 +58,44 @@ func writeJSON(w http.ResponseWriter, body []byte) {
w.Header().Set("Content-Type", "application/json; encoding=utf-8")
w.Write(body)
}
// parseBody handles parsing a request body into our standard API response,
// taking care to only consume the request body once based on the Content-Type
// of the request. The given Resp will be updated.
func parseBody(w http.ResponseWriter, r *http.Request, resp *Resp) error {
if r.Body == nil {
return nil
}
// Restrict size of request body
r.Body = http.MaxBytesReader(w, r.Body, maxMemory)
ct := r.Header.Get("Content-Type")
switch {
case ct == "application/x-www-form-urlencoded":
err := r.ParseForm()
if err != nil {
return err
}
resp.Form = r.PostForm
case ct == "multipart/form-data":
err := r.ParseMultipartForm(maxMemory)
if err != nil {
return err
}
resp.Form = r.PostForm
case strings.HasPrefix(ct, "application/json"):
dec := json.NewDecoder(r.Body)
err := dec.Decode(&resp.JSON)
if err != nil {
return err
}
default:
data, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
resp.Data = data
}
return nil
}
......@@ -16,10 +16,10 @@ type Resp struct {
Origin string `json:"origin"`
URL string `json:"url"`
Data string `json:"data,omitempty"`
Data []byte `json:"data,omitempty"`
Files map[string][]string `json:"files,omitempty"`
Form map[string][]string `json:"form,omitempty"`
JSON map[string][]string `json:"json,omitempty"`
JSON interface{} `json:"json,omitempty"`
}
// IPResp is the response for the /ip endpoint
......@@ -37,6 +37,9 @@ type UserAgentResp struct {
UserAgent string `json:"user-agent"`
}
// Max size of a request body we'll handle
const maxMemory = 1024*1024*5 + 1
// Index must be wrapped by the withTemplates middleware before it can be used
func index(w http.ResponseWriter, r *http.Request, t *template.Template) {
t = t.Lookup("index.html")
......@@ -70,6 +73,25 @@ func get(w http.ResponseWriter, r *http.Request) {
writeResponse(w, r, resp)
}
func post(w http.ResponseWriter, r *http.Request) {
args, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
http.Error(w, fmt.Sprintf("error parsing query params: %s", err), http.StatusBadRequest)
return
}
resp := &Resp{
Args: args,
Headers: r.Header,
}
err = parseBody(w, r, resp)
if err != nil {
http.Error(w, fmt.Sprintf("error parsing request body: %s", err), http.StatusBadRequest)
}
writeResponse(w, r, resp)
}
func ip(w http.ResponseWriter, r *http.Request) {
body, _ := json.Marshal(&IPResp{
Origin: getOrigin(r),
......@@ -97,6 +119,7 @@ func app() http.Handler {
h.HandleFunc("/", methods(templateWrapper(index), "GET"))
h.HandleFunc("/forms/post", methods(templateWrapper(formsPost), "GET"))
h.HandleFunc("/get", methods(get, "GET"))
h.HandleFunc("/post", methods(post, "POST"))
h.HandleFunc("/ip", ip)
h.HandleFunc("/user-agent", userAgent)
h.HandleFunc("/headers", headers)
......
package main
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
......@@ -225,7 +227,149 @@ func TestHeaders(t *testing.T) {
t.Fatalf("expected header %#v in response", k)
}
if !reflect.DeepEqual(expectedValues, values) {
t.Fatalf("%#v != %#v", values, expectedValues)
t.Fatalf("header value mismatch: %#v != %#v", values, expectedValues)
}
}
}
func TestPost__EmptyBody(t *testing.T) {
r, _ := http.NewRequest("POST", "/post", nil)
w := httptest.NewRecorder()
app().ServeHTTP(w, r)
var resp *Resp
err := json.Unmarshal(w.Body.Bytes(), &resp)
if err != nil {
t.Fatalf("failed to unmarshal body %s from JSON: %s", w.Body, err)
}
if len(resp.Args) > 0 {
t.Fatalf("expected no query params, got %#v", resp.Args)
}
if len(resp.Form) > 0 {
t.Fatalf("expected no form data, got %#v", resp.Form)
}
}
func TestPost__FormEncodedBody(t *testing.T) {
params := url.Values{}
params.Set("foo", "foo")
params.Add("bar", "bar1")
params.Add("bar", "bar2")
r, _ := http.NewRequest("POST", "/post", strings.NewReader(params.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
app().ServeHTTP(w, r)
var resp *Resp
err := json.Unmarshal(w.Body.Bytes(), &resp)
if err != nil {
t.Fatalf("failed to unmarshal body %#v from JSON: %s", w.Body.String(), err)
}
if len(resp.Args) > 0 {
t.Fatalf("expected no query params, got %#v", resp.Args)
}
if len(resp.Form) != len(params) {
t.Fatalf("expected %d form values, got %d", len(params), len(resp.Form))
}
for k, expectedValues := range params {
values, ok := resp.Form[k]
if !ok {
t.Fatalf("expected form field %#v in response", k)
}
if !reflect.DeepEqual(expectedValues, values) {
t.Fatalf("form value mismatch: %#v != %#v", values, expectedValues)
}
}
}
func TestPost__FormEncodedBodyNoContentType(t *testing.T) {
params := url.Values{}
params.Set("foo", "foo")
params.Add("bar", "bar1")
params.Add("bar", "bar2")
r, _ := http.NewRequest("POST", "/post", strings.NewReader(params.Encode()))
w := httptest.NewRecorder()
app().ServeHTTP(w, r)
var resp *Resp
err := json.Unmarshal(w.Body.Bytes(), &resp)
if err != nil {
t.Fatalf("failed to unmarshal body %s from JSON: %s", w.Body, err)
}
if len(resp.Args) > 0 {
t.Fatalf("expected no query params, got %#v", resp.Args)
}
if len(resp.Form) != 0 {
t.Fatalf("expected no form values, got %d", len(resp.Form))
}
if string(resp.Data) != params.Encode() {
t.Fatalf("response data mismatch, %#v != %#v", string(resp.Data), params.Encode())
}
}
func TestPost__JSON(t *testing.T) {
type testInput struct {
Foo string
Bar int
Baz []float64
Quux map[int]string
}
input := &testInput{
Foo: "foo",
Bar: 123,
Baz: []float64{1.0, 1.1, 1.2},
Quux: map[int]string{1: "one", 2: "two", 3: "three"},
}
inputBody, _ := json.Marshal(input)
r, _ := http.NewRequest("POST", "/post", bytes.NewReader(inputBody))
r.Header.Set("Content-Type", "application/json; charset=utf-8")
w := httptest.NewRecorder()
app().ServeHTTP(w, r)
var resp *Resp
err := json.Unmarshal(w.Body.Bytes(), &resp)
if err != nil {
t.Fatalf("failed to unmarshal body %s from JSON: %s", w.Body, err)
}
if len(resp.Args) > 0 {
t.Fatalf("expected no query params, got %#v", resp.Args)
}
if len(resp.Form) != 0 {
t.Fatalf("expected no form values, got %d", len(resp.Form))
}
if resp.Data != nil {
t.Fatalf("expected no data, got %#v", resp.Data)
}
// Need to re-marshall just the JSON field from the response in order to
// re-unmarshall it into our expected type
outputBodyBytes, _ := json.Marshal(resp.JSON)
output := &testInput{}
err = json.Unmarshal(outputBodyBytes, output)
if err != nil {
t.Fatalf("failed to round-trip JSON: coult not re-unmarshal JSON: %s", err)
}
if !reflect.DeepEqual(input, output) {
t.Fatalf("failed to round-trip JSON: %#v != %#v", output, input)
}
}
func TestPost__BodyTooBig(t *testing.T) {
body := make([]byte, maxMemory+1)
r, _ := http.NewRequest("POST", "/post", bytes.NewReader(body))
w := httptest.NewRecorder()
app().ServeHTTP(w, r)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected code %d, got %d", http.StatusBadRequest, w.Code)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment