/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.eas.client.threetier.http; import com.eas.client.login.AnonymousPlatypusPrincipal; import com.eas.client.login.Credentials; import com.eas.client.login.PlatypusPrincipal; import com.eas.client.report.Report; import com.eas.client.settings.SettingsConstants; import com.eas.client.threetier.PlatypusClient; import com.eas.client.threetier.requests.ExceptionResponse; import com.eas.client.threetier.PlatypusConnection; import com.eas.client.threetier.Request; import com.eas.client.threetier.Response; import com.eas.client.threetier.requests.LogoutRequest; import com.eas.client.threetier.requests.RPCRequest; import com.eas.concurrent.PlatypusThreadFactory; import com.eas.script.Scripts; import com.eas.util.BinaryUtils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLEncoder; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; /** * * @author kl, mg refactoring */ public class PlatypusHttpConnection extends PlatypusConnection { static { try { HttpsURLConnection.setDefaultHostnameVerifier((String aHostName, SSLSession aSslSession) -> aHostName.equalsIgnoreCase(aSslSession.getPeerHost())); HttpsURLConnection.setDefaultSSLSocketFactory(createSSLContext().getSocketFactory()); } catch (NoSuchAlgorithmException | KeyManagementException | NoSuchProviderException | KeyStoreException | CertificateException | UnrecoverableKeyException | URISyntaxException | IOException ex) { Logger.getLogger(PlatypusClient.class.getName()).log(Level.SEVERE, null, ex); } } protected Map<String, Cookie> cookies = new HashMap<>(); private final ThreadPoolExecutor bioExecutor; protected boolean basicSchemeMet = false; public PlatypusHttpConnection(URL aUrl, String aSourcePath, Callable<Credentials> aOnCredentials, int aMaximumAuthenticateAttempts, int aMaximumBIOThreads) throws Exception { super(aUrl, aSourcePath, aOnCredentials, aMaximumAuthenticateAttempts); bioExecutor = new ThreadPoolExecutor(aMaximumBIOThreads, aMaximumBIOThreads, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new PlatypusThreadFactory("http-client-", false)); bioExecutor.allowCoreThreadTimeOut(true); } public void acceptCookies(HttpURLConnection aConnection) throws ParseException, NumberFormatException { Map<String, List<String>> headers = aConnection.getHeaderFields(); List<String> cookieHeaders = headers.get(PlatypusHttpConstants.HEADER_SETCOOKIE); if (cookieHeaders != null) { cookieHeaders.stream().forEach((setCookieHeaderValue) -> { try { Cookie cookie = Cookie.parse(setCookieHeaderValue); cookies.put(cookie.getName(), cookie); } catch (ParseException | NumberFormatException ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, ex.getMessage()); } }); } } @Override public <R extends Response> void enqueueRequest(Request aRequest, Scripts.Space aSpace, Consumer<R> onSuccess, Consumer<Exception> onFailure) throws Exception { if (Scripts.getContext() != null) { Scripts.getContext().incAsyncsCount(); } Consumer<PlatypusHttpRequestWriter> responseHandler = (PlatypusHttpRequestWriter aHttpSender) -> { if (aHttpSender.response instanceof ExceptionResponse) { if (onFailure != null) { try { PlatypusHttpResponseReader reader = new PlatypusHttpResponseReader(aRequest, aHttpSender.conn, aHttpSender.responseBody); aHttpSender.response.accept(reader); Exception cause = handleErrorResponse((ExceptionResponse) aHttpSender.response, aSpace); onFailure.accept(cause); } catch (Exception ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } } } else { if (aRequest instanceof LogoutRequest) { cookies.clear(); credentials = null; } if (onSuccess != null) { try { // Response reading in a working thread because of BIO nature of http client :( PlatypusHttpResponseReader reader = new PlatypusHttpResponseReader(aRequest, aHttpSender.conn, aHttpSender.responseBody); aHttpSender.response.accept(reader); if (aHttpSender.response instanceof RPCRequest.Response && ((RPCRequest.Response) aHttpSender.response).getResult() instanceof URL) { RPCRequest.Response rpcResponse = (RPCRequest.Response) aHttpSender.response; fetchReport(rpcResponse, aSpace, onSuccess, onFailure); } else { onSuccess.accept((R) aHttpSender.response); } } catch (Exception ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } } } }; Consumer<Consumer<PlatypusHttpRequestWriter>> requestPusher = (Consumer<PlatypusHttpRequestWriter> aOnComplete) -> { Map<String, Cookie> localCookies = new HashMap<>(); localCookies.putAll(cookies); Scripts.LocalContext context = Scripts.getContext(); bioExecutor.submit(() -> { Scripts.setContext(context); try { PlatypusHttpRequestWriter httpSender = new PlatypusHttpRequestWriter(url, sourcePath, localCookies, basicSchemeMet ? new Credentials(credentials.userName, credentials.password) : null); aRequest.accept(httpSender);// bio in a background thread aSpace.process(() -> { try { acceptCookies(httpSender.conn); aOnComplete.accept(httpSender); } catch (ParseException | NumberFormatException ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } }); } catch (Exception ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } finally { Scripts.setContext(null); } }); }; Attempts attempts = new Attempts(); retryRequest(requestPusher, aSpace, responseHandler, attempts); } private <R extends Response> void fetchReport(RPCRequest.Response rpcResponse, Scripts.Space aSpace, Consumer<R> onSuccess, Consumer<Exception> onFailure) { URL reportUrl = (URL) rpcResponse.getResult(); String reportLocation = reportUrl.getFile(); Scripts.LocalContext context = Scripts.getContext(); bioExecutor.submit(() -> {// bio background thread Scripts.setContext(context); try { HttpURLConnection reportConn = (HttpURLConnection) reportUrl.openConnection(); reportConn.setDoInput(true); PlatypusHttpRequestWriter.addCookies(cookies, reportConn); if (basicSchemeMet) { PlatypusHttpRequestWriter.addBasicAuthentication(reportConn, credentials.userName, credentials.password); } try (InputStream in = reportConn.getInputStream()) { byte[] content = BinaryUtils.readStream(in, -1); int slashIdx = reportLocation.lastIndexOf("/"); String fileName = reportLocation.substring(slashIdx + 1); if (fileName.contains(".")) { String[] nameFormat = fileName.split("\\."); Report report = new Report(content, nameFormat[nameFormat.length - 1], nameFormat[0]); rpcResponse.setResult(report); } else { Report report = new Report(content, "unknown", fileName); rpcResponse.setResult(report); } } aSpace.process(() -> {// worker thread try { acceptCookies(reportConn); onSuccess.accept((R) rpcResponse); } catch (ParseException | NumberFormatException ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } }); } catch (IOException ex) { if (onFailure != null) { aSpace.process(() -> {// worker thread onFailure.accept(ex); }); } else { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } } finally { Scripts.setContext(null); } }); } private void retryRequest(Consumer<Consumer<PlatypusHttpRequestWriter>> requestPusher, Scripts.Space aSpace, Consumer<PlatypusHttpRequestWriter> responseHandler, Attempts attempts) { Credentials sentCreds = credentials; requestPusher.accept((PlatypusHttpRequestWriter resender) -> { PlatypusHttpRequestWriter.HttpResult nextResendedRes = resender.getHttpResult(); if (nextResendedRes.isUnauthorized()) { if (attempts.count++ < maximumAuthenticateAttempts) { try { if (credentials != null && !credentials.equals(sentCreds)) { // retry retryRequest(requestPusher, aSpace, responseHandler, attempts); } else { Credentials creds = onCredentials.call(); if (creds != null) { credentials = creds; if (nextResendedRes.authScheme.toLowerCase().contains(PlatypusHttpConstants.BASIC_AUTH_NAME.toLowerCase())) { basicSchemeMet = true; // retry retryRequest(requestPusher, aSpace, responseHandler, attempts); } else if (PlatypusHttpConstants.FORM_AUTH_NAME.equalsIgnoreCase(nextResendedRes.authScheme)) { String redirectLocation = nextResendedRes.redirectLocation; Map<String, Cookie> localCookies = new HashMap<>(); localCookies.putAll(cookies); Scripts.LocalContext context = Scripts.getContext(); bioExecutor.submit(() -> {// bio background thread Scripts.setContext(context); try { URL securityFormUrl = new URL(url + (url.toString().endsWith("/") ? "" : "/") + redirectLocation); HttpURLConnection securityFormConn = (HttpURLConnection) securityFormUrl.openConnection(); securityFormConn.setInstanceFollowRedirects(false); securityFormConn.setDoOutput(true); securityFormConn.setRequestMethod(PlatypusHttpConstants.HTTP_METHOD_POST); securityFormConn.setRequestProperty(PlatypusHttpConstants.HEADER_CONTENTTYPE, PlatypusHttpConstants.FORM_CONTENT_TYPE); PlatypusHttpRequestWriter.addCookies(localCookies, securityFormConn); String formData = PlatypusHttpConstants.SECURITY_CHECK_USER + "=" + URLEncoder.encode(credentials.userName, SettingsConstants.COMMON_ENCODING) + "&" + PlatypusHttpConstants.SECURITY_CHECK_PASSWORD + "=" + URLEncoder.encode(credentials.password, SettingsConstants.COMMON_ENCODING); byte[] formDataConent = formData.getBytes(SettingsConstants.COMMON_ENCODING); securityFormConn.setRequestProperty(PlatypusHttpConstants.HEADER_CONTENTLENGTH, "" + formDataConent.length); try (OutputStream out = securityFormConn.getOutputStream()) { out.write(formDataConent); } int responseCode = securityFormConn.getResponseCode(); aSpace.process(() -> { try { acceptCookies(securityFormConn); // retry retryRequest(requestPusher, aSpace, responseHandler, attempts); } catch (ParseException | NumberFormatException ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } }); } catch (Exception ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } finally { Scripts.setContext(null); } }); } else { Logger.getLogger(PlatypusHttpRequestWriter.class.getName()).log(Level.SEVERE, "Unsupported authorization scheme: {0}", nextResendedRes.authScheme); } } else {// Credentials are inaccessible, so leave things as is... responseHandler.accept(resender); } } } catch (Exception ex) { Logger.getLogger(PlatypusHttpConnection.class.getName()).log(Level.SEVERE, null, ex); } } else {// Maximum authentication attempts per request exceeded, so leave things as is... responseHandler.accept(resender); } } else { PlatypusPrincipal.setClientSpacePrincipal(credentials != null ? new PlatypusPrincipal(credentials.userName, null, null, this) : new AnonymousPlatypusPrincipal()); responseHandler.accept(resender); } }); } @Override public <R extends Response> R executeRequest(Request aRequest) throws Exception { PlatypusHttpRequestWriter httpSender = new PlatypusHttpRequestWriter(url, sourcePath, cookies, basicSchemeMet ? credentials : null); aRequest.accept(httpSender);// bio acceptCookies(httpSender.conn); int authenticationAttempts = 0; PlatypusHttpRequestWriter.HttpResult res = httpSender.httpResult; while (res.isUnauthorized() && onCredentials != null && authenticationAttempts++ < maximumAuthenticateAttempts) { credentials = onCredentials.call(); if (res.authScheme.toLowerCase().contains(PlatypusHttpConstants.BASIC_AUTH_NAME.toLowerCase())) { basicSchemeMet = true; httpSender = new PlatypusHttpRequestWriter(url, sourcePath, cookies, credentials); aRequest.accept(httpSender);// bio acceptCookies(httpSender.conn); } else if (PlatypusHttpConstants.FORM_AUTH_NAME.equalsIgnoreCase(res.authScheme)) { String redirectLocation = res.redirectLocation; URL securityFormUrl = new URL(url + (url.toString().endsWith("/") ? "" : "/") + redirectLocation); HttpURLConnection securityFormConn = (HttpURLConnection) securityFormUrl.openConnection(); securityFormConn.setInstanceFollowRedirects(false); securityFormConn.setDoOutput(true); securityFormConn.setRequestMethod(PlatypusHttpConstants.HTTP_METHOD_POST); securityFormConn.setRequestProperty(PlatypusHttpConstants.HEADER_CONTENTTYPE, PlatypusHttpConstants.FORM_CONTENT_TYPE); PlatypusHttpRequestWriter.addCookies(cookies, securityFormConn); String formData = PlatypusHttpConstants.SECURITY_CHECK_USER + "=" + URLEncoder.encode(credentials.userName, SettingsConstants.COMMON_ENCODING) + "&" + PlatypusHttpConstants.SECURITY_CHECK_PASSWORD + "=" + URLEncoder.encode(credentials.password, SettingsConstants.COMMON_ENCODING); byte[] formDataConent = formData.getBytes(SettingsConstants.COMMON_ENCODING); securityFormConn.setRequestProperty(PlatypusHttpConstants.HEADER_CONTENTLENGTH, "" + formDataConent.length); try (OutputStream out = securityFormConn.getOutputStream()) { out.write(formDataConent); } int responseCode = securityFormConn.getResponseCode(); acceptCookies(securityFormConn); httpSender = new PlatypusHttpRequestWriter(url, sourcePath, cookies, null); aRequest.accept(httpSender);// bio acceptCookies(httpSender.conn); } else { Logger.getLogger(PlatypusHttpRequestWriter.class.getName()).log(Level.SEVERE, "Unsupported authorization scheme: {0}", res.authScheme); } } if (httpSender.response instanceof ExceptionResponse) { throw handleErrorResponse((ExceptionResponse) httpSender.response, Scripts.getSpace()); } else { PlatypusHttpResponseReader reader = new PlatypusHttpResponseReader(aRequest, httpSender.conn, httpSender.responseBody); httpSender.response.accept(reader); if (aRequest instanceof LogoutRequest) { cookies.clear(); credentials = null; } else if (httpSender.response instanceof RPCRequest.Response) { RPCRequest.Response rpcResponse = (RPCRequest.Response) httpSender.response; if (rpcResponse.getResult() instanceof URL) { URL reportUrl = (URL) rpcResponse.getResult(); String reportLocation = reportUrl.getFile(); HttpURLConnection reportConn = (HttpURLConnection) reportUrl.openConnection(); reportConn.setDoInput(true); PlatypusHttpRequestWriter.addCookies(cookies, reportConn); if (basicSchemeMet) { PlatypusHttpRequestWriter.addBasicAuthentication(reportConn, credentials.userName, credentials.password); } try (InputStream in = reportConn.getInputStream()) { byte[] content = BinaryUtils.readStream(in, -1); int slashIdx = reportLocation.lastIndexOf("/"); String fileName = reportLocation.substring(slashIdx + 1); if (fileName.contains(".")) { String[] nameFormat = fileName.split("\\."); Report report = new Report(content, nameFormat[nameFormat.length - 1], nameFormat[0]); rpcResponse.setResult(report); } else { Report report = new Report(content, "unknown", fileName); rpcResponse.setResult(report); } } acceptCookies(reportConn); } } return (R) httpSender.response; } } @Override public void shutdown() { bioExecutor.shutdown(); } }