Commit 53885e61 authored by Janoš Guljaš's avatar Janoš Guljaš Committed by GitHub

support binary upload to api files endpoint (#306)

parent 77c1f634
......@@ -27,45 +27,40 @@ type bytesPostResponse struct {
// bytesUploadHandler handles upload of raw binary data of arbitrary length.
func (s *server) bytesUploadHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
responseObject, err := s.splitUpload(ctx, r.Body, r.ContentLength)
address, err := s.splitUpload(ctx, r.Body, r.ContentLength)
if err != nil {
s.Logger.Debugf("bytes upload: %v", err)
var response jsonhttp.StatusResponse
response.Message = "upload error"
response.Code = http.StatusInternalServerError
jsonhttp.Respond(w, response.Code, response)
} else {
jsonhttp.OK(w, responseObject)
jsonhttp.InternalServerError(w, nil)
return
}
jsonhttp.OK(w, bytesPostResponse{
Reference: address,
})
}
func (s *server) splitUpload(ctx context.Context, r io.ReadCloser, l int64) (interface{}, error) {
func (s *server) splitUpload(ctx context.Context, r io.Reader, l int64) (swarm.Address, error) {
chunkPipe := file.NewChunkPipe()
go func() {
buf := make([]byte, swarm.ChunkSize)
c, err := io.CopyBuffer(chunkPipe, r, buf)
if err != nil {
s.Logger.Debugf("split upload: io error %d: %v", c, err)
s.Logger.Error("io error")
s.Logger.Error("split upload: io error")
return
}
if c != l {
s.Logger.Debugf("split upload: read count mismatch %d: %v", c, err)
s.Logger.Error("read count mismatch")
s.Logger.Error("split upload: read count mismatch")
return
}
err = chunkPipe.Close()
if err != nil {
s.Logger.Errorf("split upload: incomplete file write close %v", err)
s.Logger.Error("incomplete file write close")
s.Logger.Debugf("split upload: incomplete file write close %v", err)
s.Logger.Error("split upload: incomplete file write close")
}
}()
sp := splitter.NewSimpleSplitter(s.Storer)
address, err := sp.Split(ctx, chunkPipe, l)
if err != nil {
return swarm.ZeroAddress, err
}
return bytesPostResponse{Reference: address}, nil
return sp.Split(ctx, chunkPipe, l)
}
// bytesGetHandler handles retrieval of raw binary data of arbitrary length.
......
......@@ -5,5 +5,6 @@
package api
type (
BytesPostResponse = bytesPostResponse
BytesPostResponse = bytesPostResponse
FileUploadResponse = fileUploadResponse
)
This diff is collapsed.
......@@ -10,6 +10,7 @@ import (
"io/ioutil"
"mime"
"net/http"
"strings"
"testing"
"github.com/ethersphere/bee/pkg/api"
......@@ -21,29 +22,29 @@ import (
"github.com/ethersphere/bee/pkg/tags"
)
func TestBzz(t *testing.T) {
func TestFiles(t *testing.T) {
var (
simpleResource = func() string { return "/files" }
addressResource = func(addr string) string { return "/files/" + addr }
simpleData = []byte("this is a simple text")
mockStorer = mock.NewStorer()
client = newTestServer(t, testServerOptions{
Storer: mockStorer,
fileUploadResource = "/files"
fileDownloadResource = func(addr string) string { return "/files/" + addr }
simpleData = []byte("this is a simple text")
client = newTestServer(t, testServerOptions{
Storer: mock.NewStorer(),
Tags: tags.NewTags(),
Logger: logging.New(ioutil.Discard, 5),
})
)
t.Run("simple-upload", func(t *testing.T) {
jsonhttptest.ResponseDirectSendHeadersAndReceiveHeaders(t, client, http.MethodPost, simpleResource(), bytes.NewReader(simpleData), http.StatusBadRequest, jsonhttp.StatusResponse{
Message: "not a mutlipart/form-data",
t.Run("invalid-content-type", func(t *testing.T) {
jsonhttptest.ResponseDirectSendHeadersAndReceiveHeaders(t, client, http.MethodPost, fileUploadResource, bytes.NewReader(simpleData), http.StatusBadRequest, jsonhttp.StatusResponse{
Message: "invalid content-type header",
Code: http.StatusBadRequest,
}, nil)
})
t.Run("simple-upload", func(t *testing.T) {
t.Run("multipart-upload", func(t *testing.T) {
fileName := "simple_file.txt"
rootHash := "295673cf7aa55d119dd6f82528c91d45b53dd63dc2e4ca4abf4ed8b3a0788085"
_ = jsonhttptest.ResponseDirectWithMultiPart(t, client, http.MethodPost, simpleResource(), fileName, simpleData, http.StatusOK, "", api.FileUploadResponse{
_ = jsonhttptest.ResponseDirectWithMultiPart(t, client, http.MethodPost, fileUploadResource, fileName, simpleData, http.StatusOK, "", api.FileUploadResponse{
Reference: swarm.MustParseHexAddress(rootHash),
})
})
......@@ -51,22 +52,47 @@ func TestBzz(t *testing.T) {
t.Run("check-content-type-detection", func(t *testing.T) {
fileName := "my-pictures.jpeg"
rootHash := "f2e761160deda91c1fbfab065a5abf530b0766b3e102b51fbd626ba37c3bc581"
_ = jsonhttptest.ResponseDirectWithMultiPart(t, client, http.MethodPost, simpleResource(), fileName, simpleData, http.StatusOK, "image/jpeg; charset=utf-8", api.FileUploadResponse{
Reference: swarm.MustParseHexAddress(rootHash),
t.Run("binary", func(t *testing.T) {
headers := make(http.Header)
headers.Add("Content-Type", "image/jpeg; charset=utf-8")
_ = jsonhttptest.ResponseDirectSendHeadersAndReceiveHeaders(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, bytes.NewReader(simpleData), http.StatusOK, api.FileUploadResponse{
Reference: swarm.MustParseHexAddress(rootHash),
}, headers)
rcvdHeader := jsonhttptest.ResponseDirectCheckBinaryResponse(t, client, http.MethodGet, fileDownloadResource(rootHash), nil, http.StatusOK, simpleData, nil)
cd := rcvdHeader.Get("Content-Disposition")
_, params, err := mime.ParseMediaType(cd)
if err != nil {
t.Fatal(err)
}
if params["filename"] != fileName {
t.Fatal("Invalid file name detected")
}
if rcvdHeader.Get("Content-Type") != "image/jpeg; charset=utf-8" {
t.Fatal("Invalid content type detected")
}
})
rcvdHeader := jsonhttptest.ResponseDirectCheckBinaryResponse(t, client, http.MethodGet, addressResource(rootHash), nil, http.StatusOK, simpleData, nil)
cd := rcvdHeader.Get("Content-Disposition")
_, params, err := mime.ParseMediaType(cd)
if err != nil {
t.Fatal(err)
}
if params["filename"] != fileName {
t.Fatal("Invalid file name detected")
}
if rcvdHeader.Get("Content-Type") != "image/jpeg; charset=utf-8" {
t.Fatal("Invalid content type detected")
}
t.Run("multipart", func(t *testing.T) {
_ = jsonhttptest.ResponseDirectWithMultiPart(t, client, http.MethodPost, fileUploadResource, fileName, simpleData, http.StatusOK, "image/jpeg; charset=utf-8", api.FileUploadResponse{
Reference: swarm.MustParseHexAddress(rootHash),
})
rcvdHeader := jsonhttptest.ResponseDirectCheckBinaryResponse(t, client, http.MethodGet, fileDownloadResource(rootHash), nil, http.StatusOK, simpleData, nil)
cd := rcvdHeader.Get("Content-Disposition")
_, params, err := mime.ParseMediaType(cd)
if err != nil {
t.Fatal(err)
}
if params["filename"] != fileName {
t.Fatal("Invalid file name detected")
}
if rcvdHeader.Get("Content-Type") != "image/jpeg; charset=utf-8" {
t.Fatal("Invalid content type detected")
}
})
})
t.Run("upload-then-download-and-check-data", func(t *testing.T) {
......@@ -83,28 +109,60 @@ func TestBzz(t *testing.T) {
</body>
</html>`
rcvdHeader := jsonhttptest.ResponseDirectWithMultiPart(t, client, http.MethodPost, simpleResource(), fileName, []byte(sampleHtml), http.StatusOK, "", api.FileUploadResponse{
Reference: swarm.MustParseHexAddress(rootHash),
t.Run("binary", func(t *testing.T) {
headers := make(http.Header)
headers.Add("Content-Type", "text/html; charset=utf-8")
rcvdHeader := jsonhttptest.ResponseDirectSendHeadersAndReceiveHeaders(t, client, http.MethodPost, fileUploadResource+"?name="+fileName, strings.NewReader(sampleHtml), http.StatusOK, api.FileUploadResponse{
Reference: swarm.MustParseHexAddress(rootHash),
}, headers)
if rcvdHeader.Get("ETag") != fmt.Sprintf("%q", rootHash) {
t.Fatal("Invalid ETags header received")
}
// try to fetch the same file and check the data
rcvdHeader = jsonhttptest.ResponseDirectCheckBinaryResponse(t, client, http.MethodGet, fileDownloadResource(rootHash), nil, http.StatusOK, []byte(sampleHtml), nil)
// check the headers
cd := rcvdHeader.Get("Content-Disposition")
_, params, err := mime.ParseMediaType(cd)
if err != nil {
t.Fatal(err)
}
if params["filename"] != fileName {
t.Fatal("Invalid filename detected")
}
if rcvdHeader.Get("Content-Type") != "text/html; charset=utf-8" {
t.Fatal("Invalid content type detected")
}
})
if rcvdHeader.Get("ETag") != fmt.Sprintf("%q", rootHash) {
t.Fatal("Invalid ETags header received")
}
// try to fetch the same file and check the data
rcvdHeader = jsonhttptest.ResponseDirectCheckBinaryResponse(t, client, http.MethodGet, addressResource(rootHash), nil, http.StatusOK, []byte(sampleHtml), nil)
// check the headers
cd := rcvdHeader.Get("Content-Disposition")
_, params, err := mime.ParseMediaType(cd)
if err != nil {
t.Fatal(err)
}
if params["filename"] != fileName {
t.Fatal("Invalid filename detected")
}
if rcvdHeader.Get("Content-Type") != "text/html; charset=utf-8" {
t.Fatal("Invalid content type detected")
}
t.Run("multipart", func(t *testing.T) {
rcvdHeader := jsonhttptest.ResponseDirectWithMultiPart(t, client, http.MethodPost, fileUploadResource, fileName, []byte(sampleHtml), http.StatusOK, "", api.FileUploadResponse{
Reference: swarm.MustParseHexAddress(rootHash),
})
if rcvdHeader.Get("ETag") != fmt.Sprintf("%q", rootHash) {
t.Fatal("Invalid ETags header received")
}
// try to fetch the same file and check the data
rcvdHeader = jsonhttptest.ResponseDirectCheckBinaryResponse(t, client, http.MethodGet, fileDownloadResource(rootHash), nil, http.StatusOK, []byte(sampleHtml), nil)
// check the headers
cd := rcvdHeader.Get("Content-Disposition")
_, params, err := mime.ParseMediaType(cd)
if err != nil {
t.Fatal(err)
}
if params["filename"] != fileName {
t.Fatal("Invalid filename detected")
}
if rcvdHeader.Get("Content-Type") != "text/html; charset=utf-8" {
t.Fatal("Invalid content type detected")
}
})
})
}
......@@ -35,18 +35,16 @@ func (s *server) setupRouting() {
fmt.Fprintln(w, "User-agent: *\nDisallow: /")
})
handle(router, "/bytes", jsonhttp.MethodHandler{
"POST": http.HandlerFunc(s.bytesUploadHandler),
})
handle(router, "/files", jsonhttp.MethodHandler{
"POST": http.HandlerFunc(s.bzzFileUploadHandler),
"POST": http.HandlerFunc(s.fileUploadHandler),
})
handle(router, "/files/{addr}", jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.bzzFileDownloadHandler),
"GET": http.HandlerFunc(s.fileDownloadHandler),
})
handle(router, "/bytes", jsonhttp.MethodHandler{
"POST": http.HandlerFunc(s.bytesUploadHandler),
})
handle(router, "/bytes/{address}", jsonhttp.MethodHandler{
"GET": http.HandlerFunc(s.bytesGetHandler),
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment