1. 程式人生 > 實用技巧 >HTTP POST 請求的兩種編碼格式:application/x-www-form-urlencoded 和 multipart/form-data

HTTP POST 請求的兩種編碼格式:application/x-www-form-urlencoded 和 multipart/form-data

在常見業務開發中,POST 請求常常在這些地方使用:前端表單提交時、呼叫介面程式碼時和使用 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>

通過測試發現可以正常訪問介面,在 Chrome 的開發者工具中可以看出,表單上傳編碼格式為 application/x-www-form-urlencoded(Request Headers 中),引數的格式為 key=value&key=value

我們可以看出,伺服器知道引數用符號 & 間隔,如果引數值中需要 &,則必須對其進行編碼。編碼格式就是 application/x-www-form-urlencoded將鍵值對的引數用 & 連線起來,如果有空格,將空格轉換為 + 加號;有特殊符號,將特殊符號轉換為 ASCII HEX)。

application/x-www-form-urlencoded

是瀏覽器預設的編碼格式。對於 Get 請求,是將引數轉換 ?key=value&key=value 格式,連線到 url 後

ps:可以在這個網址測試表單:http://www.runoob.com/try/try.php?filename=tryhtml_form_submit

multipart/form-data

那麼當伺服器使用 multipart/form-data 接收 POST 請求時,伺服器怎麼知道每個引數的開始位置和結束位置呢?

<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>

我們在開發者工具中可以看出 multipart/form-data 不會對引數編碼,使用的 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 型別引數,還要在 spring 上下文配置以下內容,SpringBoot 專案則不需要

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<property name="defaultEncoding" value="utf-8"></property>
</bean>

我們可以通過 FormData 物件模擬表單提交,用原始的 XMLHttpRequest 來發送資料,讓我們可以在 Chrome 開發工具中檢視到具體格式:

<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>

格式如下:

二、呼叫介面程式碼時

1、在程式碼中使用 application/x-www-form-urlencoded 編碼格式設定 Request 屬性呼叫介面,可以如下實現:

private static String doPost(String strUrl, String content) {
        String result = "";

        try {
            URL url = new URL(strUrl);
            // 通過呼叫 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() 返回的輸出流用於傳輸資料
            urlConnection.setDoOutput(true);
            // 設定通用請求屬性為預設瀏覽器編碼型別
            urlConnection.setRequestProperty("content-type", "application/x-www-form-urlencoded");
            //getOutputStream() 返回的輸出流,用於寫入引數資料。
            OutputStream outputStream = urlConnection.getOutputStream();
            outputStream.write(content.getBytes());
            outputStream.flush();
            outputStream.close();
            // 此時將呼叫介面方法。getInputStream() 返回的輸入流可以讀取返回的資料。
            InputStream inputStream = urlConnection.getInputStream();
            byte[] data = new byte[1024];
            StringBuilder sb = new StringBuilder();
            //inputStream 每次就會將讀取 1024 個 byte 到 data 中,當 inputSteam 中沒有資料時,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 屬性呼叫介面時,其中 boundary 的值可以在設定 Content-Type 時指定,讓伺服器知道如何拆分它接受的引數。通過以下程式碼的呼叫介面:

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,有兩個要求:使用不會出現在傳送到伺服器的 HTTP 資料中的值;並在請求訊息中的分割位置都使用相同的值
    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 開發工具中 (Request Headers 處的 Content-Type 和 Form Data) 一樣的資料

2、POST 請求 -> Body -> form-data

相當於 html 表單請求,value 可為 Text 或檔案。

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

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

可以更改左上角的型別,來檢視相應的 Headers 程式碼,常見的是下面三種:

Java OK HTTP

JavaScript Jquery AJAX

JavaScript XHR

介面程式碼

@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;
    }
}

總結

POST 請求的兩種編碼格式:application/x-www-urlencoded 是瀏覽器預設的編碼格式,用於鍵值對引數,引數之間用 & 間隔;multipart/form-data 常用於檔案等二進位制,也可用於鍵值對引數,最後連線成一串字元傳輸 (參考 Java OK HTTP)。除了這兩個編碼格式,還有 application/json 也經常使用。

延伸閱讀