Handle file uploading with go

huangapple go评论105阅读模式
英文:

Handle file uploading with go

问题

我最近开始学习使用Go,所以我还是个新手,如果我犯了太多错误,请原谅。我一直在尝试解决这个问题很长时间了,但我就是不明白发生了什么。在我的main.go文件中,我有一个main函数:

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

handler函数的代码如下:

func handler(w http.ResponseWriter, r *http.Request) {
    data, _ := ioutil.ReadFile("web/index.html")
    w.Write(data)
}

我知道这不是提供网站的最佳方式。submit函数的代码如下:

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 + "' submitted successfully", false})
}

问题是当执行submit函数时,r.Method的值是GETr.Header.Get("Content-Type")的值是空字符串,然后继续执行直到第一个if语句,其中r.FormFile返回以下错误:
request Content-Type isn't 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" />

请注意,<input>标签不在<form>标签内,我认为这可能是问题所在,所以我将函数和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>

但这也没有起作用。我在Google上搜索了如何使用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的控制台上没有任何输出。

英文:

I've started playing with go very recently so I'm still a noob, sorry if I make too many mistakes. I've been trying to fix this for a long time but I just don't understand what's going on. In my main.go file I have a main function:

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

The handler function looks like this:

func handler(w http.ResponseWriter, r *http.Request) {
    data, _ := ioutil.ReadFile(&quot;web/index.html&quot;)
    w.Write(data)
}

I know this is not the best way to serve a website
The submit function looks like this:

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

    out, err := os.Create(&quot;/tmp/file_&quot; + time.Now().String() + &quot;.png&quot;)
    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{&quot;File &#39;&quot; + header.Filename + &quot;&#39; submited successfully&quot;, false})
}

The problem is when the submit function is executed, r.Method is GET and r.Header.Get(&quot;Content-Type&quot;) is an empty string, then it continues until the first if where r.FormFile returns the following error:
request Content-Type isn&#39;t multipart/form-data
I don't understand why r.Method is always GET and there's no Content-Type. I've tried to do the index.html in many different ways but r.Method is always GET and Content-Type is empty. Here's the function in index.html that uploads a file:

function upload() {
    var formData = new FormData();
    formData.append(&#39;uploadFile&#39;, document.querySelector(&#39;#file-input&#39;).files[0]);
    fetch(&#39;/submit&#39;, {
        method: &#39;post&#39;,
        headers: {
          &quot;Content-Type&quot;: &quot;multipart/form-data&quot;
        },
        body: formData
    }).then(function json(response) {
        return response.json()
    }).then(function(data) {
        window.console.log(&#39;Request succeeded with JSON response&#39;, data);
    }).catch(function(error) {
        window.console.log(&#39;Request failed&#39;, error);
    });
}

And here's the HTML:

&lt;input id=&quot;file-input&quot; type=&quot;file&quot; name=&quot;uploadFile&quot; /&gt;

Note that the <input> tag is not inside a <form> tag, I thought that could be the problem so I changed both the function and the HTML to something like this:

function upload() {
    fetch(&#39;/submit&#39;, {
        method: &#39;post&#39;,
        headers: {
          &quot;Content-Type&quot;: &quot;multipart/form-data&quot;
        },
        body: new FormData(document.querySelector(&#39;#form&#39;)
    }).then(function json(response) {
        return response.json()
    }).then(function(data) {
        window.console.log(&#39;Request succeeded with JSON response&#39;, data);
    }).catch(function(error) {
        window.console.log(&#39;Request failed&#39;, error);
    });
}

&lt;form id=&quot;form&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot; action=&quot;/submit&quot;&gt;&lt;input id=&quot;file-input&quot; type=&quot;file&quot; name=&quot;uploadFile&quot; /&gt;&lt;/form&gt;

But that didn't work neither. I've searched with Google how to use fetch() and how to receive a file upload from go and I've seen that they are pretty similar to mine, I don't know what I'm doing wrong.

UPDATE:
After using curl -v -F &#39;uploadFile=@\&quot;C:/Users/raul-/Desktop/test.png\&quot;&#39; http://localhost:8080/submit I get the following output:

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

The console where I'm running go run main.go outputs nothing when using curl.

答案1

得分: 15

我成功解决了我的问题,以下是解决方法,以便其他人需要时参考。感谢 @JiangYD 提供使用 curl 测试服务器的提示。

简短回答

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

详细回答

我按照 @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 上传文件,但是当我尝试通过托管的网页上传文件时,出现了错误:no multipart boundary param in Content-Type

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

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

我像这样计算 boundary:

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

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

英文:

I managed to solve my problem, so here it is in case someone else needs it. And thanks @JiangYD for the tip of using curl to test the server.

TL;DR

  • I wrote http.HandleFunc(&quot;/submit/&quot;, submit) but I was making a POST request to /submit (note the missing slash) << This is important because of redirections
  • Don't specify the Content-Type yourself, the browser will do it for you

LONG ANSWER

I did as @JiangYD said and used curl to test the server, I updated my answer with the response. I found odd that there was a 301 Redirect since I didn't put it there, I decided to use the following curl command

curl -v -F &#39;uploadFile=@\&quot;C:/Users/raul-/Desktop/test.png\&quot;&#39; -L http://localhost:8080/submit

(note the -L) That way curl followed the redirect, though it failed again because, when redirecting, curl switched from POST to GET but with that response I found out that the request to /submit was being redirected to /submit/ and I remembered that's how I wrote it in the main function.

After fixing that it still failed, the response was http: no such file and by looking at the net/http code I found that it meant the field didn't exist, so I did a quick test iterating over all the field names obtained:

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

I was getting &#39;uploadFile as the field name, I removed the single quotes in the curl command and now it uploaded the file perfectly

But it doesn't end here, I now knew the server was working correctly because I could upload a file using curl but when I tried uploading it through the hosted web page I got an error: no multipart boundary param in Content-Type.

So I found out I was suppose to include the boundary in the header, I changed fetch to something like this:

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

I calculate the boundary like this:

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

But I still got an error: multipart: NextPart: EOF So how do you calculate the boundary? I read the spec https://html.spec.whatwg.org/multipage/forms.html#multipart/form-data-encoding-algorithm and found out the boundary is calculated by the algorithm that encodes the file, which in my case is FormData, the FormData API doesn't expose a way to get that boundary but I found out that the browser adds the Content-Type with multipart/form-data and the boundary automatically if you don't specify it so I removed the headers object from the fetch call and now it finally works!

答案2

得分: 0

移除头部实际上是有效的,特别是在通过fetch或axios发送请求时。

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

}

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

英文:

Removing the header altogether actually works. Especially when sending the request via fetch or axios.

axios.post(
					endpoint + &quot;/api/v1/personalslip&quot;,
					{
					  newSlip
					},
					{


					 
					}
				  )
				  .then(res =&gt; {
					console.log(res);
				  });

huangapple
  • 本文由 发表于 2015年11月18日 11:11:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/33771167.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定