摘要:大多數情況下,我們使用內置的策略,便能夠方便直接地獲取這些。在默認的實現中,主要的工作是判斷中規范的類型,然后再調用具體的某一個實現。
本文原地址:http://www.fullstackyang.com/...,轉發請注明本博客地址或segmentfault地址,謝謝!
在使用HttpClient進行抓取一些網頁的時候,經常會保留從服務器端發回的Cookie信息,以便發起其他需要這些Cookie的請求。大多數情況下,我們使用內置的cookie策略,便能夠方便直接地獲取這些cookie。
下面的一小段代碼,就是訪問http://www.baidu.com,并獲取對應的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(); } } }
打印結果
>>>>>>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]
但是也有一些網站返回的cookie并不一定完全符合規范,例如下面這個例子,從打印出的header中可以看到,這個cookie中的Expires屬性是時間戳形式,并不符合標準的時間格式,因此,httpclient對于cookie的處理失效,最終無法獲取到cookie,并且發出了一條警告信息:“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的數據,重新構造一個cookie出來,也有很多人確實也是這么做的,但這種方法不夠優雅,那么如何解決這個問題?網上相關的資料又很少,所以就只能先從官方文檔入手。在官方文檔3.4小節custom cookie policy中講到允許自定義的cookie策略,自定義的方法是實現CookieSpec接口,并通過CookieSpecProvider來完成在httpclient中的初始化和注冊策略實例的工作。好了,關鍵的線索在于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; …… }
在源碼中我們發現了一個parse方法,看注釋就知道正是這個方法,將Set-Cookie的header信息解析為Cookie對象,自然地再了解一下在httplcient中的默認實現DefaultCookieSpec,限于篇幅,源碼就不貼了。在默認的實現中,DefaultCookieSpec主要的工作是判斷header中Cookie規范的類型,然后再調用具體的某一個實現。像上述這種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); } …… }
到這里已經比較清楚了,我們只需要將Cookie中expires的時間轉換為正確的格式,然后再送入默認的解析器就可以了。
解決方法:
自定義一個CookieSpec類,繼承DefaultCookieSpec
重寫parser方法
將Cookie中的expires轉換為正確的時間格式
調用默認的解析方法
實現如下(URL就不公開了,已經隱去)
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(); } } } }
再次運行,順利地打印出正確的結果,完美!
>>>>>>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]
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70424.html
摘要:最近開發中遇到的一個主從延遲的坑,記錄并總結,避免再次犯同樣的錯誤。運行時查詢為空,執行完畢后查詢時內容存在,初步懷疑是主從延遲問題。報錯只是部分失敗,確定是主從延遲的問題。接下來,會去學習主從復制的原理,敬請期待。 最近開發中遇到的一個MySQL主從延遲的坑,記錄并總結,避免再次犯同樣的錯誤。 情景 一個活動信息需要審批,審批之后才能生效。因為之后活動要編輯,編輯后也可能觸發審批,審...
最近在改一份二手代碼的時候,項目運行報了個java.lang.IllegalArgumentException: node to traverse cannot be null異常。WTF?!難道我HQL寫錯了?!我只是添加了一個update方法而已啊! 問題排查: showImg(https://segmentfault.com/img/bVbeBmn?w=1657&h=155);這里使用的是J...
摘要:通常情況下,第一次請求完畢后,服務器都會給客戶端返回一些字段,在第二次請求時,如果使用的是測試工具或者的這個庫,字段都會自動被附加在第二次請求的頭部。從里取出前一次請求中由服務器返回的這里把里的加到第二個請求的頭部字段,謎底就這樣解開了。 我們用apache的HttpClient這個庫消費云端的Restful API時,一般都需要兩次HTTP調用,第一次獲得某種token,比如獲取防止...
摘要:通常情況下,第一次請求完畢后,服務器都會給客戶端返回一些字段,在第二次請求時,如果使用的是測試工具或者的這個庫,字段都會自動被附加在第二次請求的頭部。從里取出前一次請求中由服務器返回的這里把里的加到第二個請求的頭部字段,謎底就這樣解開了。 我們用apache的HttpClient這個庫消費云端的Restful API時,一般都需要兩次HTTP調用,第一次獲得某種token,比如獲取防止...
閱讀 2987·2021-11-23 09:51
閱讀 2800·2021-11-11 16:55
閱讀 2908·2021-10-14 09:43
閱讀 1394·2021-09-23 11:22
閱讀 1035·2019-08-30 11:04
閱讀 1663·2019-08-29 11:10
閱讀 956·2019-08-27 10:56
閱讀 3102·2019-08-26 12:01