客戶端解析伺服器響應的multipart/form-data資料
multipart/form-data
,多部件請求體。這個請求體比較特殊,它可以拆分為多個部件,每個部件都有自己的header
和body
,最常用的地方就是:客戶端檔案上傳,因為有多個部件,在上傳檔案的時候,還可以在body
中新增其他的資料。json
,form
。。。
一般來說,都是客戶端發起multipart/form-data
請求 ,伺服器進行解析。而且這種東西的編碼解碼工作一般都是由底層的容器/框架完成。開發根本不必關心。但是我最近遇到了一個需求:
伺服器響應
multipart/form-data
(包含了一個二進位制檔案和其他的文字資料),客戶端來解析
意味著,需要自己完成2個東西
- 在服務端完成
multipart/form-data
的資料編碼,並且響應給客戶端 - 在客戶端獲取到響應後,進行資料的解碼
multipart/form-data
的請求體,看起來像這樣(省略了部分 header)
POST /foo HTTP/1.1
Content-Length: 68137
Content-Type: multipart/form-data; boundary=---------------------------974767299852498929531610575
---------------------------974767299852498929531610575
Content-Disposition: form-data; name="description"
some text
---------------------------974767299852498929531610575
Content-Disposition: form-data; name="myFile"; filename="foo.txt"
Content-Type: text/plain
(content of the uploaded file foo.txt)
---------------------------974767299852498929531610575
服務端的編碼
使用 org.apache.httpcomponents
庫進行編碼
<!-- -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.12</version>
</dependency>
Controller
透過 MultipartEntityBuilder
, 新增多個部件,每個部件有自己的名字,型別。構建出一個 HttpEntity
物件。可以從這個物件中獲取到編碼後的IO流以及ContentType
,直接響應給 客戶端就完事兒,比較簡單。
import java.io.File;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriUtils;
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping
public void test (HttpServletResponse response) throws Exception {
HttpEntity httpEntity = MultipartEntityBuilder.create()
// 表單 => (部件名稱,資料,型別),要注意uri編碼
.addPart("name", new StringBody(UriUtils.encode("SpringBoot中文社群", StandardCharsets.UTF_8), ContentType.APPLICATION_FORM_URLENCODED))
// JSON => (部件名稱,JSON,型別)
.addPart("info", new StringBody("{"site": "", "year": 2019}", ContentType.APPLICATION_JSON))
// 檔案 => ( 部件名稱,檔案,型別,檔名稱)
.addBinaryBody("logo", new File("D:\logo.png"), ContentType.IMAGE_PNG, "logo.png")
.build();
// 設定ContentType
response.setContentType(httpEntity.getContentType().getValue());
// 響應客戶端
httpEntity.writeTo(response.getOutputStream());
}
}
客戶端的解碼
使用commons-fileupload
庫進行解碼
<!-- -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
MultipartTest
看這個程式碼,會覺得似曾相識。不錯,在Servlet3.0
以前,HttpServletRequest
還沒有getPart
方法的時候 ,大家都是透過 commons-fileupload
來從multipart/form-data
請求中解析出資料的。
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileItemHeaders;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.portlet.PortletFileUpload;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
/**
* 自己定義一個RequestContext的實現
*/
class SimpleRequestContext implements RequestContext {
private final Charset charset; // 編碼
private final MediaType contentType; // contentType
private final InputStream content; // 資料
public SimpleRequestContext(Charset charset, MediaType contentType, InputStream content) {
this.charset = charset;
this.contentType = contentType;
this.content = content;
}
@Override
public String getCharacterEncoding() {
return this.charset.displayName();
}
@Override
public String getContentType() {
return this.contentType.toString();
}
@Override
public int getContentLength() {
try {
return this.content.available();
} catch (IOException e) {
}
return 0;
}
@Override
public InputStream getInputStream() throws IOException {
return this.content;
}
}
public class MultipartTest {
public static void main(String[] args) throws IOException, FileUploadException {
// 獲取伺服器響應的IO流
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Resource> responseEntity = restTemplate.getForEntity("", Resource.class);
// 建立RequestContext物件
RequestContext requestContext = new SimpleRequestContext(StandardCharsets.UTF_8, responseEntity.getHeaders().getContentType(),
responseEntity.getBody().getInputStream());
// 解析器建立
FileUploadBase fileUploadBase = new PortletFileUpload();
FileItemFactory fileItemFactory = new DiskFileItemFactory();
fileUploadBase.setFileItemFactory(fileItemFactory);
fileUploadBase.setHeaderEncoding(StandardCharsets.UTF_8.displayName());
// 解析出所有的部件
List<FileItem> fileItems = fileUploadBase.parseRequest(requestContext);
for (FileItem fileItem : fileItems) {
// 請求頭
System.out.println("headers:==========================");
FileItemHeaders fileItemHeaders = fileItem.getHeaders();
Iterator<String> headerNamesIterator = fileItemHeaders.getHeaderNames();
while (headerNamesIterator.hasNext()) { // 迭代name
String headerName = headerNamesIterator.next();
Iterator<String> headerValueIterator = fileItemHeaders.getHeaders(headerName);
while (headerValueIterator.hasNext()) { // 迭代value
String headerValue = headerValueIterator.next();
System.out.println(headerName + ":" + headerValue);
}
}
// 請求體
System.out.println("body:==========================");
if(fileItem.isFormField()) { // 是普通表單項
byte[] data = fileItem.get();
System.out.println(new String(data, StandardCharsets.UTF_8));
} else { // 是檔案表單項
String fileName = fileItem.getName(); // 檔案的原始名稱
InputStream inputStream = fileItem.getInputStream(); // 檔案的IO流
System.out.println("fileName=" + fileName + ", size=" + inputStream.available());
}
System.out.println();
}
}
}
完整的日誌輸出
17:18:55.384 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET
17:18:55.449 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json, */*]
17:18:56.426 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
17:18:56.461 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [org.springframework.core.io.Resource] as "multipart/form-data;boundary=0W40KHiHJTyo5H_n1EIL68aM4tNRhPa-7Vp"
headers:==========================
content-disposition:form-data; name="name"
content-type:application/x-www-form-urlencoded; charset=ISO-8859-1
content-transfer-encoding:8bit
body:==========================
SpringBoot%E4%B8%AD%E6%96%87%E7%A4%BE%E5%8C%BA
headers:==========================
content-disposition:form-data; name="info"
content-type:application/json; charset=UTF-8
content-transfer-encoding:8bit
body:==========================
{"site": "", "year": 2019}
headers:==========================
content-disposition:form-data; name="logo"; filename="logo.png"
content-type:image/png
content-transfer-encoding:binary
body:==========================
fileName=logo.png, size=2423
客戶端準確的解析出了伺服器響應的 multipart/form-data
資料。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2471/viewspace-2826119/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PHP multipart/form-data 遠端DOS漏洞PHPORM
- 從零開始實現multipart/form-data資料提交ORM
- 理解HTTP協議中的multipart/form-dataHTTP協議ORM
- Spring Boot響應式客戶端WebClient入門Spring Boot客戶端Webclient
- 如何使用 multiparty 工具庫在 Node.js 應用裡解析 multipart form-data 格式的請求Node.jsORM
- HDFS原始碼解析:教你用HDFS客戶端寫資料原始碼客戶端
- Netcore webapi + 後端多檔案多引數 multipart/form-data 上傳NetCoreWebAPI後端ORM
- Web 應用客戶端渲染和伺服器端渲染的比較Web客戶端伺服器
- 客戶端資料儲存概述客戶端
- socket.io 客戶端與伺服器應用客戶端伺服器
- Easyvision中的伺服器與客戶端伺服器客戶端
- 跟著大彬讀原始碼 - Redis 2 - 伺服器如何響應客戶端請求?(上)原始碼Redis伺服器客戶端
- 跟著大彬讀原始碼 - Redis 3 - 伺服器如何響應客戶端請求?(下)原始碼Redis伺服器客戶端
- 支付寶客戶端架構解析:iOS 客戶端啟動效能優化初探客戶端架構iOS優化
- 《球球大作戰》原始碼解析:伺服器與客戶端架構原始碼伺服器客戶端架構
- 實現伺服器和客戶端資料互動,Java Socket有妙招伺服器客戶端Java
- 自適應服務端渲染(服務端根據客戶端環境自適應地響應首屏)服務端客戶端
- 專案資料視覺化對甲方客戶的影響視覺化
- Redis 6.0 客戶端快取的伺服器端實現Redis客戶端快取伺服器
- 支付寶客戶端架構解析:Android 客戶端啟動速度優化之「垃圾回收」客戶端架構Android優化
- MQTT伺服器搭建服務端和客戶端MQQT伺服器服務端客戶端
- 從客戶端到伺服器再到資料庫的一些思考與疑問客戶端伺服器資料庫
- SQLPro Studio Mac資料庫管理客戶端工具SQLMac資料庫客戶端
- 客戶端 post ,get 訪問伺服器客戶端伺服器
- CRM系統資料庫是如何影響客戶體驗的?資料庫
- Block披露其影響820萬客戶的資料洩露事件BloC事件
- Linux下簡單的ACE socket客戶端和伺服器端Linux客戶端伺服器
- vue響應式資料的實現原理解析Vue
- 表單 x-www-form-urlencoded 與 multipart/form-data 區別ORM
- Web端與Client客戶端資料互動方案選擇Webclient客戶端
- 在Qt5中使用Http Rest客戶端請求並解析Json資料QTHTTPREST客戶端JSON
- AJAX 獲取伺服器響應資料伺服器
- 超越 Cookie:當今的客戶端資料儲存技術Cookie客戶端
- 主流資料庫和 NoSQL 的 Rust 客戶端驅動程式資料庫SQLRust客戶端
- 雲時代的資料庫客戶端 —— CloudQuery最佳實踐資料庫客戶端Cloud
- UE 客戶端和伺服器上的時間同步客戶端伺服器
- Ubuntu 16.04下安裝資料庫Oracle客戶端Ubuntu資料庫Oracle客戶端
- JetBrains DataGrip 2021 for Mac(資料庫客戶端軟體)AIMac資料庫客戶端