[Jsoup] 如何发送Json请求(how to send json by Jsoup)

版权所有:  bluetata  dietime1943@gmail.com
本文地址:  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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bluetata

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值