闲的没事,想着自己搭建一个资源服务器可能以后能用到。选择使用
Nginx + Golang
搭建,结果这一搞就搭进去了三个晚上,都是泪啊。所以决定写一篇博客来祭奠一下逝去的三个晚上。
环境依赖
-
Nginx
我安装的版本为nginx version: nginx/1.12.2
-
upload-nginx-module
标准的Nginx
是不包含upload-nginx-module
的,所在编译的时候记得添加 -
Golang
环境go version go1.8.1 darwin/amd64
上面提及这些具体怎么安装,我就不在这详细描述了,大家可以自行Google
解决
资源服务器搭建思路
- 客户端上传文件资源到
Nginx
服务器 -
Nginx
服务器将资源保存在临时目录,将请求转发给Golang
业务服务器 - 业务服务器将资源移动位置,重命名操作
- 业务服务器返回处理结果给客户端
当然还有一些其他设计思路,比如可以将图片直接上传给业务服务器,再由业务服务器传给资源服务器。或者直接将图片传给业务服务器,等客户端向资源服务器请求资源的时候,再由资源服务器来业务服务器取。但是后面说的这两种,如果要做断点续传,都需要业务服务器自己处理一些逻辑。但是第一种设计,就可以把断电续传等功能都交由资源服务器处理了,因此我选择了第一种设计。
nginx配置
location /upload {
#上传完成后后端接受处理文件
upload_pass @uploadImageGo;
#文件上传路径,文件临时路径
upload_store /Users/liuhu/Desktop/nginxRoot/www/upload/images;
#上传速度限制
upload_limit_rate 2048k;
#文件读写权限
upload_store_access user:rw;
#表单中提交的信息
upload_set_form_field $upload_field_name.name "$upload_file_name";
upload_set_form_field $upload_field_name.content_type "$upload_content_type";
upload_set_form_field $upload_field_name.path "$upload_tmp_path";
#存储完毕后的信息
upload_aggregate_form_field "$upload_field_name.md5" "$upload_file_md5";
upload_aggregate_form_field "$upload_field_name.size" "$upload_file_size";
#转发的时候只传submit和description
#upload_pass_form_field "^submit$|^description$";
#打开开关,意思就是把前端请求的参数会传给后端
upload_pass_args on;
#将表单中所有信息传递给后端使用,客户端提交的信息也传递
upload_pass_form_field "^.*$";
#发生这些错无的时候删除资源
upload_cleanup 400 404 499 500-505;
}
location @uploadImageGo {
proxy_method POST;
proxy_pass http://localhost:8081;
}
注意:必须使用Post
请求/uoload接口,不能nginx会直接报错
业务服务器处理
三个晚上就坑在了这里
-
当时调用
r.ParseForm()
之后,就直接调用r.Form
发现Form
是一个空的Map
,但是Content-Length
又是大于0
的 -
然而想看到底有没有穿参数过来,就改了
nginx
的几个配置。比如少传几个参数或者多传几个参数,发现Content-Length
又是有变化的,证明参数是穿过来的 -
没有办法只能就把整个
http
给dump
出来了-
dump
整个http
的代码如下httpData, _ := httputil.DumpRequest(r, true) log.Println(string(httpData))
-
dump
的结果是POST /upload HTTP/1.0 Host: localhost:8081 Connection: close Accept: */* Connection: close Content-Length: 778 Content-Type: multipart/form-data; boundary=------------------------767b3fb9efcce66b User-Agent: curl/7.54.0 --------------------------767b3fb9efcce66b Content-Disposition: form-data; name="image.name" vim_keymap.png --------------------------767b3fb9efcce66b Content-Disposition: form-data; name="image.content_type" application/octet-stream --------------------------767b3fb9efcce66b Content-Disposition: form-data; name="image.path" /Users/liuhu/Desktop/nginxRoot/www/upload/images/0006817569 --------------------------767b3fb9efcce66b Content-Disposition: form-data; name="image.md5" 62519e639e490a69d0106bceb7e0f184 --------------------------767b3fb9efcce66b Content-Disposition: form-data; name="image.size" 176536 --------------------------767b3fb9efcce66b Content-Disposition: form-data; name="sss" sss --------------------------767b3fb9efcce66b--
-
发现参数全部放在了
Content-Disposition
里面,并且参数还是用一个动态的boundary
进行的分割。
-
-
通过
dump
信息发现请求的Content-Type
为multipart/form-data
。因此查了一下Golang
的文档,发现multipart/form-data
需要先将整个请求的body
解析到内存中,才能从Form
中获取到相应的字段。ParseMultipartForm parses a request body as multipart/form-data. The whole request body is parsed and up to a total of maxMemory bytes of its file parts are stored in memory, with the remainder stored on disk in temporary files. ParseMultipartForm calls ParseForm if necessary. After one call to ParseMultipartForm, subsequent calls have no effect.
-
服务端的主要代码如下
if r.Method == "POST" { r.ParseMultipartForm(32 << 20) log.Println(r.Form) //处理图片 //todo w.Write([]byte("上传成功")) }
网友评论