該隨筆記錄了在實際專案中使用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目錄下
啟動服務
我們先啟動服務端,如下圖正常啟動
執行測試類,控制檯輸出如下:
服務端輸出:
檢視是否儲存成功
成功!!
注意事項:
但是在成功之前也遇到了問題,如下:
上傳檔案:
普通引數,中文亂碼: