Right way to use apache httpclient - CloseableHttpClient - httpclient

I have below implementation of Apache http client and Implementation Class. Do we need to close the client after every request or Close the response object? Our application processes lot of API requests per second and I don't want to close and create connection for every request
HttpUtil.java
private static final CloseableHttpClient httpClient;
static {
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(TIMEOUT_SECONDS * 1000)
.setConnectTimeout(TIMEOUT_SECONDS * 1000)
.build();
httpClient = HttpClients.custom().setMaxConnPerRoute(20).setMaxConnTotal(500).setDefaultRequestConfig(requestConfig).build();
}
ImplementationClass.java
CloseableHttpClient httpClient = HttpUtil.getHttpClient();
CloseableHttpResponse resp = httpClient.execute(httpGet);
EntityUtils.consume(resp.getEntity());

Related

HTTP request doesn't work with a paricular rest

I'm making an application filled with various rest services, so I create a one-for-all HTTP class in order to allow a client application to keep asking information, via rest, to a server application
public HttpURLConnection HTTPSENDJSON(String urlAPI,String out,String requestmethod) throws IOException {
URL url = new URL(urlAPI);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestMethod(requestmethod);
conn.setDoOutput(true);
conn.setDoInput(true);
OutputStream os = conn.getOutputStream();
System.out.println(out);
os.write(out.getBytes());
os.flush();
os.close();
return conn;
urlAPI is the desired URL, a string is the JSON string (I'm using GSON) and the requestmethod is a string in order to switch from PUT\POST\GET\PATCH.
So, as I wrote, it's all ok if I need to retrieve information from DB\insert a new record
ATM my Client application makes a call to the server who calls an EJB in order to CRUD the information.
this is the Client method who call the upper method (the HTTPSENDJSON )
public String modifica() throws IOException {
Universal_HTTPREQUEST httprequest = new Universal_HTTPREQUEST();
String url= "http://localhost:8080/ModuloWebClientNuovo/rest/clientela/modifica/account/"+ac.getId()+"";
Gson g = new Gson();
String out=g.toJson(ac, Account.class);
httprequest.HTTPSENDJSON(url, out,"PUT");
and this is the working (at least with POSTMAN) services
#PUT
#Path("modifica/account/{id}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response modificaaccount(#PathParam("id") int id,Account a) {
System.out.println("i'm inside the api and i wrote: "+ a.toString());
ac.updateAccount(a);
return Response.status(200).entity(a).build() ;
}
The Client doesn't even make the call to the server, BUT the only with this specific rest, other works fine.
update account EJB is:
#Stateless
public class AccountEJB implements IAccountCrud {
#EJB
Iconnessioni x;
#Override
public void updateAccount(Account account) {
EntityManager entityManager=x.apriconnessione();
entityManager.merge(account);
x.chiudiconnessione(entityManager);
}
}
Fixed whit a new from scratch wildfly

proxy for the REST

I have a method in which sending XML(byte array) through REST. I set the global properties of a test proxy (CCProxy). In the application CCProxy log I have nothing. What did I do wrong?
url = "https://myurl/api/Storage/Init";
SSLContext sc = createSslContext();
Client client = ClientBuilder.newBuilder().sslContext(sc).hostnameVerifier(new HostnameVerifier() {
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}).build();
System.setProperty("http.proxySet", "true");
System.setProperty("http.proxyHost", "192.168.11.64");
System.setProperty("http.proxyPort", "808");
System.setProperty("https.proxyHost", "192.168.11.64");
System.setProperty("https.proxyPort", "808");
// client.property("http.proxy.server.uri", "192.168.11.64");
// client.property("http.proxy.server.port", "808");
// client.property("https.proxy.server.uri", "192.168.11.64");
// client.property("https.proxy.server.port", "808");
javax.ws.rs.core.Response response = client.target(url).request(MediaType.APPLICATION_JSON)
.post(Entity.entity(doc, MediaType.APPLICATION_XML));
setting CCProxy
The second question, how to set the proxy only for a particular method? I tried something like this but it did not work.
client.property("http.proxy.server.uri", "192.168.11.64");
client.property("http.proxy.server.port", "808");
Update: After the changes as suggested by CWasp
final SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, (x509CertChain, authType) -> true).build();
HttpClient httpClient = HttpClientBuilder.create()
.setProxy(new HttpHost("192.168.11.64", 808))
.setDefaultCredentialsProvider(credProvider).setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy())
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.setSSLContext(sslContext)
.build();
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
client = new ResteasyClientBuilder().httpEngine(engine).build();
Try this if proxy authentication required
For Jersey 2.x:
ClientConfig config = new ClientConfig();
config.connectorProvider(new ApacheConnectorProvider());
config.property(ClientProperties.PROXY_URI, "http://" + PROXY_HOST + ":" + PROXY_PORT);
config.property(ClientProperties.PROXY_USERNAME, PROXY_USER);
config.property(ClientProperties.PROXY_PASSWORD, PROXY_PASS);
JerseyClient client = new JerseyClientBuilder()
.withConfig(config)
.build();
For Resteasy:
Credentials credentials = new UsernamePasswordCredentials(PROXY_USER, PROXY_PASS);
CredentialsProvider credProvider = new BasicCredentialsProvider();
credProvider.setCredentials(new AuthScope(PROXY_HOST, PROXY_PORT), credentials);
HttpClient httpClient = HttpClientBuilder.create()
.setProxy(new HttpHost(PROXY_HOST, PROXY_PORT))
.setDefaultCredentialsProvider(credProvider)
.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy())
.build();
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
ResteasyClient client = new ResteasyClientBuilder()
.httpEngine(engine)
.build();

