版权所有: 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
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、转载请务必注明出处。