MIME的全稱是Multipurpose Internet Mail Extensions,即多用途互聯網郵件擴展,儘管讀起來有些拗口,但大多數人可能都知道, 這是HTTP協議中用來定義文檔性質及格式的標準。IETF RFC 6838,對HTTP傳輸內容類型進行了全面定義。 而IANA(互聯網號碼分配機構)是負責管理全部標準MIME類型的官方機構。能夠在這裏)找到全部的標準MIMEjavascript
服務器經過MIME告知響應內容類型,而瀏覽器則經過MIME類型來肯定如何處理文檔; 所以爲傳輸內容(文檔、圖片等)設置正確的MIME很是重要。css
一般Server會在HTTP響應中設置Content-Type,以下面的響應:html
HTTP/1.1 200 OK Server: Golfe2 Content-Length: 233 Content-Type: application/html Date: Sun, 28 Dec 2018 02:55:19 GMT
這表示服務端將返回html格式的文檔,而一樣客戶端也能夠在HTTP請求中設置Content-Type以告知服務器當前所發送內容的格式。 以下面的請求體:java
POST / HTTP/1.1 Host: localhost:8000 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Connection: keep-alive Content-Type: application/json Content-Length: 465
這表示客戶端會發送application/json格式的數據到服務端,同時應該注意到Accept請求頭,這個選項用於告知服務器應該返回什麼樣的數據格式(由客戶端接收並完成解析)。git
MIME的格式github
type/subtype
這是一個兩級的分類,比較容易理解,第一級分類一般包含:web
類型 | 描述 |
---|---|
text | 普通文本 |
image | 某種圖像 |
audio | 某種音頻文件 |
video | 某種視頻文件 |
application | 應用數據 |
multi-part | 複合內容 |
而二級類型則很是多,如下是一些經常使用的MIME:spring
MIME | 描述 |
---|---|
audio/wav | wave音頻流媒體文件 |
audio/webm | webm 音頻文件格式 |
audio/ogg | ogg多媒體文件格式的音頻文件 |
audio/mpeg | mpeg多媒體文件格式的音頻文件 |
image/gif | gif圖片 |
image/jpeg | jpeg圖片 |
image/png | png圖片 |
image/svg+xml | svg矢量圖片 |
application/json | json格式 |
application/xml | xml格式 |
application/xhtml+xml | 擴展html格式 |
application/x-www-form-urlencoded | 表單url內容編碼 |
application/octet-stream | 二進制格式 |
application/pdf | pdf文檔 |
application/atom+xml | atom訂閱feed流 |
multipart/form-data | 多文檔格式 |
text/plain | 普通文本 |
text/html | html文檔 |
text/css | css文件 |
text/javascript | javascript文件 |
text/markdown | markdown文檔 |
video/mpeg | mpeg多媒體視頻文件 |
video/quicktime | mov多媒體視頻文件 |
接下來,看看springboot如何實現幾個常見類型格式的處理。json
先看看這樣一段代碼:瀏覽器
@ResponseBody @PostMapping(value = "/json", consumes= { MediaType.APPLICATION_JSON_UTF8_VALUE }, produces="application/json;charset=UTF-8") public Map<String, Object> jsonIO(@RequestBody Map<String, Object> jsonData) { Map<String, Object> resultData = new HashMap<>(jsonData); resultData.put("resultCode", UUID.randomUUID().toString()); return resultData; }
這是一個Controller層的方法定義,其中@PostMapping將該方法映射到***/json***路徑的POST方法。
經過觀察請求響應,咱們會獲得如下的結果:
====> Request: Content-Type=application/json; { "key": "value" } ====> Response: Content-Type=application/json;charset=UTF-8 { "resultCode": "1ec407e1-d753-4439-b31c-bb7e888aa6a2", "key": "value" }
使用Postman工具進行調試,能夠很是直觀的得到想要的信息,點擊這裏能夠下載
異常狀況 若是,請求的內容格式不是json,而是其餘的如application/x-www-form-urlencoded呢? 放心,框架會返回以下面的錯誤:
{ "timestamp": 1530626924715, "status": 415, "error": "Unsupported Media Type", "exception": "org.springframework.web.HttpMediaTypeNotSupportedException", "message": "Content type 'application/x-www-form-urlencoded' not supported", "path": "/content/json" }
如上,經過springboot框架,咱們快速實現了Json格式的輸入輸出。 那麼,如何實現xml格式的處理呢?xml格式主要用於soap、rpc等領域,爲了實現xml數據的序列化,咱們須要添加jackson-xml依賴包
<!-- support for xml bean --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.8.6</version> </dependency>
接下來,聲明一個Controller方法
@PostMapping(value = "/xml", consumes = { MediaType.APPLICATION_XML_VALUE }, produces = MediaType.APPLICATION_XML_VALUE) @ResponseBody public ParamData xmlIO(@RequestBody ParamData data) { data.setAge(data.getAge() + 1); return data; }
此次,咱們指定了consumes、produces都是application/xml,經過@RequestBody、@ResponseBody註解以後, springboot框架會自動根據需求的內容格式進行轉換。
這裏的ParamData是一個簡單的Pojo類:
public static class ParamData { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
經過真實的請求-響應觀測,咱們獲得以下的結果:
====> Request: Content-Type=application/xml; <ParamData> <name>Jim</name> <age>1</age> </ParamData> ====> Response: Content-Type=application/xml;charset=UTF-8 <ParamData> <name>Jim</name> <age>2</age> </ParamData>
BTW,springboot 完成自動類型轉換是經過內容協商實現的,相關的接口爲ContentNegotiationManager。 默認狀況下,對於聲明瞭consumes及produce屬性的方法,會按照聲明的值進行處理,不然格式的轉換會根據請求中的Content-Type、Accept頭部來進行判斷。 此外,實現請求/響應內容到DTO轉換功能的是HttpMessageConverter接口。
準確說,內容轉換是由springmvc框架提供,而springboot是一個整合模塊的腳手架
對於普通的表單請求參數處理,咱們一般有兩種方式:
@PostMapping(value = "/form", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String form(@RequestParam("name") String name, @RequestParam("age") int age) { return String.format("Welcome %s, you are %d years old", name, age); }
@PostMapping(value = "/form1", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String form1(ParamData data) { return String.format("Welcome %s, you are %d years old. Bye", data.getName(), data.getAge()); }
form表單的請求內容格式爲application/x-www-form-urlencoded, 一個請求的樣例以下:
====>Request: Content-Length →40 Content-Type →text/plain;charset=UTF-8 Date →Mon, 16 Jul 2018 13:50:14 GMT name=Lilei age=11 ====>Response: Content-Length →40 Content-Type →text/plain;charset=UTF-8 Date →Mon, 16 Jul 2018 13:50:14 GMT Welcome Lilei, you are 11 years old. Bye
對於文件上傳,咱們須要將請求聲明爲multipart/form-data格式,一個文件上傳的請求樣例以下:
POST / HTTP/1.1 Host: localhost:8000 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Upgrade-Insecure-Requests: 1 Content-Type: multipart/form-data; boundary=---------------------------8721656041911415653955004498 Content-Length: 465 -----------------------------8721656041911415653955004498 Content-Disposition: form-data; name="name" Test -----------------------------8721656041911415653955004498 Content-Disposition: form-data; name="file"; filename="flower.jpg" Content-Type: image/jpeg .... -----------------------------8721656041911415653955004498--
參照如下的代碼能夠實現簡單的文件上傳處理:
@PostMapping(value = "file", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String file(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) { logger.info("file receive {}", name); if (file.isEmpty()) { return "No File"; } String fileName = file.getOriginalFilename(); File root = new File("D:/temp"); if (!root.isDirectory()) { root.mkdirs(); } try { file.transferTo(new File(root, name)); return String.format("Upload to %s", fileName); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return "Upload Failed"; }
這個例子很是簡單,經過聲明***@RequestParam註解得到MultipartFile*** 對象,在得到上傳文件後存儲到服務器本地目錄。 固然,在真實的項目應用中你須要作的更多,好比文件的大小、類型校驗,將文件進行壓縮或將文件存放到大容量、高穩定性的分佈式文件存儲系統等等。
這裏很少囉嗦了,關於文件下載,能夠經過如下的方法實現:
@GetMapping(path = "/download") public ResponseEntity<Resource> download(@RequestParam("name") String name) throws IOException { File file = new File("D:/temp", name); Path path = Paths.get(file.getAbsolutePath()); ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path)); return ResponseEntity.ok().header("Content-Disposition", "attachment;fileName=" + name) .contentLength(file.length()).contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource); }
聰明的讀者必定會發現,除了將文件內容做爲輸出以外,咱們還爲響應添加兩個header:
在某些狀況下,你可能須要得到原始的請求字節流,好比實現內容的過濾,或者爲了完成製做本身的RPC接口。 在springboot中得到字節流很是簡單,從Servlet API的定義中能夠發現,直接經過HttpServletRequest對象即可以獲取一個InputStream。
在咱們定義的Controller方法中,還能夠直接聲明流類型的參數以獲取數據。
@PostMapping(value = "/data", produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String rawIO(InputStream dataStream) throws Exception { return IOUtils.toString(dataStream, "UTF-8"); }
然而,若是這麼作了,你可能會遇到一些麻煩: 當請求頭中Content-Type=application/x-www-form-urlencoded 時,你會得到一個空的InputStream!
筆者曾經在製做代理服務器的時候遇到了這個問題,通過一番查閱,發現問題的緣由在於:
按照Servlet規範,若是同時知足下列條件,則請求體(Entity)中的表單數據,將被填充到request的parameter集合中(致使inputstream爲空)。 1 這是一個HTTP/HTTPS請求 2 請求方法是POST 3 請求的類型Content-Type=application/x-www-form-urlencoded 4 Servlet調用了getParameter系列方法
springboot框架內置了HiddenHttpMethodFilter,用於支持瀏覽器form表單沒法支持put/delete等請求方法的問題。
在Filter的實現中發現存在以下代碼:
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) { String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { requestToUse = new HttpMethodRequestWrapper(request, paramValue); } }
因爲getParameter被提早調用,致使後續獲取InputStream爲空。 該問題的解決方法是實現HttpServletRequest的代理,事先將InputStream保存起來供屢次使用,經過高優先級的過濾器提早將Request對象置換可達到目的。 因爲篇幅限制這裏不作展開。感興趣的能夠參考這裏得到更多信息。
mozilla開發手冊-MIME springboot-requestmapping usage JavaServlet3.1規範筆記 ServletRequest-InputStream屢次獲取
HTTP協議中定義了MIME標準,以實現傳輸內容格式的識別及轉換。 本文介紹了常見的MIME類型,並結合springboot框架的代碼樣例,講述如何完成Json/xml/字節流等常見類型的內容處理。 對於Http參數、文件的上傳下載提供了簡單代碼示例,讀者在充分了解用法以後能夠進一步完善,並應用到實際的項目中去。 最後,歡迎繼續關注"美碼師的補習系列-springboot篇" ,期待更多精彩內容^-^