该随笔记录了在实际项目中使用HttpClient调用外部api,需上传文件和普通参数的代码。
笔者在使用 HttpClient 调用 http api 接口时,需要服务端上传文件和一些普通参数给 http api,如果使用 Java 自带的 HttpURLConnection 请求的话,发送 multipart/form-data + POST 请求会比较麻烦,需要设置一些边界(将文件与文件、文件与普通参数之间隔开,便于接收者截取,这是 http 协议要求的)。
因为上传文件和普通参数时,服务端读取报文是根据边界值来截取的,如果使用原生的 HttpURLConnection 则比较麻烦,所以笔者采用 HttpClient 工具,httpclient是apache 软件基金会下的子项目,它很好的封装了Http工具,面向对象的思想省去了很多细节,使程序员关注与业务逻辑处理,不用关注这些通讯细节。
笔者使用HTTPClient实现文件的上传,使用 MultipartEntityBuilder 构造请求体,实现 multipart/form-data + POST 请求http接口。下面提供了使用时的代码实现,包括服务端和客户端。
不过,笔者在使用的过程中发现,当传递的普通参数有中文时,对方接到的参数会乱码,因为开始笔者使用的是multipartEntity.addTextBody(key, postParam.get(key));的方式设置普通参数。
为了解决乱码问题,最后查到了解决办法,记录如下。
如下代码是可以上传多个文件和普通参数的,使用 multipart/form-data + POST 方式提交,模拟浏览器在页面上 form表单 的提交方式。
客户端上传文件及普通参数代码:
1 /** 2 * httpclient 文件上传 3 * @param postFiles 4 * @param postUrl 5 * @param postParam 6 * @return 7 */ 8 public static Map<String, Object> uploadFileByHttpPost(File[] postFiles, String postUrl, Map<String, String> postParam) { 9 Map<String, Object> resultMap = new HashMap<String, Object>(); 10 CloseableHttpClient httpClient = HttpClients.createDefault(); 11 try { 12 //把一个普通参数和文件上传给下面这个api接口 13 HttpPost httpPost = new HttpPost(postUrl); 14 //设置传输参数,设置编码。设置浏览器兼容模式,解决文件名乱码问题 15 MultipartEntityBuilder multipartEntity = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532); 16 for (int i = 0; i < postFiles.length; i++) { 17 File postFile = postFiles[i]; 18 FileBody fundFileBin = new FileBody(postFile, ContentType.MULTIPART_FORM_DATA); 19 20 //相当于<input type="file" name="media"/> 21 multipartEntity.addPart("upload_file"+i, fundFileBin); 22 } 23 //把文件转换成流对象FileBody 24 Set<String> keySet = postParam.keySet(); 25 for (String key : keySet) { 26 //解决中文乱码 27 ContentType contentType = ContentType.create("text/plain", Charset.forName("UTF-8")); 28 StringBody stringBody = new StringBody(postParam.get(key), contentType); 29 multipartEntity.addPart(key, stringBody); 30 // multipartEntity.addTextBody(key, postParam.get(key));//这个中文会乱码 31 } 32 HttpEntity reqEntity = multipartEntity.build(); 33 httpPost.setEntity(reqEntity); 34 //发起请求 并返回请求的响应 35 CloseableHttpResponse response = httpClient.execute(httpPost); 36 try { 37 //打印响应状态 38 resultMap.put("statusCode", response.getStatusLine().getStatusCode()); 39 //获取响应对象 40 HttpEntity resEntity = response.getEntity(); 41 if (resEntity != null) { 42 //打印响应内容 43 resultMap.put("data", EntityUtils.toString(resEntity, Charset.forName("UTF-8"))); 44 } 45 //销毁 46 EntityUtils.consume(resEntity); 47 } catch (Exception e) { 48 e.printStackTrace(); 49 } finally { 50 response.close(); 51 } 52 } catch (ClientProtocolException e1) { 53 e1.printStackTrace(); 54 } catch (IOException e1) { 55 e1.printStackTrace(); 56 } finally { 57 try { 58 httpClient.close(); 59 } catch (IOException e) { 60 e.printStackTrace(); 61 } 62 } 63 return resultMap; 64 }
服务端处理请求代码
这是客户端上传的代码,我们看一下怎么接收,使用SpringMVC controller层接收文件和普通参数:
1 /** 2 * 上传文件 3 * @throws IOException 4 * @throws IllegalStateException 5 */ 6 @RequestMapping("/postFile") 7 @ResponseBody 8 public String postFile(HttpServletRequest request){ 9 Map<String, Object> map = new HashMap<String, Object>(); 10 // 创建一个通用的多部分解析器 11 CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request 12 .getSession().getServletContext()); 13 String name = request.getParameter("name"); 14 String age = request.getParameter("age"); 15 16 System.out.println(name+","+age); 17 request.getSession().getServletContext(); 18 // 判断 request 是否有文件上传,即多部分请求 19 if (multipartResolver.isMultipart(request)) { 20 // 转换成多部分request 21 MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request; 22 // 取得request中的所有文件名 23 Iterator<String> iter = multiRequest.getFileNames(); 24 while (iter.hasNext()) {//多文件 25 // 取得上传文件 26 MultipartFile multipartFile = multiRequest.getFile(iter.next()); 27 if (null != multipartFile) { 28 // 取得当前上传文件的文件名称 29 String fileName = multipartFile.getOriginalFilename(); 30 if (fileName.trim() != null && fileName.trim().length() > 0) { 31 CommonsMultipartFile cf = (CommonsMultipartFile) multipartFile; 32 DiskFileItem fi = (DiskFileItem) cf.getFileItem(); 33 File tempFile = fi.getStoreLocation(); 34 // 拿到文件,存储 35 try { 36 multipartFile.transferTo(new File("F:\static\page\"+multipartFile.getOriginalFilename())); 37 } catch (IOException e) { 38 e.printStackTrace(); 39 return "error"; 40 } 41 } 42 } 43 } 44 } 45 return "success"; 46 }
测试代码:
1 public static void main(String[] args) { 2 String url = "http://localhost:8080/postFile"; 3 File[] files = new File[2]; 4 files[0] = new File("F:\static\updateFile-demo.docx"); 5 files[1] = new File("F:\static\updateFile-demo02.docx"); 6 7 Map<String,String> param = new HashMap<>(); 8 param.put("name","编程大道"); 9 param.put("age","18"); 10 11 Map<String, Object> stringObjectMap = HttpClientUtil01.uploadFileByHttpPost(files, url, param); 12 System.out.println(stringObjectMap); 13 }
代码验证
我们上传两个文件和两个普通参数,服务端controller里的处理是,打印这两个普通参数并把两个文件保存到page目录下
启动服务
我们先启动服务端,如下图正常启动
运行测试类,控制台输出如下:
服务端输出:
查看是否保存成功
成功!!
注意事项:
但是在成功之前也遇到了问题,如下:
上传文件:
传普通参数,注意中文乱码的问题: