在常見業(yè)務開發(fā)中,POST請求常常在這些地方使用:前端表單提交時、調(diào)用接口代碼時和使用Postman測試接口時。我們下面來一一了解:
一、前端表單提交時
application/x-www-form-urlencoded
表單代碼:
<form action="http://localhost:8888/task/" method="POST">
First name: <input type="text" name="firstName" value="Mickey&"><br>
Last name: <input type="text" name="lastName" value="Mouse "><br>
<input type="submit" value="提交">
</form>
通過測試發(fā)現(xiàn)可以正常訪問接口,在Chrome的開發(fā)者工具中可以看出,表單上傳編碼格式為application/x-www-form-urlencoded(Request Headers中),參數(shù)的格式為key=value&key=value。

我們可以看出,服務器知道參數(shù)用符號&間隔,如果參數(shù)值中需要&,則必須對其進行編碼。編碼格式就是application/x-www-form-urlencoded(將鍵值對的參數(shù)用&連接起來,如果有空格,將空格轉換為+加號;有特殊符號,將特殊符號轉換為ASCII HEX值)。
application/x-www-form-urlencoded是瀏覽器默認的編碼格式。對于Get請求,是將參數(shù)轉換?key=value&key=value格式,連接到url后
ps:可以在這個網(wǎng)址測試表單:http://www.runoob.com/try/try.php?filename=tryhtml_form_submit
multipart/form-data
那么當服務器使用multipart/form-data接收POST請求時,服務器怎么知道每個參數(shù)的開始位置和結束位置呢?
<form action="http://localhost:8888/task/" method="POST" enctype="multipart/form-data">
First name: <input type="text" name="firstName" value="Mickey&"><br>
Last name: <input type="text" name="lastName" value="Mouse "><br>
<input type="submit" value="提交">
</form>
我們在開發(fā)者工具中可以看出multipart/form-data不會對參數(shù)編碼,使用的boundary(分割線),相當于&,boundary的值是----Web**AJv3。

文件上傳
上傳文件也要指定編碼格式為multipart/form-data。
<form action="http://localhost:8888/testFile" enctype="multipart/form-data" method="POST">
<input type="file" name="file">
<input type="submit" value="提交">
</form>
如果是SpringMVC項目,要服務器能接受multipart/form-data類型參數(shù),還要在spring上下文配置以下內(nèi)容,SpringBoot項目則不需要。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"></property>
</bean>
我們可以通過FormData對象模擬表單提交,用原始的XMLHttpRequest來發(fā)送數(shù)據(jù),讓我們可以在Chrome開發(fā)工具中查看到具體格式:
<form id="form">
First name: <input type="text" name="firstName" value="Mickey"><br>
Last name: <input type="text" name="lastName" value="Mouse"><br>
<input type="file" name="file"><br>
</form>
<button onclick="submitForm()">提交</button>
<script>
function submitForm() {
var formElement = document.getElementById("form");
var xhr = new XMLHttpRequest();
xhr.open("POST", "/task/testFile");
xhr.send(new FormData(formElement));
}
</script>
格式如下:

