英文:
Test for handler with file upload
问题
我正在尝试为一个接收文件的处理程序编写一个测试。作为其中的一部分,我正在尝试配置上下文,以便处理程序可以使用它。
我的意图是创建一个文件并使用multipart.FileHeader来打开它。
f, err := os.CreateTemp("", "upload-test")
require.NoError(t, err)
_, err = f.Write([]byte("1234"))
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
fileHeader := &multipart.FileHeader{
Filename: f.Name(),
Size: 4,
}
open, err := fileHeader.Open()
require.NoError(t, err)
然而,Open
方法返回:"open: 没有该文件或目录"
更新:
我的端点接收以下结构体:
type UploadDatabaseRequest struct {
Group string `form:"group" binding:"required"`
Database *multipart.FileHeader `form:"database" binding:"required"`
}
根据Ivan的答案,我尝试使用multipart.NewReader
来读取请求,以便获得一个*multipart.FileHeader
。然而,下面的代码在调用multipartReader.ReadForm(100000000)
时会导致"unexpected EOF"的错误。
contentType := multipartWriter.FormDataContentType()
_, params, err := mime.ParseMediaType(contentType)
assert.NoError(t, err)
multipartReader := multipart.NewReader(bytes.NewReader(buf.Bytes()), params["boundary"])
form, err := multipartReader.ReadForm(100000000)
require.NoError(t, err)
fileHeader := form.File["file"][0]
uploadRequest := &UploadDatabaseRequest{
Group: groupName,
Database: fileHeader,
}
英文:
I'm trying to write a test for a handler which receives a file. As part of such I'm trying to configure my context so the handler can use it.
My intention is to create a file and use multipart.FileHeader to open it.
f, err := os.CreateTemp("", "upload-test")
require.NoError(t, err)
_, err = f.Write([]byte("1234"))
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
fileHeader := &multipart.FileHeader{
Filename: f.Name(),
Size: 4,
}
open, err := fileHeader.Open()
require.NoError(t, err)
However the Open
method returns: "open : no such file or directory"
UPDATE:
My endpoint receives the below struct
type UploadDatabaseRequest struct {
Group string `form:"group" binding:"required"`
Database *multipart.FileHeader `form:"database" binding:"required"`
}
Based on Ivan 's answer I tried to read the request using multipart.NewReader
so I could get a *multipart.FileHeader
. However the below code results in "unexpected EOF" from the call to multipartReader.ReadForm(100000000)
.
contentType := multipartWriter.FormDataContentType()
_, params, err := mime.ParseMediaType(contentType)
assert.NoError(t, err)
multipartReader := multipart.NewReader(bytes.NewReader(buf.Bytes()), params["boundary"])
form, err := multipartReader.ReadForm(100000000)
require.NoError(t, err)
fileHeader := form.File["file"][0]
uploadRequest := &UploadDatabaseRequest{
Group: groupName,
Database: fileHeader,
}
答案1
得分: 1
我创建了一个示例来演示它应该如何工作。首先,让我介绍一下代码,然后我会逐步解释所有相关部分。
上传
上传操作在handlers_test.go
文件中完成。我编写了两个测试来展示如何创建一个带有多部分主体的有效HTTP请求(我假设通信是基于HTTP的)。以下是代码:
package multipart
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHandleFile(t *testing.T) {
t.Run("MultipartRequest", func(t *testing.T) {
// 实例化多部分请求
var buf bytes.Buffer
multipartWriter := multipart.NewWriter(&buf)
defer multipartWriter.Close()
// 添加表单字段
filePart, _ := multipartWriter.CreateFormFile("file", "file.txt")
filePart.Write([]byte("Hello, World!"))
r := httptest.NewRequest(http.MethodPost, "/file", &buf)
w := httptest.NewRecorder()
r.Header.Set("Content-Type", multipartWriter.FormDataContentType())
HandleFile(w, r)
data, _ := io.ReadAll(w.Result().Body)
assert.Equal(t, http.StatusOK, w.Result().StatusCode)
assert.Equal(t, []byte("Hello, World!"), data)
})
t.Run("PlainRequest", func(t *testing.T) {
r := httptest.NewRequest(http.MethodPost, "/file", nil)
w := httptest.NewRecorder()
HandleFile(w, r)
assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
})
}
我们可以关注子测试MultipartRequest
。首先,它实例化一个多部分主体,稍后将用作我们将要发送的HTTP请求的请求有效负载。然后,我们创建一个文件部分并向其写入虚拟内容。在发送请求之前,我们需要查看将用于解析内容的Content-Type
头部。测试的其余部分应该很简单。
读取
读取(或解析)由HTTP服务器完成。涉及的文件是handlers.go
文件:
package multipart
import (
"io"
"mime"
"mime/multipart"
"net/http"
"strings"
)
func HandleFile(w http.ResponseWriter, r *http.Request) {
mediaType, params, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
if strings.HasPrefix(mediaType, "multipart/") {
multipartReader := multipart.NewReader(r.Body, params["boundary"])
filePart, _ := multipartReader.NextPart()
// 注意在读取大文件时
data, _ := io.ReadAll(filePart)
w.Write(data)
return
}
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("request is not multipart"))
}
在这里,相关步骤可以总结如下:
- 我们从HTTP请求中检索并解析
Content-Type
头部 - 我们检查上述值是否以字符串
multipart/
开头 - 如果是,我们读取主体的下一个(也是唯一的)部分,并将其内容写入响应流
- 如果不是,我们向HTTP客户端返回
BadRequest
错误
在代码中,我添加了一些注释来解释一些需要注意的关键部分。此外,我简化了代码库,没有处理可能发生的任何错误。
希望能帮助解决您的问题,如果有任何疑问,请告诉我!
英文:
I created an example to demonstrate how it should work. First, let me present the code, then, I'll walk you through all of the relevant parts.
Upload
The upload is done in the handlers_test.go
file. I wrote two tests to show off how to create a valid HTTP Request with a multipart body (I assumed that the communication was based on HTTP). Here you can find the code:
package multipart
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHandleFile(t *testing.T) {
t.Run("MultipartRequest", func(t *testing.T) {
// instantiate multipart request
var buf bytes.Buffer
multipartWriter := multipart.NewWriter(&buf)
defer multipartWriter.Close()
// add form field
filePart, _ := multipartWriter.CreateFormFile("file", "file.txt")
filePart.Write([]byte("Hello, World!"))
r := httptest.NewRequest(http.MethodPost, "/file", &buf)
w := httptest.NewRecorder()
r.Header.Set("Content-Type", multipartWriter.FormDataContentType())
HandleFile(w, r)
data, _ := io.ReadAll(w.Result().Body)
assert.Equal(t, http.StatusOK, w.Result().StatusCode)
assert.Equal(t, []byte("Hello, World!"), data)
})
t.Run("PlainRequest", func(t *testing.T) {
r := httptest.NewRequest(http.MethodPost, "/file", nil)
w := httptest.NewRecorder()
HandleFile(w, r)
assert.Equal(t, http.StatusBadRequest, w.Result().StatusCode)
})
}
We can focus on the subtest MultipartRequest
. First, it instantiates a multipart body which will be used later as the request payload of the HTTP request we're going to send. Then, we create a file part and write dummy content to it. Before sending out the request, we've to see the Content-Type
header that will be used for parsing stuff. The rest of the test should be pretty straightforward.
Read
The read (or parsing) is done by the HTTP server. The file involved is the handlers.go
file:
package multipart
import (
"io"
"mime"
"mime/multipart"
"net/http"
"strings"
)
func HandleFile(w http.ResponseWriter, r *http.Request) {
mediaType, params, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
if strings.HasPrefix(mediaType, "multipart/") {
multipartReader := multipart.NewReader(r.Body, params["boundary"])
filePart, _ := multipartReader.NextPart()
// pay attention here when you read large file
data, _ := io.ReadAll(filePart)
w.Write(data)
return
}
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("request is not multipart"))
}
Here, the relevant steps can be summarized in the following list:
- We retrieve and parse the
Content-Type
header from the HTTP Request - We check if the above value starts with the string
multipart/
- If so, we read the next (and only) part of the body and we write its content to the response stream
- If not, we return a
BadRequest
error to the HTTP client
In the code I put some comments to explain some delicate sections that deserve attention. Furthermore, I simplified the codebase by not handling any error that might happen.
Hope to help in solving your issue, let me know!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论