Caching Digest Authentication Responses

I am using Apache's HttpClient to talk to a server that requires Digest Authentication.
I set up an HttpClientContext with credentials like so:
#Bean
public HttpClientContext getCredentialsHttpClientContext() {
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(createCredentialsProvider());
context.setAuthCache(new BasicAuthCache());
return context;
}
Later on I use this context to stream data from my server:
public InputStream getInputStreamFromUrl(String url) throws IOException {
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpResponse httpResponse = httpClient.execute(new HttpGet(url), clientContext);
return httpResponse.getEntity().getContent();
}
Later on in processing this input stream, I need to make calls to the same server using the HttpClientContext, and every subsequent call requires authentication.
Is it possible to cache the results of the digest auth, so that subsequent requests don't need to be authenticated again?
#oleg's comments above pointed me in the right direction.
I actually create a singleton HttoClientContext bean:
#Bean
public HttpClientContext getCredentialsHttpClientContext() {
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(createCredentialsProvider());
context.setAuthCache(new BasicAuthCache());
return context;
}
I inject this into a service that I use in the getInputStreamFromUrl method above.
In other cases, I am using the RestOperations template. In order for this to work, I created the following Spring bean, creates a RestTemplate using the same HttpClientContext singelton:
#Bean
public RestOperations getIcsCredentialedRestOperations() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactoryWithContext(httpClient, httpClientContext);
return new RestTemplate(requestFactory);
}
In the above example, HttpComponentsClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory overriding the createHttpContext method returning the context that I want.

Why HandshakeRequest doesn't return HttpSession in my ServerEndpointConfig.Configurator?

