摘要:大多數(shù)情況下,我們使用內(nèi)置的策略,便能夠方便直接地獲取這些。在默認的實現(xiàn)中,主要的工作是判斷中規(guī)范的類型,然后再調(diào)用具體的某一個實現(xiàn)。
本文原地址:http://www.fullstackyang.com/...,轉(zhuǎn)發(fā)請注明本博客地址或segmentfault地址,謝謝!
在使用HttpClient進行抓取一些網(wǎng)頁的時候,經(jīng)常會保留從服務(wù)器端發(fā)回的Cookie信息,以便發(fā)起其他需要這些Cookie的請求。大多數(shù)情況下,我們使用內(nèi)置的cookie策略,便能夠方便直接地獲取這些cookie。
下面的一小段代碼,就是訪問http://www.baidu.com,并獲取對應(yīng)的cookie:
@Test public void getCookie(){ CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet get=new HttpGet("http://www.baidu.com"); HttpClientContext context = HttpClientContext.create(); try { CloseableHttpResponse response = httpClient.execute(get, context); try{ System.out.println(">>>>>>headers:"); Arrays.stream(response.getAllHeaders()).forEach(System.out::println); System.out.println(">>>>>>cookies:"); context.getCookieStore().getCookies().forEach(System.out::println); } finally { response.close(); } } catch (IOException e) { e.printStackTrace(); }finally { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } }
打印結(jié)果
>>>>>>headers: Server: bfe/1.0.8.18 Date: Tue, 12 Sep 2017 06:19:06 GMT Content-Type: text/html Last-Modified: Mon, 23 Jan 2017 13:28:24 GMT Transfer-Encoding: chunked Connection: Keep-Alive Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform Pragma: no-cache Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/ >>>>>>cookies: [version: 0][name: BDORZ][value: 27315][domain: baidu.com][path: /][expiry: null]
但是也有一些網(wǎng)站返回的cookie并不一定完全符合規(guī)范,例如下面這個例子,從打印出的header中可以看到,這個cookie中的Expires屬性是時間戳形式,并不符合標準的時間格式,因此,httpclient對于cookie的處理失效,最終無法獲取到cookie,并且發(fā)出了一條警告信息:“Invalid ‘expires’ attribute: 1505204523”
警告: Invalid cookie header: "Set-Cookie: yd_cookie=90236a64-8650-494b332a285dbd886e5981965fc4a93f023d; Expires=1505204523; Path=/; HttpOnly". Invalid "expires" attribute: 1505204523 >>>>>>headers: Date: Tue, 12 Sep 2017 06:22:03 GMT Content-Type: text/html Connection: keep-alive Set-Cookie: yd_cookie=90236a64-8650-494b332a285dbd886e5981965fc4a93f023d; Expires=1505204523; Path=/; HttpOnly Cache-Control: no-cache, no-store Server: WAF/2.4-12.1 >>>>>>cookies:
雖然我們可以利用header的數(shù)據(jù),重新構(gòu)造一個cookie出來,也有很多人確實也是這么做的,但這種方法不夠優(yōu)雅,那么如何解決這個問題?網(wǎng)上相關(guān)的資料又很少,所以就只能先從官方文檔入手。在官方文檔3.4小節(jié)custom cookie policy中講到允許自定義的cookie策略,自定義的方法是實現(xiàn)CookieSpec接口,并通過CookieSpecProvider來完成在httpclient中的初始化和注冊策略實例的工作。好了,關(guān)鍵的線索在于CookieSpec接口,我們來看一下它的源碼:
public interface CookieSpec { …… /** * Parse the {@code "Set-Cookie"} Header into an array of Cookies. * *This method will not perform the validation of the resultant * {@link Cookie}s
* * @see #validate * * @param header the {@code Set-Cookie} received from the server * @param origin details of the cookie origin * @return an array of {@code Cookie}s parsed from the header * @throws MalformedCookieException if an exception occurs during parsing */ Listparse(Header header, CookieOrigin origin) throws MalformedCookieException; …… }
在源碼中我們發(fā)現(xiàn)了一個parse方法,看注釋就知道正是這個方法,將Set-Cookie的header信息解析為Cookie對象,自然地再了解一下在httplcient中的默認實現(xiàn)DefaultCookieSpec,限于篇幅,源碼就不貼了。在默認的實現(xiàn)中,DefaultCookieSpec主要的工作是判斷header中Cookie規(guī)范的類型,然后再調(diào)用具體的某一個實現(xiàn)。像上述這種Cookie,最終是交由NetscapeDraftSpec的實例來做解析,而在NetscapeDraftSpec的源碼中,定義了默認的expires時間格式為“EEE, dd-MMM-yy HH:mm:ss z”
public class NetscapeDraftSpec extends CookieSpecBase { protected static final String EXPIRES_PATTERN = "EEE, dd-MMM-yy HH:mm:ss z"; /** Default constructor */ public NetscapeDraftSpec(final String[] datepatterns) { super(new BasicPathHandler(), new NetscapeDomainHandler(), new BasicSecureHandler(), new BasicCommentHandler(), new BasicExpiresHandler( datepatterns != null ? datepatterns.clone() : new String[]{EXPIRES_PATTERN})); } NetscapeDraftSpec(final CommonCookieAttributeHandler... handlers) { super(handlers); } public NetscapeDraftSpec() { this((String[]) null); } …… }
到這里已經(jīng)比較清楚了,我們只需要將Cookie中expires的時間轉(zhuǎn)換為正確的格式,然后再送入默認的解析器就可以了。
解決方法:
自定義一個CookieSpec類,繼承DefaultCookieSpec
重寫parser方法
將Cookie中的expires轉(zhuǎn)換為正確的時間格式
調(diào)用默認的解析方法
實現(xiàn)如下(URL就不公開了,已經(jīng)隱去)
public class TestHttpClient { String url = sth; class MyCookieSpec extends DefaultCookieSpec { @Override public Listparse(Header header, CookieOrigin cookieOrigin) throws MalformedCookieException { String value = header.getValue(); String prefix = "Expires="; if (value.contains(prefix)) { String expires = value.substring(value.indexOf(prefix) + prefix.length()); expires = expires.substring(0, expires.indexOf(";")); String date = DateUtils.formatDate(new Date(Long.parseLong(expires) * 1000L),"EEE, dd-MMM-yy HH:mm:ss z"); value = value.replaceAll(prefix + "d{10};", prefix + date + ";"); } header = new BasicHeader(header.getName(), value); return super.parse(header, cookieOrigin); } } @Test public void getCookie() { CloseableHttpClient httpClient = HttpClients.createDefault(); Registry cookieSpecProviderRegistry = RegistryBuilder. create() .register("myCookieSpec", context -> new MyCookieSpec()).build();//注冊自定義CookieSpec HttpClientContext context = HttpClientContext.create(); context.setCookieSpecRegistry(cookieSpecProviderRegistry); HttpGet get = new HttpGet(url); get.setConfig(RequestConfig.custom().setCookieSpec("myCookieSpec").build()); try { CloseableHttpResponse response = httpClient.execute(get, context); try{ System.out.println(">>>>>>headers:"); Arrays.stream(response.getAllHeaders()).forEach(System.out::println); System.out.println(">>>>>>cookies:"); context.getCookieStore().getCookies().forEach(System.out::println); } finally { response.close(); } } catch (IOException e) { e.printStackTrace(); }finally { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } }
再次運行,順利地打印出正確的結(jié)果,完美!
>>>>>>headers: Date: Tue, 12 Sep 2017 07:24:10 GMT Content-Type: text/html Connection: keep-alive Set-Cookie: yd_cookie=9f521fc5-0248-4ab3ee650ca50b1c7abb1cd2526b830e620f; Expires=1505208250; Path=/; HttpOnly Cache-Control: no-cache, no-store Server: WAF/2.4-12.1 >>>>>>cookies: [version: 0][name: yd_cookie][value: 9f521fc5-0248-4ab3ee650ca50b1c7abb1cd2526b830e620f][domain: www.sth.com][path: /][expiry: Tue Sep 12 17:24:10 CST 2017]
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/70424.html
摘要:最近開發(fā)中遇到的一個主從延遲的坑,記錄并總結(jié),避免再次犯同樣的錯誤。運行時查詢?yōu)榭?,?zhí)行完畢后查詢時內(nèi)容存在,初步懷疑是主從延遲問題。報錯只是部分失敗,確定是主從延遲的問題。接下來,會去學習主從復制的原理,敬請期待。 最近開發(fā)中遇到的一個MySQL主從延遲的坑,記錄并總結(jié),避免再次犯同樣的錯誤。 情景 一個活動信息需要審批,審批之后才能生效。因為之后活動要編輯,編輯后也可能觸發(fā)審批,審...
最近在改一份二手代碼的時候,項目運行報了個java.lang.IllegalArgumentException: node to traverse cannot be null異常。WTF?!難道我HQL寫錯了?!我只是添加了一個update方法而已??! 問題排查: showImg(https://segmentfault.com/img/bVbeBmn?w=1657&h=155);這里使用的是J...
摘要:通常情況下,第一次請求完畢后,服務(wù)器都會給客戶端返回一些字段,在第二次請求時,如果使用的是測試工具或者的這個庫,字段都會自動被附加在第二次請求的頭部。從里取出前一次請求中由服務(wù)器返回的這里把里的加到第二個請求的頭部字段,謎底就這樣解開了。 我們用apache的HttpClient這個庫消費云端的Restful API時,一般都需要兩次HTTP調(diào)用,第一次獲得某種token,比如獲取防止...
摘要:通常情況下,第一次請求完畢后,服務(wù)器都會給客戶端返回一些字段,在第二次請求時,如果使用的是測試工具或者的這個庫,字段都會自動被附加在第二次請求的頭部。從里取出前一次請求中由服務(wù)器返回的這里把里的加到第二個請求的頭部字段,謎底就這樣解開了。 我們用apache的HttpClient這個庫消費云端的Restful API時,一般都需要兩次HTTP調(diào)用,第一次獲得某種token,比如獲取防止...
閱讀 3000·2021-11-23 09:51
閱讀 2820·2021-11-11 16:55
閱讀 2935·2021-10-14 09:43
閱讀 1404·2021-09-23 11:22
閱讀 1046·2019-08-30 11:04
閱讀 1674·2019-08-29 11:10
閱讀 970·2019-08-27 10:56
閱讀 3125·2019-08-26 12:01