From 2dd5983841bc4231c15d80cb06e700bcbb032456 Mon Sep 17 00:00:00 2001 From: Rick Olson Date: Wed, 25 Feb 2015 18:32:33 -0700 Subject: [PATCH] teach Download() how to follow hypermedia --- hawser/client.go | 48 ++++++++++++++++++++++- hawser/client_download_test.go | 72 ++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/hawser/client.go b/hawser/client.go index 5bb489e3..eee4f2d6 100644 --- a/hawser/client.go +++ b/hawser/client.go @@ -14,15 +14,20 @@ import ( "net/http" "os" "path/filepath" + "strings" ) const ( // Legacy type gitMediaType = "application/vnd.git-media" + // The main type, sub type, and suffix. Use this when ensuring the type from + // an HTTP response is correct. + gitMediaMetaTypePrefix = gitMediaType + "+json" + // Adds the extra mime params. Use this when sending the type in an HTTP // request. - gitMediaMetaType = gitMediaType + "+json; charset=utf-8" + gitMediaMetaType = gitMediaMetaTypePrefix + "; charset=utf-8" ) func Download(oidPath string) (io.ReadCloser, int64, *WrappedError) { @@ -46,6 +51,47 @@ func Download(oidPath string) (io.ReadCloser, int64, *WrappedError) { return nil, 0, wErr } + if strings.HasPrefix(contentType, gitMediaMetaTypePrefix) { + obj := &objectResource{} + err := json.NewDecoder(res.Body).Decode(obj) + res.Body.Close() + if err != nil { + wErr := Error(err) + setErrorResponseContext(wErr, res) + return nil, 0, wErr + } + + dlReq, err := obj.NewRequest("download", "GET") + if err != nil { + wErr := Error(err) + setErrorResponseContext(wErr, res) + return nil, 0, wErr + } + + dlCreds, err := setRequestHeaders(dlReq) + if err != nil { + return nil, 0, Errorf(err, "Error attempting to GET %s", oidPath) + } + + dlRes, err := DoHTTP(Config, dlReq) + if err != nil { + wErr := Error(err) + setErrorResponseContext(wErr, res) + return nil, 0, wErr + } + + saveCredentials(dlCreds, dlRes) + + contentType := dlRes.Header.Get("Content-Type") + if contentType == "" { + wErr = Error(errors.New("Empty Content-Type")) + setErrorResponseContext(wErr, res) + return nil, 0, wErr + } + + res = dlRes + } + ok, headerSize, wErr := validateMediaHeader(contentType, res.Body) if !ok { setErrorResponseContext(wErr, res) diff --git a/hawser/client_download_test.go b/hawser/client_download_test.go index 7d2e682c..8abff2d0 100644 --- a/hawser/client_download_test.go +++ b/hawser/client_download_test.go @@ -1,6 +1,7 @@ package hawser import ( + "encoding/json" "io/ioutil" "net/http" "net/http/httptest" @@ -49,6 +50,77 @@ func TestDownload(t *testing.T) { } } +func TestDownloadFromMeta(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + tmp := tempdir(t) + defer server.Close() + defer os.RemoveAll(tmp) + + // simulates an endpoint that returns the meta data for every request. + // this way downloads keep working with the older prototype server during + // the pre-release. + mux.HandleFunc("/media/objects/oid", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + w.WriteHeader(405) + return + } + + obj := &objectResource{ + Oid: "oid", + Size: 4, + Links: map[string]*linkRelation{ + "download": &linkRelation{ + Href: server.URL + "/media/download/oid", + }, + }, + } + + by, err := json.Marshal(obj) + if err != nil { + t.Errorf("Error marshaling json: %s", err) + } + + head := w.Header() + head.Set("Content-Type", "application/vnd.git-media+json") + w.WriteHeader(200) + w.Write(by) + }) + + mux.HandleFunc("/media/download/oid", func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + w.WriteHeader(405) + return + } + + head := w.Header() + head.Set("Content-Type", "application/octet-stream") + head.Set("Content-Length", "4") + w.WriteHeader(200) + w.Write([]byte("test")) + }) + + Config.SetConfig("hawser.url", server.URL+"/media") + reader, size, wErr := Download("whatever/oid") + if wErr != nil { + t.Fatalf("unexpected error: %s", wErr) + } + defer reader.Close() + + if size != 4 { + t.Errorf("unexpected size: %d", size) + } + + by, err := ioutil.ReadAll(reader) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if body := string(by); body != "test" { + t.Errorf("unexpected body: %s", body) + } +} + func TestDownloadWithRedirect(t *testing.T) { mux := http.NewServeMux() server := httptest.NewServer(mux)