etcdhttp: use Form values over query parameters

This commit is contained in:
Jonathan Boulle 2014-09-11 15:12:31 -07:00
parent 7c03704b19
commit 52ddd389ff
2 changed files with 217 additions and 76 deletions

View File

@ -254,6 +254,9 @@ func genId() int64 {
} }
} }
// parseRequest converts a received http.Request to a server Request,
// performing validation of supplied fields as appropriate.
// If any validation fails, an empty Request and non-nil error is returned.
func parseRequest(r *http.Request, id int64) (etcdserverpb.Request, error) { func parseRequest(r *http.Request, id int64) (etcdserverpb.Request, error) {
emptyReq := etcdserverpb.Request{} emptyReq := etcdserverpb.Request{}
@ -264,7 +267,6 @@ func parseRequest(r *http.Request, id int64) (etcdserverpb.Request, error) {
err.Error(), err.Error(),
) )
} }
q := r.URL.Query()
if !strings.HasPrefix(r.URL.Path, keysPrefix) { if !strings.HasPrefix(r.URL.Path, keysPrefix) {
return emptyReq, etcdErr.NewRequestError( return emptyReq, etcdErr.NewRequestError(
@ -275,61 +277,49 @@ func parseRequest(r *http.Request, id int64) (etcdserverpb.Request, error) {
p := r.URL.Path[len(keysPrefix):] p := r.URL.Path[len(keysPrefix):]
var pIdx, wIdx, ttl uint64 var pIdx, wIdx, ttl uint64
if pIdxS := q.Get("prevIndex"); pIdxS != "" { if pIdx, err = getUint64(r.Form, "prevIndex"); err != nil {
if pIdx, err = parseUint64(pIdxS); err != nil {
return emptyReq, etcdErr.NewRequestError( return emptyReq, etcdErr.NewRequestError(
etcdErr.EcodeIndexNaN, etcdErr.EcodeIndexNaN,
fmt.Sprintf("invalid value for prevIndex: %q", pIdxS), fmt.Sprintf("invalid value for prevIndex"),
) )
} }
} if wIdx, err = getUint64(r.Form, "waitIndex"); err != nil {
if wIdxS := q.Get("waitIndex"); wIdxS != "" {
if wIdx, err = parseUint64(wIdxS); err != nil {
return emptyReq, etcdErr.NewRequestError( return emptyReq, etcdErr.NewRequestError(
etcdErr.EcodeIndexNaN, etcdErr.EcodeIndexNaN,
fmt.Sprintf("invalid value for waitIndex: %q", wIdxS), fmt.Sprintf("invalid value for waitIndex"),
) )
} }
} if ttl, err = getUint64(r.Form, "ttl"); err != nil {
if ttlS := q.Get("ttl"); ttlS != "" {
if ttl, err = parseUint64(ttlS); err != nil {
return emptyReq, etcdErr.NewRequestError( return emptyReq, etcdErr.NewRequestError(
etcdErr.EcodeTTLNaN, etcdErr.EcodeTTLNaN,
fmt.Sprintf("invalid value for ttl: %q", ttlS), `invalid value for "ttl"`,
) )
} }
}
var rec, sort, wait bool var rec, sort, wait bool
if recS := q.Get("recursive"); recS != "" { if rec, err = getBool(r.Form, "recursive"); err != nil {
if rec, err = strconv.ParseBool(recS); err != nil {
return emptyReq, etcdErr.NewRequestError( return emptyReq, etcdErr.NewRequestError(
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
fmt.Sprintf("invalid value for recursive: %q", recS), `invalid value for "recursive"`,
) )
} }
} if sort, err = getBool(r.Form, "sorted"); err != nil {
if sortS := q.Get("sorted"); sortS != "" {
if sort, err = strconv.ParseBool(sortS); err != nil {
return emptyReq, etcdErr.NewRequestError( return emptyReq, etcdErr.NewRequestError(
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
fmt.Sprintf("invalid value for sorted: %q", sortS), `invalid value for "sorted"`,
) )
} }
} if wait, err = getBool(r.Form, "wait"); err != nil {
if waitS := q.Get("wait"); waitS != "" {
if wait, err = strconv.ParseBool(waitS); err != nil {
return emptyReq, etcdErr.NewRequestError( return emptyReq, etcdErr.NewRequestError(
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
fmt.Sprintf("invalid value for wait: %q", waitS), `invalid value for "wait"`,
) )
} }
}
// prevExists is nullable, so leave it null if not specified // prevExists is nullable, so leave it null if not specified
var pe *bool var pe *bool
if _, ok := q["prevExists"]; ok { if _, ok := r.Form["prevExists"]; ok {
bv, err := strconv.ParseBool(q.Get("prevExists")) bv, err := getBool(r.Form, "prevExists")
if err != nil { if err != nil {
return emptyReq, etcdErr.NewRequestError( return emptyReq, etcdErr.NewRequestError(
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
@ -344,7 +334,7 @@ func parseRequest(r *http.Request, id int64) (etcdserverpb.Request, error) {
Method: r.Method, Method: r.Method,
Path: p, Path: p,
Val: r.FormValue("value"), Val: r.FormValue("value"),
PrevValue: q.Get("prevValue"), PrevValue: r.FormValue("prevValue"),
PrevIndex: pIdx, PrevIndex: pIdx,
PrevExists: pe, PrevExists: pe,
Recursive: rec, Recursive: rec,
@ -367,8 +357,26 @@ func parseRequest(r *http.Request, id int64) (etcdserverpb.Request, error) {
return rr, nil return rr, nil
} }
func parseUint64(s string) (uint64, error) { // getUint64 extracts a uint64 by the given key from a Form. If the key does
return strconv.ParseUint(s, 10, 64) // not exist in the form, 0 is returned. If the key exists but the value is
// badly formed, an error is returned. If multiple values are present only the
// first is considered.
func getUint64(form url.Values, key string) (i uint64, err error) {
if vals, ok := form[key]; ok {
i, err = strconv.ParseUint(vals[0], 10, 64)
}
return
}
// getBool extracts a bool by the given key from a Form. If the key does not
// exist in the form, false is returned. If the key exists but the value is
// badly formed, an error is returned. If multiple values are present only the
// first is considered.
func getBool(form url.Values, key string) (b bool, err error) {
if vals, ok := form[key]; ok {
b, err = strconv.ParseBool(vals[0])
}
return
} }
// writeError logs and writes the given Error to the ResponseWriter // writeError logs and writes the given Error to the ResponseWriter

View File

@ -7,6 +7,7 @@ import (
"net/url" "net/url"
"path" "path"
"reflect" "reflect"
"strings"
"sync" "sync"
"testing" "testing"
@ -34,6 +35,18 @@ func mustNewRequest(t *testing.T, p string) *http.Request {
} }
} }
// mustNewForm takes a set of Values and constructs a PUT *http.Request,
// with a URL constructed from appending the given path to the standard keysPrefix
func mustNewForm(t *testing.T, p string, vals url.Values) *http.Request {
u := mustNewURL(t, path.Join(keysPrefix, p))
req, err := http.NewRequest("PUT", u.String(), strings.NewReader(vals.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if err != nil {
t.Fatalf("error creating new request: %v", err)
}
return req
}
func TestBadParseRequest(t *testing.T) { func TestBadParseRequest(t *testing.T) {
tests := []struct { tests := []struct {
in *http.Request in *http.Request
@ -56,56 +69,90 @@ func TestBadParseRequest(t *testing.T) {
}, },
// bad values for prevIndex, waitIndex, ttl // bad values for prevIndex, waitIndex, ttl
{ {
mustNewRequest(t, "?prevIndex=foo"), mustNewForm(t, "foo", url.Values{"prevIndex": []string{"garbage"}}),
etcdErr.EcodeIndexNaN, etcdErr.EcodeIndexNaN,
}, },
{ {
mustNewRequest(t, "?prevIndex=1.5"), mustNewForm(t, "foo", url.Values{"prevIndex": []string{"1.5"}}),
etcdErr.EcodeIndexNaN, etcdErr.EcodeIndexNaN,
}, },
{ {
mustNewRequest(t, "?prevIndex=-1"), mustNewForm(t, "foo", url.Values{"prevIndex": []string{"-1"}}),
etcdErr.EcodeIndexNaN, etcdErr.EcodeIndexNaN,
}, },
{ {
mustNewRequest(t, "?waitIndex=garbage"), mustNewForm(t, "foo", url.Values{"waitIndex": []string{"garbage"}}),
etcdErr.EcodeIndexNaN, etcdErr.EcodeIndexNaN,
}, },
{ {
mustNewRequest(t, "?waitIndex=??"), mustNewForm(t, "foo", url.Values{"waitIndex": []string{"??"}}),
etcdErr.EcodeIndexNaN, etcdErr.EcodeIndexNaN,
}, },
{ {
mustNewRequest(t, "?ttl=-1"), mustNewForm(t, "foo", url.Values{"ttl": []string{"-1"}}),
etcdErr.EcodeTTLNaN, etcdErr.EcodeTTLNaN,
}, },
// bad values for recursive, sorted, wait // bad values for recursive, sorted, wait, prevExists
{ {
mustNewRequest(t, "?recursive=hahaha"), mustNewForm(t, "foo", url.Values{"recursive": []string{"hahaha"}}),
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
}, },
{ {
mustNewRequest(t, "?recursive=1234"), mustNewForm(t, "foo", url.Values{"recursive": []string{"1234"}}),
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
}, },
{ {
mustNewRequest(t, "?recursive=?"), mustNewForm(t, "foo", url.Values{"recursive": []string{"?"}}),
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
}, },
{ {
mustNewRequest(t, "?sorted=hahaha"), mustNewForm(t, "foo", url.Values{"sorted": []string{"?"}}),
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
}, },
{ {
mustNewRequest(t, "?sorted=!!"), mustNewForm(t, "foo", url.Values{"sorted": []string{"x"}}),
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
}, },
{ {
mustNewRequest(t, "?wait=notreally"), mustNewForm(t, "foo", url.Values{"wait": []string{"?!"}}),
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
}, },
{ {
mustNewRequest(t, "?wait=what!"), mustNewForm(t, "foo", url.Values{"wait": []string{"yes"}}),
etcdErr.EcodeInvalidField,
},
{
mustNewForm(t, "foo", url.Values{"prevExists": []string{"yes"}}),
etcdErr.EcodeInvalidField,
},
{
mustNewForm(t, "foo", url.Values{"prevExists": []string{"#2"}}),
etcdErr.EcodeInvalidField,
},
// query values are considered
{
mustNewRequest(t, "foo?prevExists=wrong"),
etcdErr.EcodeInvalidField,
},
{
mustNewRequest(t, "foo?ttl=wrong"),
etcdErr.EcodeTTLNaN,
},
// but body takes precedence if both are specified
{
mustNewForm(
t,
"foo?ttl=12",
url.Values{"ttl": []string{"garbage"}},
),
etcdErr.EcodeTTLNaN,
},
{
mustNewForm(
t,
"foo?prevExists=false",
url.Values{"prevExists": []string{"yes"}},
),
etcdErr.EcodeInvalidField, etcdErr.EcodeInvalidField,
}, },
} }
@ -122,6 +169,7 @@ func TestBadParseRequest(t *testing.T) {
} }
if ee.ErrorCode != tt.wcode { if ee.ErrorCode != tt.wcode {
t.Errorf("#%d: code=%d, want %v", i, ee.ErrorCode, tt.wcode) t.Errorf("#%d: code=%d, want %v", i, ee.ErrorCode, tt.wcode)
t.Logf("cause: %#v", ee.Cause)
} }
if !reflect.DeepEqual(got, etcdserverpb.Request{}) { if !reflect.DeepEqual(got, etcdserverpb.Request{}) {
t.Errorf("#%d: unexpected non-empty Request: %#v", i, got) t.Errorf("#%d: unexpected non-empty Request: %#v", i, got)
@ -144,67 +192,152 @@ func TestGoodParseRequest(t *testing.T) {
}, },
{ {
// value specified // value specified
mustNewRequest(t, "foo?value=some_value"), mustNewForm(
t,
"foo",
url.Values{"value": []string{"some_value"}},
),
etcdserverpb.Request{ etcdserverpb.Request{
Id: 1234, Id: 1234,
Method: "PUT",
Val: "some_value", Val: "some_value",
Path: "/foo", Path: "/foo",
}, },
}, },
{ {
// prevIndex specified // prevIndex specified
mustNewRequest(t, "foo?prevIndex=98765"), mustNewForm(
t,
"foo",
url.Values{"prevIndex": []string{"98765"}},
),
etcdserverpb.Request{ etcdserverpb.Request{
Id: 1234, Id: 1234,
Method: "PUT",
PrevIndex: 98765, PrevIndex: 98765,
Path: "/foo", Path: "/foo",
}, },
}, },
{ {
// recursive specified // recursive specified
mustNewRequest(t, "foo?recursive=true"), mustNewForm(
t,
"foo",
url.Values{"recursive": []string{"true"}},
),
etcdserverpb.Request{ etcdserverpb.Request{
Id: 1234, Id: 1234,
Method: "PUT",
Recursive: true, Recursive: true,
Path: "/foo", Path: "/foo",
}, },
}, },
{ {
// sorted specified // sorted specified
mustNewRequest(t, "foo?sorted=true"), mustNewForm(
t,
"foo",
url.Values{"sorted": []string{"true"}},
),
etcdserverpb.Request{ etcdserverpb.Request{
Id: 1234, Id: 1234,
Method: "PUT",
Sorted: true, Sorted: true,
Path: "/foo", Path: "/foo",
}, },
}, },
{ {
// wait specified // wait specified
mustNewRequest(t, "foo?wait=true"), mustNewForm(
t,
"foo",
url.Values{"wait": []string{"true"}},
),
etcdserverpb.Request{ etcdserverpb.Request{
Id: 1234, Id: 1234,
Method: "PUT",
Wait: true, Wait: true,
Path: "/foo", Path: "/foo",
}, },
}, },
{ {
// prevExists should be non-null if specified // prevExists should be non-null if specified
mustNewRequest(t, "foo?prevExists=true"), mustNewForm(
t,
"foo",
url.Values{"prevExists": []string{"true"}},
),
etcdserverpb.Request{ etcdserverpb.Request{
Id: 1234, Id: 1234,
Method: "PUT",
PrevExists: boolp(true), PrevExists: boolp(true),
Path: "/foo", Path: "/foo",
}, },
}, },
{ {
// prevExists should be non-null if specified // prevExists should be non-null if specified
mustNewRequest(t, "foo?prevExists=false"), mustNewForm(
t,
"foo",
url.Values{"prevExists": []string{"false"}},
),
etcdserverpb.Request{ etcdserverpb.Request{
Id: 1234, Id: 1234,
Method: "PUT",
PrevExists: boolp(false), PrevExists: boolp(false),
Path: "/foo", Path: "/foo",
}, },
}, },
// mix various fields
{
mustNewForm(
t,
"foo",
url.Values{
"value": []string{"some value"},
"prevExists": []string{"true"},
"prevValue": []string{"previous value"},
},
),
etcdserverpb.Request{
Id: 1234,
Method: "PUT",
PrevExists: boolp(true),
PrevValue: "previous value",
Val: "some value",
Path: "/foo",
},
},
// query parameters should be used if given
{
mustNewForm(
t,
"foo?prevValue=woof",
url.Values{},
),
etcdserverpb.Request{
Id: 1234,
Method: "PUT",
PrevValue: "woof",
Path: "/foo",
},
},
// but form values should take precedence over query parameters
{
mustNewForm(
t,
"foo?prevValue=woof",
url.Values{
"prevValue": []string{"miaow"},
},
),
etcdserverpb.Request{
Id: 1234,
Method: "PUT",
PrevValue: "miaow",
Path: "/foo",
},
},
} }
for i, tt := range tests { for i, tt := range tests {
@ -213,7 +346,7 @@ func TestGoodParseRequest(t *testing.T) {
t.Errorf("#%d: err = %v, want %v", i, err, nil) t.Errorf("#%d: err = %v, want %v", i, err, nil)
} }
if !reflect.DeepEqual(got, tt.w) { if !reflect.DeepEqual(got, tt.w) {
t.Errorf("#%d: bad request: got %#v, want %#v", i, got, tt.w) t.Errorf("#%d: request=%#v, want %#v", i, got, tt.w)
} }
} }
} }