There is a good example for sharing HttpSession between Websocket and Rest service. (Spring DispatchServlet cannot find resource within Jetty) But it doesn't work for me. I'm not sure is there any thing I'm missing?
I'm using Jetty as websocket server and also I created a WebApp as well which injected by SpringConfig.
private void init() throws Exception
{
Server server = new Server();
// Create SSL Connector
ServerConnector serverConnector = getSSLConnector(server);
// Bundle to server
server.setConnectors(new Connector[] { serverConnector });
// Create request handler collection
HandlerCollection handlers = new HandlerCollection();
// Add WebSocket handler
final ServletContextHandler servletContextHandler = getWebSocketContextHandler();
handlers.addHandler(servletContextHandler);
// Add Servlet handler
handlers.addHandler(getWebAppServletContextHandler());
server.setHandler(handlers);
// Initial WebSocket
WebSocketServerContainerInitializer.configureContext(servletContextHandler);
// Start Jetty
server.start();
server.join();
}
Both WebSocket and Rest are working under same port perfectly, of course, with different context paths.
Now, I created a Rest service:
#RequestMapping(value = "/login", method = RequestMethod.POST)
#Consumes({ MediaType.APPLICATION_JSON_VALUE })
#Produces({ MediaType.APPLICATION_JSON_VALUE })
public #ResponseBody Message login(#RequestBody Credential credential, #Context HttpServletRequest servlerRequest)
{
...
HttpSession session = servlerRequest.getSession(true);
session.setAttribute("userName", credential.getUserName());
...
Message message = new Message();
...
return message;
}
In this service I created a HttpSession and stored something in. As I said, it works, and so does the session.
Rest client:
public void login() throws KeyManagementException, NoSuchAlgorithmException
{
final String loginServiceUri = HTTP_SERVICE_BASE_URI + "/login";
ClientConfig clientConfig = new DefaultClientConfig();
...
Client client = Client.create(clientConfig);
WebResource webResource = client.resource(loginServiceUri);
ClientResponse response = webResource
.type("application/json")
.post(ClientResponse.class, new Credential("user","pass"));
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
}
List<NewCookie>cookies = response.getCookies();
ClientEndpointConfigurator.setCookies(cookies); <== Store cookies as well as session to ClientEndpointConfigrator class
Message message = response.getEntity(Message.class);
...
}
ClientEndpointConfigrator class has a static list for all cookies which like this:
public class ClientEndpointConfigurator extends ClientEndpointConfig.Configurator {
private static List<NewCookie> cookies = null;
public static void setCookies(List<NewCookie> cookies) {
ClientEndpointConfigurator.cookies = cookies;
}
...
#Override
public void beforeRequest(Map<String, List<String>> headers) {
...
if(null != cookies)
{
List<String> cookieList = new ArrayList<String>();
for(NewCookie cookie: cookies)
{
cookieList.add(cookie.toString());
}
headers.put("Cookie", cookieList);
}
...
}
}
beforeRequest() method will put all cookies to request header. If you inspect the cookieList, you will see:
[JSESSIONID=tvum36z6j2bc1p9uf2gumxguh;Version=1;Path=/rs;Secure]
Things looks prefect.
Finally, create a server end ServerEndpointConfigurator class, and override the modifyHandshake() method to retrieve the session and cookies
public class SpringServerEndpointConfigurator extends ServerEndpointConfig.Configurator {
#Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
super.modifyHandshake(sec, request, response);
httpSession = (HttpSession)request.getHttpSession(); <== **It returns null here!**
...
}
}
}
I can't get my HttpSession back! and if you print headers out, you will see the cookie has been changed:
Cookie: JSESSIONID="tvum36z6j2bc1p9uf2gumxguh";$Path="/rs"
Any one knows what's the reason?
All right, I figured it out, it's because I put WebSocket and Rest to different context handler. Jetty keeps handlers isolate to each other. To share session information, you have to put them together.
But if someone does want to separate them, it is still possible done by sharing SessionManager or SessionHandler. There are many ways to achieve this, you can inject SessionHandler to each ServletContext or just define it as a static variable and put it on somewhere every one can reach, each way works.

HTTPClient unable to establish route between https and http

I am testing HttpClient 4.2 by hitting a mixture of http and https links.
HttpClient seems to stick with the protocol from the first call. If the first call is http, then all following https calls fail but http calls are fine. And vice versa.
Here is the test code I used.
#Test
public void testNoRedirectMixed() throws ClientProtocolException, IOException {
HttpClient httpclient = new DefaultHttpClient();
httpclient=WebClientDevWrapper.wrapClient(httpclient);
HttpClientParams.setRedirecting(httpclient.getParams(), false);
{
HttpGet httpget = new HttpGet("http://www.hotmail.com");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
assertTrue(EntityUtils.toString(entity).indexOf("com")>0);
}
try {
HttpGet httpget = new HttpGet("https://www.hotmail.com");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
}catch (Exception e) {
e.printStackTrace();
}
{
HttpGet httpget = new HttpGet("http://www.baidu.com");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
assertTrue(EntityUtils.toString(entity).indexOf("com")>0);
}
}
The second request (https) will fail, but the baidu request is fine.
Caused by: org.apache.http.HttpException: Unable to establish route: planned = {s}->https://www.hotmail.com; current = {s}->http://www.hotmail.com
at org.apache.http.impl.client.DefaultRequestDirector.establishRoute(DefaultRequestDirector.java:842)
I also have to disable redirection because hotmail redirects request: http://www.hotmail.com -> https://www.hotmail.com or https://www.hotmail.com -> https://www.live.com. A similar error is thrown in either cases.
The wrapper is shown below. It is used to accept all certificates.
public class WebClientDevWrapper {
public static HttpClient wrapClient(HttpClient base) {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] xcs,
String string) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] xcs,
String string) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = base.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", ssf, 443));
DefaultHttpClient client= new DefaultHttpClient(ccm, base.getParams());
return client;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
HttpClient should be able to manage connections absolutely transparently to the user. This problem is likely to be caused by a regression introduced in the 4.2 release (see HTTPCLIENT-1193).
Use either PoolingConnectionManager or SingleConnectionManager instead of the default one until 4.2.1 version is released.
You are trying to use one connection to communicate to a number of different sites. AFAIR You have to create new connection (== new client) for every unique site.