使用Go处理文件上传

10

我最近开始学习go语言,所以还是个新手,如果我犯了太多错误,请见谅。我一直在尝试修复这个问题,但是就是不明白发生了什么。在我的main.go文件中,我有一个main函数:

func main() {
    http.HandleFunc("/", handler)
    http.HandleFunc("/submit/", submit)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

处理程序函数如下所示:
func handler(w http.ResponseWriter, r *http.Request) {
    data, _ := ioutil.ReadFile("web/index.html")
    w.Write(data)
}

我知道这不是为网站提供服务的最佳方式。 提交函数看起来像这样:

func submit(w http.ResponseWriter, r *http.Request) {
    log.Println("METHOD IS " + r.Method + " AND CONTENT-TYPE IS " + r.Header.Get("Content-Type"))
    r.ParseMultipartForm(32 << 20)
    file, header, err := r.FormFile("uploadFile")
    if err != nil {
        json.NewEncoder(w).Encode(Response{err.Error(), true})
        return
    }
    defer file.Close()

    out, err := os.Create("/tmp/file_" + time.Now().String() + ".png")
    if err != nil {
        json.NewEncoder(w).Encode(Response{err.Error(), true})
        return
    }
    defer out.Close()

    _, err = io.Copy(out, file)
    if err != nil {
        json.NewEncoder(w).Encode(Response{err.Error(), true})
        return
    }

    json.NewEncoder(w).Encode(Response{"File '" + header.Filename + "' submited successfully", false})
}

问题出在提交函数执行时,r.MethodGETr.Header.Get("Content-Type")为空字符串,然后它会一直执行到第一个if语句,其中r.FormFile返回以下错误:请求的Content-Type不是multipart/form-data。我不明白为什么r.Method总是GET,而且没有Content-Type。我尝试了许多不同的方式来编写index.html,但r.Method总是GET,Content-Type为空。这是上传文件的index.html中的函数:
function upload() {
    var formData = new FormData();
    formData.append('uploadFile', document.querySelector('#file-input').files[0]);
    fetch('/submit', {
        method: 'post',
        headers: {
          "Content-Type": "multipart/form-data"
        },
        body: formData
    }).then(function json(response) {
        return response.json()
    }).then(function(data) {
        window.console.log('Request succeeded with JSON response', data);
    }).catch(function(error) {
        window.console.log('Request failed', error);
    });
}

以下是HTML代码:

<input id="file-input" type="file" name="uploadFile" />

请注意,<main>标签不在<body>标签内。我认为这可能是问题所在,因此我将函数和HTML都更改为以下内容:
function upload() {
    fetch('/submit', {
        method: 'post',
        headers: {
          "Content-Type": "multipart/form-data"
        },
        body: new FormData(document.querySelector('#form')
    }).then(function json(response) {
        return response.json()
    }).then(function(data) {
        window.console.log('Request succeeded with JSON response', data);
    }).catch(function(error) {
        window.console.log('Request failed', error);
    });
}

<form id="form" method="post" enctype="multipart/form-data" action="/submit"><input id="file-input" type="file" name="uploadFile" /></form>

但是那也没有起作用。我在谷歌上搜索了如何使用fetch()和如何从go接收文件上传,发现它们与我的代码非常相似,我不知道我做错了什么。

更新: 在使用curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' http://localhost:8080/submit之后,我得到了以下输出:

* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /submit HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.45.0
> Accept: */*
> Content-Length: 522
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=---------------------------a17d4e54fcec53f8
>
< HTTP/1.1 301 Moved Permanently
< Location: /submit/
< Date: Wed, 18 Nov 2015 14:48:38 GMT
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
* HTTP error before end of send, stop sending
<
* Closing connection 0

当我使用curl时,在运行go run main.go的控制台上没有任何输出。


1
第一步,使用curl检查服务器是否正常。 - Jiang YD
你需要在表单标签上设置method和enctype属性。input标签与GET/POST决策、多部分编码和内容类型设置无关。 - Volker
1
@Volker 我原以为在fetch中指定method: postheader: {'Content-Type': 'multipart/form-data'}就足够了,不需要使用<form>标签,但我也尝试使用了一个类似这样的<form>标签:<form id="form" method="post" enctype="multipart/form-data" action="/submit">,然后使用new FormData来提交该表单中的数据,但是它并没有起作用,所以我猜想可能是其他问题。 - redsalt
@JiangYD 当我使用curl时,运行go run main.go的控制台没有记录任何内容,就好像submit函数从未运行过一样。我在Windows中使用以下命令:curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' http://localhost:8080/submit。我得到了一个HTTP/1.1 301 Moved Permanently的响应,并说HTTP error before end of send, stop sending,这可能解释了为什么submit函数没有运行。 - redsalt
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - redsalt
2个回答

15

我已经成功解决了我的问题,如果有其他人需要的话,这里是解决方法。感谢 @JiangYD 提供使用 curl 测试服务器的提示。

TL;DR

  • 我写了 http.HandleFunc("/submit/", submit) 但是我正在向 /submit 发送 POST 请求(注意缺少的斜杠)<< 这很重要,因为会有重定向
  • 不要手动指定 Content-Type,浏览器会自动处理

LONG ANSWER

我按照 @JiangYD 的建议使用curl测试服务器,并更新了我的答案以包含响应。发现有一个 301 重定向,而我没有添加它,于是我决定使用以下 curl 命令

curl -v -F 'uploadFile=@\"C:/Users/raul-/Desktop/test.png\"' -L http://localhost:8080/submit

(注意-L标志) 这样curl就会跟随重定向,尽管它再次失败了,因为在重定向时,curl从POST切换到GET,但通过该响应,我发现对/submit的请求被重定向到/submit/,我记得这就是我在main函数中写的方式。

修复后仍然失败,响应是http: no such file。通过查看net/http代码,我发现这意味着该字段不存在,所以我快速测试遍历了所有获得的字段名称:

for k, _ := range r.MultipartForm.File {
    log.Println(k)
}

我本来得到的是字段名'uploadFile',我在curl命令中删除了单引号,现在文件上传完美成功了。

但事情并没有结束,我现在知道服务器正在正常运行,因为我可以使用curl上传文件,但当我尝试通过托管的网页上传它时,我收到了一个错误:Content-Type中没有多部分边界参数。

所以我发现我应该在头部包含边界,我将fetch更改为以下内容:

fetch('/submit', {
    method: 'post',
    headers: {
        "Content-Type": "multipart/form-data; boundary=------------------------" + boundary
    }, body: formData})

我是这样计算边界的:

var boundary = Math.random().toString().substr(2);

但是我仍然收到一个错误:multipart: NextPart: EOF,那么如何计算边界呢?我阅读了规范https://html.spec.whatwg.org/multipage/forms.html#multipart/form-data-encoding-algorithm,发现边界是由编码文件的算法计算出来的,而在我的情况下是FormData,FormData API没有公开获取边界的方法,但我发现浏览器会自动添加Content-Type和边界,如果你不指定的话,所以我从fetch调用中删除了headers对象,现在它终于可以工作了!


谢谢你的见解。帮了我很多。 - darkdefender27
“注意缺少的斜杠”拯救了我的一天。请强调您的注意事项,因为这经常是罪魁祸首。+1 - Odd

0

完全删除标题确实有效。特别是通过fetch或axios发送请求时。

axios.post(
                    endpoint + "/api/v1/personalslip",
                    {
                      newSlip
                    },
                    {


                     
                    }
                  )
                  .then(res => {
                    console.log(res);
                  });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接