diff --git a/binding/binding.go b/binding/binding.go index 0414a345..85986898 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -22,6 +22,7 @@ const ( MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK2 = "application/msgpack" MIMEYAML = "application/x-yaml" + MIMEOctetStream = "application/octet-stream" ) // Binding describes the interface which needs to be implemented for binding the diff --git a/context.go b/context.go index d69df70b..138ec222 100644 --- a/context.go +++ b/context.go @@ -601,6 +601,27 @@ func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error return err } +// SaveOctetStreamFile is useful for uploading large file since containing request data, it will operate underlying data stream to dst. +func (c *Context) SaveOctetStreamFile(dst string, flag int, perm os.FileMode) error { + if c.GetHeader("Content-Type") != binding.MIMEOctetStream { + return fmt.Errorf("octet stream required %s data format", binding.MIMEOctetStream) + } + method := c.Request.Method + // In particular, only support POST/PATCH/PUT for taking payload according to https://www.rfc-editor.org/rfc/rfc2616#section-9 + if method != http.MethodPost && method != http.MethodPatch && method != http.MethodPut { + return fmt.Errorf("invalid http request method, only support POST/PATCH/PUT") + } + + out, err := os.OpenFile(dst, flag, perm) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, c.Request.Body) + return err +} + // Bind checks the Method and Content-Type to select a binding engine automatically, // Depending on the "Content-Type" header different bindings are used, for example: // "application/json" --> JSON binding diff --git a/context_test.go b/context_test.go index 9e02aede..bfd9082f 100644 --- a/context_test.go +++ b/context_test.go @@ -143,6 +143,38 @@ func TestSaveUploadedCreateFailed(t *testing.T) { assert.Error(t, c.SaveUploadedFile(f, "/")) } +func TestSaveOctetStreamFile(t *testing.T) { + buf := new(bytes.Buffer) + _, err := buf.WriteString("large file binary content") + assert.NoError(t, err, "write string error for buffer") + + c, _ := CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest("POST", "/", buf) + c.Request.Header.Set("Content-Type", binding.MIMEOctetStream) + + assert.NoError(t, c.SaveOctetStreamFile("test", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)) +} + +func TestSaveOctetStreamFileFailed(t *testing.T) { + buf := new(bytes.Buffer) + _, err := buf.WriteString("large file binary content") + assert.NoError(t, err, "write string error for buffer") + + c, _ := CreateTestContext(httptest.NewRecorder()) + + c.Request, _ = http.NewRequest("GET", "/", buf) + c.Request.Header.Set("Content-Type", binding.MIMEOctetStream) + err = c.SaveOctetStreamFile("test", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + assert.Error(t, err) + assert.Contains(t, err.Error(), "support POST/PATCH/PUT") + + c.Request, _ = http.NewRequest("PATCH", "/", buf) + c.Request.Header.Set("Content-Type", binding.MIMEJSON) + err = c.SaveOctetStreamFile("test", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + assert.Error(t, err) + assert.Contains(t, err.Error(), binding.MIMEOctetStream) +} + func TestContextReset(t *testing.T) { router := New() c := router.allocateContext()