使用流畅的 API 修复 HttpClient 警告“无效过期属性”

我使用 HttpClient 流畅的 API 发出 GET 请求:

String jsonResult = Request.Get(requestUrl)
.connectTimeout(2000)
.socketTimeout(2000)
.execute().returnContent().asString();

但对于每一个请求,我都会得到以下警告:

apr 07, 2016 12:26:46 PM org.apache.http.client.protocol.ResponseProcessCookies processCookies
WARNING: Invalid cookie header: "Set-Cookie: WMF-Last-Access=07-Apr-2016;Path=/;HttpOnly;Expires=Mon, 09 May 2016 00:00:00 GMT". Invalid 'expires' attribute: Mon, 09 May 2016 00:00:00 GMT

我怎样才能解决这个问题,并继续使用流畅的界面?理想情况下,我想要一个合适的方法来修复它,但是因为我并不真正关心我的用例中的 cookie,所以任何允许我停止显示警告的解决方案(除了重定向 stderr,因为我需要它)都是受欢迎的。

57740 次浏览

Solved with:

System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.client.protocol.ResponseProcessCookies", "fatal");

The default HttpClient has difficulty understanding the latest RFC-compliant headers.

Instead of hiding the warning, just switch to a standard cookie spec like this (HttpClient 4.4+):

HttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(RequestConfig.custom()
.setCookieSpec(CookieSpecs.STANDARD).build())
.build();

If you want to use HttpClientBuilder you can use the following sytax:

        HttpClient httpClient = HttpClientBuilder.create()
.setDefaultRequestConfig(RequestConfig.custom()
.setCookieSpec(CookieSpecs.STANDARD).build()).build();

For the developers don't want to think on the object model, wrapping the HttpClient for a RestTemplate might be used as below ( as @comiventor mentioned above especially for Spring Boot Developers).

a Customizer for RestTemplate,

public class RestTemplateStandardCookieCustomizer
implements RestTemplateCustomizer {


@Override
public void customize(final RestTemplate restTemplate) {


final HttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(RequestConfig.custom()
.setCookieSpec(CookieSpecs.STANDARD).build())
.build();


restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(httpClient)
);
}
}

and using it with the RestTemplate Builder

var restTemplate = restTemplateBuilder.additionalCustomizers(
new RestTemplateStandardCookieCustomizer()
).build();

Apache HttpClient 5.1.3 is fully compliant with RFC 6265 by default. Hence the following code doesn't produce any warnings anymore:

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;


//...


try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(url);
try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
System.out.println(response.getCode() + " " + response.getReasonPhrase());
}
}

This answer is not directly related to the OP, but to help those looking for a solution to the problem. This is the first result on Google when searching Invalid expires attribute.

I am using Resteasy as a transitive dependency of Keycloak Admin REST Client.

The problem is resteasy-client does not provide an easy way to configure the Apache HttpClient HttpClient#RequestConfig#CookieSpec. See the default ClientHttpEngineBuilder used by resteasy-client ClientHttpEngineBuilder43.

To fix it, since the ClientHttpEngineBuilder43#createEngine method is protected, I have extended the class and specified the suggested CookieSpecs.STANDARD:

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;


import org.apache.http.HttpHost;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.jboss.resteasy.client.jaxrs.ClientHttpEngine;
import org.jboss.resteasy.client.jaxrs.ClientHttpEngineBuilder43;


public class StandardCookieSpecClientHttpEngineBuilder extends ClientHttpEngineBuilder43 {


@Override
protected ClientHttpEngine createEngine(
HttpClientConnectionManager cm,
Builder rcBuilder,
HttpHost defaultProxy,
int responseBufferSize,
HostnameVerifier verifier,
SSLContext context) {
// CookieSpecs.DEFAULT does not handle the latest RFC correctly
// <https://stackoverflow.com/a/40697322/13688761>
rcBuilder.setCookieSpec(CookieSpecs.STANDARD);
return super.createEngine(cm, rcBuilder, defaultProxy, responseBufferSize, verifier, context);
}
}


Then, the corresponding ResteasyClient and Keycloak are built as:

import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class KeycloakConfig {


@Bean
public Keycloak keycloak() {
return KeycloakBuilder.builder()
// ... Some other configuration to the builder
.resteasyClient(buildResteasyClient())
.build();
}


private ResteasyClient buildResteasyClient() {
ResteasyClientBuilder resteasyClientBuilder = new ResteasyClientBuilder();
// ... Some other configuration to the client builder
return resteasyClientBuilder
.httpEngine(
new StandardCookieSpecClientHttpEngineBuilder()
.resteasyClientBuilder(resteasyClientBuilder)
.build())
.build();
}
}