[Jsoup] 如何發送Json請求(how to send json by Jsoup)

版權所有:  bluetata  [email protected]
本文地址:  http://blog.csdn.net/dietime1943/article/details/78974194
轉載請註明來源/作者


本文章意在講解如何進行Post提交的時候發送Json(即如何利用RequestBody發送Json)。

本文大綱如下:

1. 必要的前提知識儲備(High
2. Jsoup源碼對於Content-Type的處理分析(Medium)
3. Jsoup如何進行發送Json請求示例(Demo/High
4. 其他(待補充更新 2018-1-4 18:54:28)


1. 必要的前提知識儲備(High

首先要明確請求頭(Request Headers)注意這裏說的不是響應頭(Response Headers)中的 Content-Type 與 Form表單提交中的enctype都分別是什麼, 及其有何關聯。

Content-Type 意在用於指示資源的MIME類型(media type /互聯網媒體類型

我們在瀏覽器按F12進行查看報文頭的時候會看到有如下類似格式的信息, 其中第7行即爲Content-Type信息

POST /aggsite/EditorPickStat HTTP/1.1
Host: www.bluetata.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/plain, */*; q=0.01
Accept-Language: zh-CN,en-US;q=0.8,zh;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/json; charset=utf-8
X-Requested-With: XMLHttpRequest
Referer: https://www.bluetata.com/
Content-Length: 60
Cookie: _ga=GA1.2.1201449784.1496925001; bdshare_firstime=1497342__utmc=226521935
Connection: keep-alive
在通過HTML form提交生成的POST請求中,請求頭的Content-Type由<form>元素上的enctype屬性指定

也就是<form>中的enctype屬性決定了Content-Type值和請求body裏頭的數據格式。

在這裏值得注意的是現在W3C官方已經確定的enctype屬性只有3種,application/x-www-form-urlencoded(缺省默認值),multipart/form-data(文件上傳), text/plain。而對於enctype='application/json'的這種設置在W3C官方指定指定的爲draft狀態,也就是沒有被官方正式認可,可以參考下面鏈接查看:

W3C HTML JSON form submission Note

在W3C官方給出的說明中,重點comment出:當心,該規範不再處於主動維護中,而HTML工作組也不打算進一步維護它
Beware. This specification is no longer in active maintenance and the HTML Working Group does not intend to maintain it further.

2.Jsoup源碼對於Content-Type的處理分析(Medium)

在Jsoup源碼的HttpConnection.class中, 注意第06行, 13行, 15行, 對應Jsoup源碼的第636, 643, 645行

        static Response execute(Connection.Request req, Response previousResponse) throws IOException {
            Validate.notNull(req, "Request must not be null");
            String protocol = req.url().getProtocol();
            if (!protocol.equals("http") && !protocol.equals("https"))
                throw new MalformedURLException("Only http & https protocols supported");
            final boolean methodHasBody = req.method().hasBody(); // 判斷是否含有請求方法(get, post, delete...etc.)
            final boolean hasRequestBody = req.requestBody() != null;
            if (!methodHasBody)
                Validate.isFalse(hasRequestBody, "Cannot set a request body for HTTP method " + req.method());

            // set up the request for execution
            String mimeBoundary = null;
            if (req.data().size() > 0 && (!methodHasBody || hasRequestBody)) // 帶有請求date,沒有請求方法或者有請求體body
                serialiseRequestUrl(req); // 針對請求的URL進行其序列化使其data map綁定在url上(類似這樣:http://bluetata.com?a=1,b=2).
            else if (methodHasBody)
                mimeBoundary = setOutputContentType(req);

            HttpURLConnection conn = createConnection(req);
            Response res;
            try {
                conn.connect();
                if (conn.getDoOutput())
                    writePost(req, conn.getOutputStream(), mimeBoundary); // 創建提交的上傳輸出流 (重要)

                int status = conn.getResponseCode();
                res = new Response(previousResponse);
                res.setupFromConnection(conn, previousResponse);
                res.req = req;
在源碼的execute方法(Jsoup-1.10.2中第631行)中, 會對你conding的時候創建的connection對象進行以此過濾判斷, 判斷其是否帶有請求data, 有沒有指定具體的請求方法類型(get, post等)亦或者是否帶有請求體(.requestBody),  如果帶有請求data(這種get請求的時候最多, 會直接在url上進行參數綁定), 並且在請求的時候沒有指定其請求方法類型, 或者其帶有請求體, 該情況Jsoup會直接進行請求序列化,注意一旦滿足了這種情況, Jsoup不會在對Header進行Content-Type再處理. 另一方面如果不滿足上述情況, 既沒有帶有請求data, 並且請求中帶有請求體, 這種情況Jsoup會進行Content-Type的再處理, 具體處理情況見下setOutputContentType方法描述.

        // 源碼中的setOutputContentType方法
        private static String setOutputContentType(final Connection.Request req) {
            String bound = null;
            if (req.hasHeader(CONTENT_TYPE)) { // 如果在請求中含有"Content-Type"
                // no-op; don't add content type as already set (e.g. for requestBody())
                // todo - if content type already set, we could add charset or boundary if those aren't included
            }
            else if (needsMultipart(req)) {
                bound = DataUtil.mimeBoundary(); // 生成以隨機數爲形式的分割線
                req.header(CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bound); // 複合組件的時候: multipart/form-data
            } else { // 默認設置Content-Type爲:application/x-www-form-urlencoded
                req.header(CONTENT_TYPE, FORM_URL_ENCODED + "; charset=" + req.postDataCharset());
            }
            return bound;
        }
在源碼中的setOutputContentType方法(Jsoup-1.10.2中第937行)中. 如果在請求頭中帶有Content-Type, 既conding的時候爲connection.header("Content-Type",...), 此時不做任何處理, 直接使用conding所定義的Content-Type屬性, 如果複合組件的時候, Jsoup會將請求頭中Content-Type會被綁定爲:multipart/form-data, 如果沒有指定其Content-Type, Jsoup會強制默認指定其爲:application/x-www-form-urlencoded.

// 源碼中的writePost()方法
        private static void writePost(final Connection.Request req, final OutputStream outputStream, final String bound) throws IOException {
            final Collection<Connection.KeyVal> data = req.data();
            final BufferedWriter w = new BufferedWriter(new OutputStreamWriter(outputStream, req.postDataCharset()));

            if (bound != null) {
                // boundary will be set if we're in multipart mode
                // ....代碼省略
            } else if (req.requestBody() != null) {
                // data will be in query string, we're sending a plaintext body
                w.write(req.requestBody());// 如果請求中帶有request Body那麼會將請求體綁定在提交輸出流中
            }

可以看出只有在Multipart的時候, 也就是Content-Type爲multipart/form-data的時候纔會生成mimeBoundary模擬分割符, 並且這個mimeBoundary會在源碼execute方法中調用writePost(req, conn.getOutputStream(), mimeBoundary)時被使用,Jsoup會執行該writePost方法進行創建提交上傳的輸出流, 這麼說可能有些抽象, 可以參看如下的提交輸出流示例.

POST /post_test.php?t=1 HTTP/1.1  
Accept-Language: zh-CN  
User-Agent: Mozilla/4.0    
Content-Type: multipart/form-data; boundary=---------------------------7dbf514701e8  
Accept-Encoding: gzip, deflate  
Host: http://bluetata.com/  
Content-Length: 345  
Connection: Keep-Alive  
Cache-Control: no-cache  
   
-----------------------------7dbf514701e8  
Content-Disposition: form-data; name="title"  
test  
-----------------------------7dbf514701e8  
Content-Disposition: form-data; name="content"  
....  
-----------------------------7dbf514701e8  
Content-Disposition: form-data; name="submit"  
post article  
-----------------------------7dbf514701e8 
源碼總結:無論是否請求頭中是否帶有Content-Type, 1. Jsoup在執行writePost方法時都會對requestBody進行判斷, 如果帶有即綁定在提交輸出流中; 2.如果請求中帶有請求體requestBody, 會進而再判斷其請求是否帶有請求data, 如果帶有會先進行其序列化到url中;3. 如果請求中設置了請求方法(get, post)在不滿足第2總結點的時候, 會對其進行Content-Type再處理, 如果沒有指定其Content-Type, Jsoup會強制設置默認的Content-Type屬性爲application/x-www-form-urlencoded.

3.Jsoup如何進行發送Json請求示例(Demo/High

根據源碼分析, 我們可以知道Jsoup在請求中綁定數據的時候有兩種提交綁定, 一種是根據data(), 這種一般爲Key - Value鍵值對的形式, 另一種爲請求體帶有requestBody()的形式. 在Content-Type上, 如果不對其指定, Jsoup會強制設置成默認的Content-Type類型.

所以如果要提交Json而非鍵值對的方式進行提交請求,需要使用Jsoup API中的requestBody()方法:

Jsoup.connect(url)
 .requestBody(json)
 .header("Content-Type", "application/json")
 .post();
示例代碼demo:

        String jsonBody = "{\"name\":\"ACTIVATE\",\"value\":\"E0010\"}";
        
        Connection connection = Jsoup.connect("http://bluetata.com/")
                .userAgent("Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36") // User-Agent of Chrome 55
                .referrer("http://bluetata.com/")
                .header("Content-Type", "application/json; charset=UTF-8")
                .header("Accept", "text/plain, */*; q=0.01")
                .header("Accept-Encoding", "gzip,deflate,sdch")
                .header("Accept-Language", "es-ES,es;q=0.8")
                .header("Connection", "keep-alive")
                .header("X-Requested-With", "XMLHttpRequest")
                .requestBody(jsonBody)
                .maxBodySize(100)
                .timeout(1000 * 10)
                .method(Connection.Method.POST);

        Response response = connection.execute();

Jsoup學習討論QQ羣:50695115

Jsoup爬蟲代碼示例及博客內源碼下載:https://github.com/bluetata/crawler-jsoup-maven

更多Jsoup相關文章,請查閱專欄:【Jsoup in action】

本文原創由`bluetata`發佈於blog.csdn.net、轉載請務必註明出處。


Flag Counter


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