花了几天时间,搞了一下远程文件传输的事儿:都是用url传流的方式进行传输
一、发送文件有两种方式,都是用表单的方式进行post请求,既可以传输文件,也可以传输键值对。
第一种:原始的直接用stringbuffer拼接需要传的数据
public String upLoadFilePost(String actionUrl, Map<String, List<File>> files, Map<String, String> textMap) throws IOException {
String BOUNDARY = "WebKitFormBoundary"+java.util.UUID.randomUUID().toString();
String PREFIX = "--", LINEND = "\r\n";
String MULTIPART_FROM_DATA = "multipart/form-data";
String CHARSET = "UTF-8";
URL uri = new URL(actionUrl);
HttpURLConnection conn = (HttpURLConnection) uri.openConnection();
conn.setReadTimeout(5 * 1000);
conn.setDoInput(true);// 允许输入
conn.setDoOutput(true);// 允许输出
conn.setUseCaches(false);
conn.setRequestMethod("POST"); // Post方式
conn.setRequestProperty("connection", "keep-alive");
conn.setRequestProperty("Charsert", "UTF-8");
conn.setRequestProperty("Content-Type", MULTIPART_FROM_DATA
+ ";boundary=" + BOUNDARY);
DataOutputStream outStream = new DataOutputStream(
conn.getOutputStream());
if (textMap != null) {
StringBuffer strBuf = new StringBuffer();
Iterator iter = textMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
String inputValue = (String) entry.getValue();
if (inputValue == null) {
continue;
}
strBuf.append(LINEND).append(PREFIX).append(BOUNDARY).append(LINEND);
strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"").append(LINEND).append(LINEND);
strBuf.append(inputValue);
strBuf.append(LINEND);
}
outStream.write(strBuf.toString().getBytes());
}
// 发送文件数据
if (files != null)
for (Map.Entry<String, List<File>> filelist : files.entrySet()) {
for(int i=0;i<filelist.getValue().size();i++){
StringBuilder sb1 = new StringBuilder();
sb1.append(PREFIX);
sb1.append(BOUNDARY);
sb1.append(LINEND);
sb1.append("Content-Disposition: form-data; name=\""+filelist.getKey()+"_"+i+"\"; filename=\""
+ filelist.getValue().get(i).getName() + "\"" + LINEND);
sb1.append("Content-Type: application/octet-stream; charset="
+ CHARSET + LINEND);
sb1.append(LINEND);
outStream.write(sb1.toString().getBytes());
InputStream is = new FileInputStream(filelist.getValue().get(i));
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
is.close();
outStream.write(LINEND.getBytes());
}
}
// 请求结束标志
byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINEND).getBytes();
outStream.write(end_data);
outStream.flush();
// 得到响应码
int res = conn.getResponseCode();
if (res == 200) {
InputStream in = conn.getInputStream();
InputStreamReader isReader = new InputStreamReader(in);
BufferedReader bufReader = new BufferedReader(isReader);
String line = "";
String data = "";
while ((line = bufReader.readLine()) != null) {
data += line;
}
outStream.close();
conn.disconnect();
return data;
}
outStream.close();
conn.disconnect();
return null;
}
form表单里键值对的拼接结果示例
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="textkey"
tttttt
form表单里文件的拼接结果示例
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="filekey"; filename=""
Content-Type:
第二种:使用httpclient,需要下载httpclient-4.5.jar、httpmime-4.5.jar两个包,下载地址为:http://hc.apache.org/downloads.cgi 大致看了下源码,其实也是使用的上述方法中的字符拼接,只是封装后使用起来更简洁方便一些。
这种方法需要注意的点:
1.HttpClient4.3版本往后,原来的MultipartEntity过时不建议再使用了,替换成新的httpmime下面的MultipartEntityBuilder
2.multipartEntity 最好设置contentType和boundary,不然会导致后面接收文件时的第一种方法接收不到文件报错。
3.使用addBinaryBody的坑:addBinaryBody 有6种不同用法,一般都是如下代码所示上传File即可,但是当传byte[]字节数组时,必须使用4参传值,且第四参必须带后缀名,例如:entityBuilder.addBinaryBody("file",new byte[]{},ContentType.DEFAULT_BINARY,"fileName.jpg");否则会导致接收文件时getFileNames()为空。
4.不使用addBinaryBody,也可以直接使用addPart,先把文件转换成FileBody就可以了。
public static Map<String,Object> uploadFileByHTTP(Map<String, List<File>> files,String postUrl,Map<String,String> postParam){
String BOUNDARY = java.util.UUID.randomUUID().toString();
Map<String,Object> resultMap = new HashMap<String,Object>();
CloseableHttpClient httpClient = HttpClients.createDefault();
try{
//把一个普通参数和文件上传给下面这个地址 是一个servlet
HttpPost httpPost = new HttpPost(postUrl);
//设置传输参数
MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create();
multipartEntity.setCharset(Charset.forName("utf-8"));
multipartEntity.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
multipartEntity.setContentType(ContentType.MULTIPART_FORM_DATA);
multipartEntity.setBoundary(BOUNDARY);
//文件参数
for (Map.Entry<String, List<File>> filelist : files.entrySet()) {
for(int i=0;i<filelist.getValue().size();i++){
//把文件转换成流对象FileBody
FileBody fundFileBin = new FileBody(filelist.getValue().get(i));
// multipartEntity.addPart("file", fundFileBin);//相当于<input type="file" name="media"/>
multipartEntity.addBinaryBody("file", filelist.getValue().get(i));
}
}
//键值对参数
Set<String> keySet = postParam.keySet();
for (String key : keySet) {
//相当于<input type="text" name="name" value=name>
multipartEntity.addPart(key, new StringBody(postParam.get(key), ContentType.create("application/json", Consts.UTF_8)));
}
HttpEntity reqEntity = multipartEntity.build();
httpPost.setEntity(reqEntity);
//log.info("发起请求的页面地址 " + httpPost.getRequestLine());
//发起请求 并返回请求的响应
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
///log.info("----------------------------------------");
//打印响应状态
//log.info(response.getStatusLine());
resultMap.put("statusCode", response.getStatusLine().getStatusCode());
//获取响应对象
HttpEntity resEntity = response.getEntity();
if (resEntity != null) {
//打印响应长度
//log.info("Response content length: " + resEntity.getContentLength());
//打印响应内容
resultMap.put("data", EntityUtils.toString(resEntity,Charset.forName("UTF-8")));
}
//销毁
EntityUtils.consume(resEntity);
} catch (Exception e) {
e.printStackTrace();
} finally {
response.close();
}
} catch (ClientProtocolException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} finally{
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//log.info("uploadFileByHTTP result:"+resultMap);
return resultMap;
}
解释一下发送文件中的几点:
1.contentType使用【multipart/form-data】:它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件,
2.boundary:请求体中的boundary参数指定的就是分隔体,请求参数之间需要通过分隔体分隔,不然就会被识别成一个参数。
二、接收文件也有两种方式
第一种:使用MultipartHttpServletRequest
报错问题1:xxxRequest cannot be cast to MultipartHttpServletRequest异常
好多文章里都是直接将request强转为MultipartHttpServletRequest,我这里反正是强转不了就会报这个错,使用如下方式则没有问题。
报错问题2:the request was rejected because no multipart boundary was found
就是在发送文件时未设置contentType和boundary导致,所以记得一定要设置奥
报错问题3:request.getParameter("name")的值为null
因为设置了【multipart/form-data】,这个表单是基于流的方式提交的,所以需要下载一个jar包,地址如下:http://www.servlets.com/cos/index.html ,再使用multiRequest.getParameter("name")就可以获取到了。
public void doPost(HttpServletRequest requestold,HttpServletResponse response) {
String result = "ok";
// 将当前上下文初始化给 CommonsMutipartResolver (多部分解析器)
MultipartResolver resolver = new CommonsMultipartResolver(requestold.getSession().getServletContext());
// 检查form中是否有enctype="multipart/form-data"
if (resolver.isMultipart(requestold)) {
MultipartHttpServletRequest multiRequest = resolver.resolveMultipart(requestold);
//解析request,将结果放置在list中
String value = multiRequest.getParameter("name");
System.out.println("-----"+value+"---------");
Map<String, List<MultipartFile>> fileMap = multiRequest.getMultiFileMap();
for (String key : fileMap.keySet()) {
List<MultipartFile> files = fileMap.get(key);
for (MultipartFile file : files) {
if (!file.isEmpty()) {
String fileName = file.getOriginalFilename();
String columnName = file.getName();
// 文件保存路径
String filePath = "d:\\download";
File iFile = new File(filePath);
File iFileParent = iFile.getParentFile();
if(!iFileParent.exists()){
iFileParent.mkdirs();
}
// 转存文件
try {
file.transferTo(new File(filePath));
} catch (Exception e) {
result = "error";
log.error("转存文件失败!params:{columnName:"+columnName+",fileName:"+fileName+"}",e);
e.printStackTrace();
}
}else{
result = "error";
log.info("file.isEmpty!");
}
}
}
}
try {
ServletOutputStream out = response.getOutputStream();
out.write(result.getBytes());
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
log.info("return value error!");
}
}
第二种:使用DiskFileItemFactory
注意点:
1.jdk1.7以上使用,否则item.getFieldName()会报错
2.该方法通过item.isFormField()判断是否是文件,如果不是文件,就是键值对参数
3.使用stingbuffer拼接发送参数时参数末尾记得加上【\r\n】,否则会将参数视为同一个,我之前就是在传完键值对参数后忘了加,导致最后一个键值对参数和第一个文件被视为同一个参数,当成键值对参数直接输出二进制流了。
public void doPost(HttpServletRequest requestold, HttpServletResponse response) {
RequestAction request = RequestUtils.getRequestAction(requestold);
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List items = null;
try {
items = upload.parseRequest(requestold);
} catch (FileUploadException e) {
e.printStackTrace();
}
Iterator iter = items.iterator();
String picUrl = null;
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
if(item.isFormField()){//判断是非文件上传
String value;
try {
value = item.getString("UTF-8");
System.out.println(item.getFieldName()+":"+value);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
//得到文件保存路径
String sysPath = request.getSession().getServletContext().getRealPath("/resources/images/article/");
//得到上传问价的文件名 test.jpg
String filename = item.getName();
item.getFieldName();
if (null == filename || filename == "") {
filename = picUrl;
}
sysPath = sysPath + filename;
System.out.println(item.getFieldName()+":"+sysPath);
File saveFile = new File(sysPath);
File iFileParent = saveFile.getParentFile();
if(!iFileParent.exists()){
iFileParent.mkdirs();
}
try {
item.write(saveFile);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}