Add /getBlob/ endpoint (#3082)

Add /getBlob/ endpoint
This commit is contained in:
Rafael Weinstein
2017-01-17 18:25:33 -08:00
committed by GitHub
parent f6a6283ee5
commit 7397fbbb1a
4 changed files with 102 additions and 7 deletions

View File

@@ -7,6 +7,7 @@ package constants
const (
RootPath = "/root/"
GetRefsPath = "/getRefs/"
GetBlobPath = "/getBlob/"
HasRefsPath = "/hasRefs/"
WriteValuePath = "/writeValue/"
BasePath = "/"

View File

@@ -61,6 +61,7 @@ func (s *RemoteDatabaseServer) Run() {
router := httprouter.New()
router.POST(constants.GetRefsPath, s.corsHandle(s.makeHandle(HandleGetRefs)))
router.GET(constants.GetBlobPath, s.corsHandle(s.makeHandle(HandleGetBlob)))
router.OPTIONS(constants.GetRefsPath, s.corsHandle(noopHandle))
router.POST(constants.HasRefsPath, s.corsHandle(s.makeHandle(HandleHasRefs)))
router.OPTIONS(constants.HasRefsPath, s.corsHandle(noopHandle))

View File

@@ -44,34 +44,40 @@ var (
// ordered sequence of Chunks to be validated and stored on the server.
// TODO: Nice comment about what headers it expects/honors, payload
// format, and error responses.
HandleWriteValue = versionCheck(handleWriteValue)
HandleWriteValue = createHandler(handleWriteValue, true)
// HandleGetRefs is meant to handle HTTP POST requests to the getRefs/
// server endpoint. Given a sequence of Chunk hashes, the server will
// fetch and return them.
// TODO: Nice comment about what headers it
// expects/honors, payload format, and responses.
HandleGetRefs = versionCheck(handleGetRefs)
HandleGetRefs = createHandler(handleGetRefs, true)
// HandleGetBlob is a custom endpoint whose sole purpose is to directly
// fetch the *bytes* contained in a Blob value. It expects a single query
// param of `h` to be the ref of the Blob.
// TODO: Support retrieving blob contents via a path.
HandleGetBlob = createHandler(handleGetBlob, false)
// HandleWriteValue is meant to handle HTTP POST requests to the hasRefs/
// server endpoint. Given a sequence of Chunk hashes, the server check for
// their presence and return a list of true/false responses.
// TODO: Nice comment about what headers it expects/honors, payload
// format, and responses.
HandleHasRefs = versionCheck(handleHasRefs)
HandleHasRefs = createHandler(handleHasRefs, true)
// HandleRootGet is meant to handle HTTP GET requests to the root/ server
// endpoint. The server returns the hash of the Root as a string.
// TODO: Nice comment about what headers it expects/honors, payload
// format, and responses.
HandleRootGet = versionCheck(handleRootGet)
HandleRootGet = createHandler(handleRootGet, true)
// HandleWriteValue is meant to handle HTTP POST requests to the root/
// server endpoint. This is used to update the Root to point to a new
// Chunk.
// TODO: Nice comment about what headers it expects/honors, payload
// format, and error responses.
HandleRootPost = versionCheck(handleRootPost)
HandleRootPost = createHandler(handleRootPost, true)
// HandleBaseGet is meant to handle HTTP GET requests to the / server
// endpoint. This is used to give a friendly message to users.
@@ -82,10 +88,11 @@ var (
writeValueConcurrency = runtime.NumCPU()
)
func versionCheck(hndlr Handler) Handler {
func createHandler(hndlr Handler, versionCheck bool) Handler {
return func(w http.ResponseWriter, req *http.Request, ps URLParams, cs chunks.ChunkStore) {
w.Header().Set(NomsVersionHeader, constants.NomsVersion)
if req.Header.Get(NomsVersionHeader) != constants.NomsVersion {
if versionCheck && req.Header.Get(NomsVersionHeader) != constants.NomsVersion {
verbose.Log("Returning version mismatch error")
http.Error(
w,
@@ -278,6 +285,29 @@ func handleGetRefs(w http.ResponseWriter, req *http.Request, ps URLParams, cs ch
}
}
func handleGetBlob(w http.ResponseWriter, req *http.Request, ps URLParams, cs chunks.ChunkStore) {
refStr := req.URL.Query().Get("h")
if refStr == "" {
d.Panic("Expected h param")
}
h := hash.Parse(refStr)
if (h == hash.Hash{}) {
d.Panic("h failed to parse")
}
vs := types.NewValueStore(types.NewBatchStoreAdaptor(cs))
v := vs.ReadValue(h)
b, ok := v.(types.Blob)
if !ok {
d.Panic("h is not a Blob")
}
w.Header().Add("Content-Type", "application/octet-stream")
w.Header().Add("Content-Length", fmt.Sprintf("%d", b.Len()))
b.Reader().Copy(w)
}
func extractHashes(req *http.Request) hash.HashSlice {
err := req.ParseForm()
d.PanicIfError(err)

View File

@@ -245,6 +245,69 @@ func TestHandleGetRefs(t *testing.T) {
}
}
func TestHandleGetBlob(t *testing.T) {
assert := assert.New(t)
blobContents := "I am a blob"
cs := chunks.NewTestStore()
db := NewDatabase(cs)
ds := db.GetDataset("foo")
// Test missing h
w := httptest.NewRecorder()
HandleGetBlob(
w,
newRequest("GET", "", "/getBlob/", strings.NewReader(""), http.Header{}),
params{},
cs,
)
assert.Equal(http.StatusBadRequest, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
b := types.NewStreamingBlob(db, bytes.NewBuffer([]byte(blobContents)))
// Test non-present hash
w = httptest.NewRecorder()
HandleGetBlob(
w,
newRequest("GET", "", fmt.Sprintf("/getBlob/?h=%s", b.Hash().String()), strings.NewReader(""), http.Header{}),
params{},
cs,
)
assert.Equal(http.StatusBadRequest, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
r := db.WriteValue(b)
ds, err := db.CommitValue(ds, r)
assert.NoError(err)
// Valid
w = httptest.NewRecorder()
HandleGetBlob(
w,
newRequest("GET", "", fmt.Sprintf("/getBlob/?h=%s", r.TargetHash().String()), strings.NewReader(""), http.Header{}),
params{},
cs,
)
if assert.Equal(http.StatusOK, w.Code, "Handler error:\n%s", string(w.Body.Bytes())) {
out, _ := ioutil.ReadAll(w.Body)
assert.Equal(string(out), blobContents)
}
// Test non-blob
r2 := db.WriteValue(types.Number(1))
ds, err = db.CommitValue(ds, r2)
assert.NoError(err)
w = httptest.NewRecorder()
HandleGetBlob(
w,
newRequest("GET", "", fmt.Sprintf("/getBlob/?h=%s", r2.TargetHash().String()), strings.NewReader(""), http.Header{}),
params{},
cs,
)
assert.Equal(http.StatusBadRequest, w.Code, "Handler error:\n%s", string(w.Body.Bytes()))
}
func TestHandleHasRefs(t *testing.T) {
assert := assert.New(t)
cs := chunks.NewTestStore()