Get和Post各種編碼方式和獲取參數的問題

Get和Post各種編碼方式和獲取參數的問題

  做後端開發,會跟各種請求打交道,Get或者Post甚至其他,而就Get和Post不同的編碼方式和content-type提交也有許多的不同,在開發過程中,經常遇到參數接收不到的問題,無論是原生servlet還是springboot框架,都有到過這種問題。這篇文中就這些問題,通過分析,希望能讓自己能更進一步的瞭解HTTP請求和參數接收相關的原理。

Get請求

  Get請求比較簡單,請求的參數爲[key=value],通過&連接起來,一般被稱爲查詢字符串(Query String),例如:http://127.0.0.1/test?name=strive&age=23,參數爲:name=strive&age=23.

GET /get?name=strive HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br

  其實我們只需要看/get?name=strive即查詢參數,GET請求參數是被存放在QueryString 中的,這種在我們後臺是可以通過request.getParameter() 獲取請求參數的。

POST請求

  下面我們來看POST請求,POST請求的方式比Get複雜,它參數的存放位置是和Content-Type 有關係的,具體有什麼關係,繼續往下看。

Content-Type爲application/x-www-form-urlencoded

POST /postUrl HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 11
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ko;q=0.8,en;q=0.7
Form Data
name=strive

  這種方式提交的參數放在了以[key=value]方式存在了請求體中,這種在我們後臺是也是可以通過request.getParameter() 獲取請求參數的。

Content-Type爲multipart/form-data
POST /postFileUpload HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 3768
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
#Content-Type爲multipart/form-data
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary6A2xLxBKaSvqBvDd
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ko;q=0.8,en;q=0.7
#Request的請求體
Request Payload
------WebKitFormBoundary6A2xLxBKaSvqBvDd
Content-Disposition: form-data; name="file"; filename="Baidu.cer"
Content-Type: application/x-x509-ca-cert
#這裏省略了文件的數據
------WebKitFormBoundary6A2xLxBKaSvqBvDd
Content-Disposition: form-data; name="name"
strive
------WebKitFormBoundary6A2xLxBKaSvqBvDd--

  在Content-Type: multipart/form-data 時,而且POST提交的參數存放在了Request Payload,這種請求參數沒辦法通過request.getParameter() 獲取請求參數的。

Content-Type爲application/json

POST /postJson HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 13
Accept: */*
Origin: http://localhost:8080
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: cors
Content-Type: application/json
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ko;q=0.8,en;q=0.7
#Request的請求體
Request Payload
{name:strive}

  在Content-Type: application/json 時,而且POST提交的參數存放在了Request Payload,這種請求參數沒辦法通過request.getParameter() 獲取請求參數的。

Content-Type爲text/plain
POST /postPlain HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 13
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
Content-Type: text/plain
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: same-origin
Referer: http://localhost:8080/getAndPostIndex
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ko;q=0.8,en;q=0.7
#Request的請求體
Request Payload
name=strive

  在Content-Type: text/plain 時,而且POST提交的參數存放在了Request Payload,這種請求參數沒辦法通過request.getParameter() 獲取請求參數的。

POST請求的問題

  從上面的幾個請求的報文的例子中我們可以找到一些規律,以application/x-www-form-urlencoded作POST提交的數據,後臺是可以通過request.getParameter() 獲取請求參數的。而multipart/form-data,application/jsontext/plain作POST提交數據時,後臺沒法通過request.getParameter() 獲取請求參數的。application/x-www-form-urlencoded的參數放在了名爲Form Data的請求體中,而其他的則放在了名爲Request Payload的請求體中。那是什麼原因了?
  解析請求參數的實現源碼org.apache.catalina.connector.Request#parseParameters如下:

 protected void parseParameters() {
        this.parametersParsed = true;
        Parameters parameters = this.coyoteRequest.getParameters();
        boolean success = false;
        try {
            parameters.setLimit(this.getConnector().getMaxParameterCount());
            Charset charset = this.getCharset();
            boolean useBodyEncodingForURI = this.connector.getUseBodyEncodingForURI();
            parameters.setCharset(charset);
            if (useBodyEncodingForURI) {
                parameters.setQueryStringCharset(charset);
            }
            parameters.handleQueryParameters();
            if (this.usingInputStream || this.usingReader) {
                success = true;
                return;
            }
            //獲取請求的contentType
            String contentType = this.getContentType();
            if (contentType == null) {
                contentType = "";
            }
            int semicolon = contentType.indexOf(59);
            if (semicolon >= 0) {
                contentType = contentType.substring(0, semicolon).trim();
            } else {
                contentType = contentType.trim();
            }
            //處理文件上傳
            if ("multipart/form-data".equals(contentType)) {
                this.parseParts(false);
                success = true;
                return;
            }
            if (!this.getConnector().isParseBodyMethod(this.getMethod())) {
                success = true;
                return;
            }
            if ("application/x-www-form-urlencoded".equals(contentType)) {
                //如果是這種contenttype則解析
                int len = this.getContentLength();
                if (len <= 0) {
                    if ("chunked".equalsIgnoreCase(this.coyoteRequest.getHeader("transfer-encoding"))) {
                        Object var21 = null;

                        Context context;
                        byte[] formData;
                        try {
                            formData = this.readChunkedPostBody();
                        } catch (IllegalStateException var17) {
                           parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                            context = this.getContext();
                            if (context != null && context.getLogger().isDebugEnabled()) {
                               context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var17);
                            }
                            return;
                        } catch (IOException var18) {
                          parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                            context = this.getContext();
                            if (context != null && context.getLogger().isDebugEnabled()) {
                                context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var18);
                            }
                            return;
                        }
                        if (formData != null) {
                            parameters.processParameters(formData, 0, formData.length);
                        }
                    }
                } else {
                    int maxPostSize = this.connector.getMaxPostSize();
                    Context context;
                    if (maxPostSize >= 0 && len > maxPostSize) {
                        context = this.getContext();
                        if (context != null && context.getLogger().isDebugEnabled()) {
                           context.getLogger().debug(sm.getString("coyoteRequest.postTooLarge"));
                        }
                        this.checkSwallowInput();
                        parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
                        return;
                    }
                    context = null;
                    byte[] formData;
                    if (len < 8192) {
                        if (this.postData == null) {
                            this.postData = new byte[8192];
                        }
                        formData = this.postData;
                    } else {
                        formData = new byte[len];
                    }
                    try {
                        if (this.readPostBody(formData, len) != len) {
                            parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
                            return;
                        }
                    } catch (IOException var19) {
                        Context context = this.getContext();
                        if (context != null && context.getLogger().isDebugEnabled()) {
                           context.getLogger().debug(sm.getString("coyoteRequest.parseParameters"), var19);
                        }
                        parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
                        return;
                    }
                    // 處理POST請求參數,把它放到requestparameter map中(即request.getParameterMap獲取到的Map,request.getParameter(name)也是從這個Map中獲取的)
                    parameters.processParameters(formData, 0, len);
                }
                success = true;
                return;
            }
            success = true;
        } finally {
            if (!success) {
                parameters.setParseFailedReason(FailReason.UNKNOWN);
            }
        }
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章