如何使用 Golang net/http 服务器接收上传的文件?

Posted

技术标签:

【中文标题】如何使用 Golang net/http 服务器接收上传的文件?【英文标题】:How can I receive an uploaded file using a Golang net/http server? 【发布时间】:2017-04-02 17:32:54 【问题描述】:

我在玩 Mux 和 net/http。最近,我正在尝试使用一个带有一个端点的简单服务器来接受文件上传。

这是我目前得到的代码:

server.go

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func main() 
    router := mux.NewRouter()
    router.
        Path("/upload").
        Methods("POST").
        HandlerFunc(UploadCsv)
    fmt.Println("Starting")
    log.Fatal(http.ListenAndServe(":8080", router))

endpoint.go

package main

import (
    "fmt"
    "net/http"
)

func UploadFile(w http.ResponseWriter, r *http.Request) 
    err := r.ParseMultipartForm(5 * 1024 * 1024)
    if err != nil 
        panic(err)
    

    fmt.Println(r.FormValue("fileupload"))

我想我已经将问题缩小到实际从UploadFile 中的请求中检索正文。当我运行这个 cURL 命令时:

curl http://localhost:8080/upload -F "fileupload=@test.txt" -vvv

我得到一个空响应(如预期的那样;我没有打印到ResponseWriter),但我只是在运行服务器的提示符处打印了一个新的(空)行,而不是请求身体。

我将文件作为多部分发送(AFAIK,通过在 cURL 中使用 -F 而不是 -d 暗示),并且 cURL 的详细输出显示已发送 502 个字节:

$ curl http://localhost:8080/upload -F "fileupload=@test.txt" -vvv
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /upload HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Length: 520
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------b578878d86779dc5
> 
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Date: Fri, 18 Nov 2016 19:01:50 GMT
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
< 
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact

在 Go 中使用 net/http 服务器接收作为多部分表单数据上传的文件的正确方法是什么?

【问题讨论】:

也许您正在寻找golang.org/pkg/net/http/#Request.FormFile? 这也是我用来解析多个文件的方法:golang.org/pkg/mime/multipart/#Form @mh-cbon 就是这样!我得到了一个[]byte,它可以正确打印,这意味着我有一些东西可以使用。谢谢! 我这里有一个例子github.com/yanpozka/go-httprouter-upfiles-token/blob/master/… @mh-cbon 请将您的答案添加为答案,以便将其标记为已接受,并且可以在人们寻找要回答的问题时过滤掉此问题。 【参考方案1】:

这是一个简单的例子

func ReceiveFile(w http.ResponseWriter, r *http.Request) 
    r.ParseMultipartForm(32 << 20) // limit your max input length!
    var buf bytes.Buffer
    // in your case file would be fileupload
    file, header, err := r.FormFile("file")
    if err != nil 
        panic(err)
    
    defer file.Close()
    name := strings.Split(header.Filename, ".")
    fmt.Printf("File name %s\n", name[0])
    // Copy the file data to my buffer
    io.Copy(&buf, file)
    // do something with the contents...
    // I normally have a struct defined and unmarshal into a struct, but this will
    // work as an example
    contents := buf.String()
    fmt.Println(contents)
    // I reset the buffer in case I want to use it again
    // reduces memory allocations in more intense projects
    buf.Reset()
    // do something else
    // etc write header
    return

【讨论】:

请注意,为避免内存消耗,您可以直接在io.Copy(&amp;Buf, file) 写入 ondisk,其中 Buf 将替换为文件处理程序。 确实如此,大文件没有es bueno。 我还在 keith wachira 的以下回答中加上了 +1 以指出这一点。 我检查了defer file.Close() 上返回的错误,似乎无法关闭文件——这里真的需要吗?【参考方案2】:

您应该使用FormFile 而不是FormValue

file, fileHeader, err := r.FormFile("fileupload")
defer file.Close()

// copy example
f, err := os.OpenFile("./downloaded", os.O_WRONLY|os.O_CREATE, 0666)
defer f.Close()
io.Copy(f, file)

【讨论】:

如果在同一个请求中恢复一个序列化的对象和一个文件怎么办? @pltvs 如果我通过邮递员中的文件上传明确传递内容长度。处理程序正在引发错误。用上面的代码。寻找建议。 你的意思是标题而不是处理程序? "./test/"+handler.Filename - 一般来说,写入路径可通过外部输入控制的文件是一个非常糟糕的主意。不要这样做。 一次上传多个文件怎么样?【参考方案3】:

这是我为帮助我上传文件而编写的函数。您可以在此处查看完整版本。 How to upload files in golang

package helpers

import (
    "io"
    "net/http"
    "os"
)

// This function returns the filename(to save in database) of the saved file
// or an error if it occurs
func FileUpload(r *http.Request) (string, error) 
    // ParseMultipartForm parses a request body as multipart/form-data
    r.ParseMultipartForm(32 << 20)

    file, handler, err := r.FormFile("file") // Retrieve the file from form data

    if err != nil 
        return "", err
    
    defer file.Close()                       // Close the file when we finish

    // This is path which we want to store the file
    f, err := os.OpenFile("/pathToStoreFile/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)

    if err != nil 
        return "", err
    

    // Copy the file to the destination path
    io.Copy(f, file)

    return handler.Filename, nil

【讨论】:

您所附代码的缩进不一致,请修正。 一次上传多个文件怎么样?

以上是关于如何使用 Golang net/http 服务器接收上传的文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Golang 的 net/http 包读取流响应正文?

纯golang爬虫实战(二)

如何在 golang net/http 中使用 Transport 添加头信息

Golang使用net/http包写http服务端

Golang使用net/http包写http服务端

Golang HTTP服务