版權所有: 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
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、轉載請務必註明出處。