最近在做向服务器提交文件,本来用的xutils,使用起来挺简单,代码超不过10行,但是想深入了解一下原理,所以就自己用HttpURLConnection实现文件的上传。
无论是浏览器通过表单提交文件,还是APP通过post提交,其实原理都是一样的。APP只要仿照浏览器的表单提交数据的样式去提交文件,服务器就可以解析并处理文件。
那就首先来看一下浏览器提交的数据样式和请求头的信息。
HTML核心代码:
请求的头部信息:
host:192.168.218.163:8080
connection:keep-alive
content-length:432
cache-control:max-age=0
origin:null
upgrade-insecure-requests:1
user-agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
content-type:multipart/form-data; boundary=----WebKitFormBoundarymYCoIxqCv49mE4Ok
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-encoding:gzip, deflate
accept-language:zh-CN,zh;q=0.8
cookie:__guid=158737658.212612550479137800.1501464417829.6194; monitor_count=15
头部信息的content-type是重点,如果是上传文件,表单的mime类型必须是multipart/form-data,“boundary”是用来隔开表单中不同部分数据的,它是由- -开头,而不是boundary数据开头,以- -结尾。
服务器获取到的数据:
------WebKitFormBoundarynug1Tk0jD0SCH33m
Content-Disposition: form-data; name="file"; filename="nuuid.ini"
Content-Type: application/octet-stream
##这是上传文件的数据##
------WebKitFormBoundarynug1Tk0jD0SCH33m--
上传文件,post出去的数据是比较严格的。数据必须以--boundary开头
每一条数据独占一行,内容描述信息样式:
Content-Disposition: form-data; name="file"; filename="nuuid.ini"
Content-Type: application/octet-stream
然后空一个空行(/r/n),再是具体的文件数据。然后- -boundary- -结尾。
下面开始写Java代码:
String uuid = UUID.randomUUID().toString();
String BOUNDARY = uuid;
String NewLine = "\r\n";
String spec = "http://192.168.218.163:8080/Test/FileUpLoad";
File file = new File("G:\\mv\\哥德巴赫猜想.txt");
FileInputStream fis=null;
DataOutputStream bos =null;
DataInputStream bis=null;
URL url = new URL(spec);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//打开输出
connection.setDoOutput(true);
//打开输入
connection.setDoInput(true);
//关闭缓存
connection.setUseCaches(false);
//读取超时
connection.setReadTimeout(50*1000);
//连接超时
connection.setConnectTimeout(5*1000);
//请求方式POST
connection.setRequestMethod("POST");
//设置请求头
// connection.setRequestProperty("Connection", "Keep-Alive");
//connection.addRequestProperty("user-agent","Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; NX507J Build/KVT49L) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1");
//必须设置,数据类型,编码方式,分界线
connection.setRequestProperty("Content-Type", "multipart/form-data; charset=utf-8; boundary="+BOUNDARY);
//connection.setRequestProperty("accept-encoding","gzip");
connection.setChunkedStreamingMode(1024 * 50);
bos = new DataOutputStream(connection.getOutputStream());
if (file.exists()) {
fis = new FileInputStream(file);
byte[]buff = new byte[1024];
bis = new DataInputStream(fis);
int cnt=0;
//数据以--BOUNDARY开始
bos.write(("--"+BOUNDARY).getBytes());
//换行
bos.write(NewLine.getBytes());
//内容描述信息
content = "Content-Disposition: form-data; name=\""+filename+"\"; filename=\""+file.getName()+"\"";
bos.write(content.getBytes());
bos.write(NewLine.getBytes());
// bos.write("Content-Type: application/octet-stream".getBytes());
// bos.write(NewLine.getBytes());
// bos.write("Content-Transfer-Encoding: binary".getBytes());
// bos.write(NewLine.getBytes());
bos.write(NewLine.getBytes());
//空一行后,开始通过流传输文件数据
while((cnt=bis.read(buff))!=-1){
bos.write(buff,0,cnt);
}
bos.write(NewLine.getBytes());
//结束标志--BOUNDARY--
bos.write(("--"+BOUNDARY+"--").getBytes());
bos.write(NewLine.getBytes());
bos.flush();
}
//开始发送请求,获取请求码和请求结果
if (connection.getResponseCode()==HttpURLConnection.HTTP_OK) {
connection.getInputStream();
System.out.println("url="+connection.getURL());
BufferedReader read = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
String dat=null;
while ((dat =read.readLine())!=null){
System.out.println(dat);
}
System.out.println("请求成功");;
}else {
System.err.println("请求失败"+connection.getResponseMessage()+"code="+connection.getResponseCode());
}
请求体的内容:
--32eeb4fb-7fe9-4fa3-a5bf-ec357c48a5d4
Content-Disposition: form-data; name="file"
Content-Type: text/plain; charset=utf-8
文本文件
--32eeb4fb-7fe9-4fa3-a5bf-ec357c48a5d4
Content-Disposition: form-data; name="哥德巴赫猜想.txt"; filename="哥德巴赫猜想.txt"
##上传文件的内容(文本或二进制)
--32eeb4fb-7fe9-4fa3-a5bf-ec357c48a5d4--
注:
1.post到服务器的数据格式要求严格,以--BOUNDARY开头,紧跟着是内容描述信息(Content-Disposition: form-data; name="filename"; filename=" "),然后空一行+内容正 文,--BOUNDARY--结尾。
2.注释的代码,是不必须的参数。
3.如果数据格式不按固定格式传输(描述信息和内容正文之间空一行),服务器在处理数据时会报Stream ended unexpectedly异常(服务器用的Apache的 FileUpload组件)