由于公司已经有个 Golang 实现的 SFTP 下载文件的服务,所以在它的基础上增加了上传功能
func (s *Server) Upload(w http.ResponseWriter, r *http.Request) {
}
它的核心是接收文件名参数,在本地打开并复制到对方服务器的指定位置
srcFile, _ := os.Open(fileName)
io.Copy(dstFile, io.LimitReader(srcFile, 3e9))
这肯定实现不了我们的需求,要把文件内容给发过来,所以改成了 POST 表单的方式
Storage::put($filename, $fileContent);
$response = (new Client())->post($this->apiUrlRoot.'/upload', [
'headers' => [
'token' => $this->token,
],
'multipart' => [
[
'name' => 'xxx',
'contents' => stream_for(Storage::readStream($filename)),
'filename' => $filename,
],
],
]);
Storage::delete($filename);
在 Golang 中接收有多种写法
file, handler, err := r.FormFile("xxx")
//老
err := r.ParseMultipartForm(0)
fileHeader := r.MultipartForm.File["xxx"][0]
这时 Golang 返回了一个错误:multipart: NextPart: EOF
在搜索解决方案时,都说是请求头不对,不要加
但是这个请求头是 GuzzleHttp 自己生成的,我手动改写后又返回了其它错误,例如 no multipart boundary param in Content-Type
我想,这种不行就换种吧,直接整个 body 放文件流
$response = (new Client())->post($this->apiUrlRoot.'/upload', [
'headers' => [
'token' => $this->token,
'filename' => $filename,
],
'body' => Storage::readStream($filename),
]);
在 Golang 中把请求放入文件
_, _ = io.Copy(dstFile, r.Body)
再经过一次次的测试后,目标文件总是空的,打印请求的内容也是
陷入沉思,到底是哪出了问题(滑稽,所以在两种方法间横跳
回到表单方法,发到本地测试的 PHP 服务上,打印 $_FILES
,成功显示
然后在网上各种搜 Golang 上传文件的例子看,也没看出有啥区别,所以就请教了公司大佬
我们一起排查问题,定位到是 URL 的问题,Go web server is automatically redirecting POST requests
由于在拼接地址时,多了一个 /
,客户端使用 //upload
POST 请求,Golang 会响应 301 到 /upload
,并转为 GET 请求,不会重新发送 Body