二、調(diào)用接口代碼時
1、在代碼中使用application/x-www-form-urlencoded編碼格式設置Request屬性調(diào)用接口,可以如下實現(xiàn):
private static String doPost(String strUrl, String content) {
String result = "";
try {
URL url = new URL(strUrl);
//通過調(diào)用url.openConnection()來獲得一個新的URLConnection對象,并且將其結果強制轉換為HttpURLConnection.
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
//設置連接的超時值為30000毫秒,超時將拋出SocketTimeoutException異常
urlConnection.setConnectTimeout(30000);
//設置讀取的超時值為30000毫秒,超時將拋出SocketTimeoutException異常
urlConnection.setReadTimeout(30000);
//將url連接用于輸出,這樣才能使用getOutputStream()。getOutputStream()返回的輸出流用于傳輸數(shù)據(jù)
urlConnection.setDoOutput(true);
//設置通用請求屬性為默認瀏覽器編碼類型
urlConnection.setRequestProperty("content-type", "application/x-www-form-urlencoded");
//getOutputStream()返回的輸出流,用于寫入?yún)?shù)數(shù)據(jù)。
OutputStream outputStream = urlConnection.getOutputStream();
outputStream.write(content.getBytes());
outputStream.flush();
outputStream.close();
//此時將調(diào)用接口方法。getInputStream()返回的輸入流可以讀取返回的數(shù)據(jù)。
InputStream inputStream = urlConnection.getInputStream();
byte[] data = new byte[1024];
StringBuilder sb = new StringBuilder();
//inputStream每次就會將讀取1024個byte到data中,當inputSteam中沒有數(shù)據(jù)時,inputStream.read(data)值為-1
while (inputStream.read(data) != -1) {
String s = new String(data, Charset.forName("utf-8"));
sb.append(s);
}
result = sb.toString();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
String str = doPost("http://localhost:8888/task/", "firstName=Mickey%26&lastName=Mouse ");
System.out.println(str);
}
2、在代碼中使用multipart/form-data編碼格式設置Request屬性調(diào)用接口時,其中boundary的值可以在設置Content-Type時指定,讓服務器知道如何拆分它接受的參數(shù)。通過以下代碼的調(diào)用接口:
private static String doPost(String strUrl, Map<String, String> params, String boundary) {
String result = "";
try {
URL url = new URL(strUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setConnectTimeout(30000);
urlConnection.setReadTimeout(30000);
urlConnection.setDoOutput(true);
//設置通用請求屬性為multipart/form-data
urlConnection.setRequestProperty("content-type", "multipart/form-data;boundary=" + boundary);
DataOutputStream dataOutputStream = new DataOutputStream(urlConnection.getOutputStream());
for (String key : params.keySet()) {
String value = params.get(key);
//注意!此處是\r(回車:將當前位置移到本行開頭)、\n(換行:將當前位置移到下行開頭)要一起使用
dataOutputStream.writeBytes("--" + boundary + "\r\n");
dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + encode(key) + "\"\r\n");
dataOutputStream.writeBytes("\r\n");
dataOutputStream.writeBytes(encode(value) + "\r\n");
}
//最后一個分隔符的結尾后面要跟"--"
dataOutputStream.writeBytes("--" + boundary + "--");
dataOutputStream.flush();
dataOutputStream.close();
InputStream inputStream = urlConnection.getInputStream();
byte[] data = new byte[1024];
StringBuilder sb = new StringBuilder();
while (inputStream.read(data) != -1) {
String s = new String(data, Charset.forName("utf-8"));
sb.append(s);
}
result = sb.toString();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private static String encode(String value) throws UnsupportedEncodingException {
return URLEncoder.encode(value, "UTF-8");
}
public static void main(String[] args) {
Map<String, String> params = new HashMap<>();
params.put("firstName", "Mickey");
params.put("lastName", "Mouse");
//自定義boundary,有兩個要求:使用不會出現(xiàn)在發(fā)送到服務器的HTTP數(shù)據(jù)中的值;并在請求消息中的分割位置都使用相同的值
String boundary = "abcdefg";
String str = doPost("http://localhost:8888/testFile", params, boundary);
System.out.println(str);
}
通過debug,可以看出dataOutputStream的值如下:

三、使用Postman測試接口時
1、POST請求 -> Body -> x-www-form-urlencoded

當切換為x-www-form-urlencoded時,Headers會自動添加Content-Type:application/x-www-form-urlencoded

當請求Send后,此時點Code,可以查看到和Chrome開發(fā)工具中(Request Headers處的Content-Type和Form Data)一樣的數(shù)據(jù)

2、POST請求 -> Body -> form-data
相當于html表單請求,value可為Text或文件。

可以不用手動指定編碼格式,也可以指定編碼為multipart/form-data

劃線處的分割線應該是被省略了。

可以更改左上角的類型,來查看相應的Headers代碼,常見的是下面三種:
Java OK HTTP:

JavaScript Jquery AJAX:

JavaScript XHR:

總結
POST請求的兩種編碼格式:application/x-www-urlencoded是瀏覽器默認的編碼格式,用于鍵值對參數(shù),參數(shù)之間用&間隔;multipart/form-data常用于文件等二進制,也可用于鍵值對參數(shù),最后連接成一串字符傳輸(參考Java OK HTTP)。除了這兩個編碼格式,還有application/json也經(jīng)常使用。
接口代碼
@RequestMapping("/task")
public class TaskController {
@RequestMapping("/")
public String index(String firstName, String lastName) {
return firstName + lastName;
}
@RequestMapping("/testFile")
public String testFile(String firstName, String lastName, MultipartFile file) {
String result = firstName + lastName;
if (file != null) {
result += (file.getOriginalFilename() != null) ? file.getOriginalFilename() : "";
}
return result;
}
}
參考網(wǎng)址
HTML <form> 標簽的 enctype 屬性:http://www.w3school.com.cn/tags/att_form_enctype.asp
HttpUrlConnection 基礎使用:https://www.cnblogs.com/libertycode/p/5979260.html
SpringMvc接收multipart/form-data 傳輸?shù)臄?shù)據(jù) 及 PostMan各類數(shù)據(jù)類型的區(qū)別:https://www.cnblogs.com/ifindu-san/p/8251370.html#undefined
What is the boundary in multipart/form-data?:https://stackoverflow.com/questions/3508338/what-is-the-boundary-in-multipart-form-data