/* * 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.Credentials; import com.eas.client.settings.SettingsConstants; import com.eas.client.threetier.Request; import com.eas.client.threetier.Response; import com.eas.client.threetier.requests.*; import com.eas.client.threetier.requests.ExceptionResponse; import com.eas.concurrent.CallableConsumer; import com.eas.util.BinaryUtils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author mg */ public class PlatypusHttpRequestWriter implements PlatypusRequestVisitor { public static final String J_SECURITY_CHECK_ACTION_NAME = "j_security_check"; public static final String RESPONSE_MISSING_MSG = "%s must have a response."; // protected URL url; protected String sourcePath; protected Map<String, Cookie> cookies; // protected String method = PlatypusHttpConstants.HTTP_METHOD_GET; protected String uriPrefix = "application"; protected List<Map.Entry<String, String>> params = new ArrayList<>(); protected HttpURLConnection conn; protected byte[] responseBody; protected Response response; protected Credentials basicCredentials; protected HttpResult httpResult; public PlatypusHttpRequestWriter(URL aUrl, String aSourcePath, Map<String, Cookie> aCookies, Credentials aBasicCredentials) { super(); url = aUrl; sourcePath = aSourcePath; cookies = aCookies; basicCredentials = aBasicCredentials; } public HttpResult getHttpResult() { return httpResult; } public static class HttpResult { public int responseCode; public String authScheme; public String redirectLocation; public HttpResult(int aResponseCode, String aAuthScheme, String aRedirectLocation) { super(); responseCode = aResponseCode; authScheme = aAuthScheme; redirectLocation = aRedirectLocation; } public void assign(HttpResult aSource) { responseCode = aSource.responseCode; authScheme = aSource.authScheme; redirectLocation = aSource.redirectLocation; } public boolean isOk() { return responseCode == HttpURLConnection.HTTP_OK; } public boolean isUnauthorized() { return (redirectLocation != null && redirectLocation.toLowerCase().contains(J_SECURITY_CHECK_ACTION_NAME)) || (authScheme != null && !authScheme.isEmpty()) || responseCode == HttpURLConnection.HTTP_UNAUTHORIZED; } } private void addCookies(HttpURLConnection aConnection) { addCookies(cookies, aConnection); } public static void addCookies(Map<String, Cookie> aCookies, HttpURLConnection aConnection) { String[] cookiesNames = aCookies.keySet().toArray(new String[]{}); for (String cookieName : cookiesNames) { Cookie cookie = aCookies.get(cookieName); if (cookie != null && cookie.isActual()) { String cookieHeaderValue = cookieName + "=" + cookie.getValue(); aConnection.addRequestProperty(PlatypusHttpConstants.HEADER_COOKIE, cookieHeaderValue); } else { aCookies.remove(cookieName); } } } public static void addBasicAuthentication(HttpURLConnection aConnection, String aLogin, String aPassword) throws UnsupportedEncodingException { if (aLogin != null && !aLogin.isEmpty() && aPassword != null) { Base64.Encoder encoder = Base64.getEncoder(); String requestAuthSting = PlatypusHttpConstants.BASIC_AUTH_NAME + " " + encoder.encodeToString(aLogin.concat(":").concat(aPassword).getBytes(PlatypusHttpConstants.HEADERS_ENCODING_NAME)); aConnection.setRequestProperty(PlatypusHttpConstants.HEADER_AUTHORIZATION, requestAuthSting); } } private void checkedAddBasicAuthentication(HttpURLConnection aConn) throws UnsupportedEncodingException { if (basicCredentials != null) { addBasicAuthentication(aConn, basicCredentials.userName, basicCredentials.password); } } private void pushRequest(Request request, Consumer<HttpURLConnection> onHeaders) throws Exception { CallableConsumer<HttpResult, Consumer<HttpURLConnection>> performer = (Consumer<HttpURLConnection> lonHeaders) -> { URL rqUrl = new URL(assembleUrl()); // create a connection conn = (HttpURLConnection) rqUrl.openConnection(); // setup the connection conn.setDefaultUseCaches(false); conn.setInstanceFollowRedirects(false); conn.setDoOutput(true); conn.setDoInput(true); // add headers checkedAddBasicAuthentication(conn); conn.setRequestProperty(PlatypusHttpConstants.HEADER_USER_AGENT, PlatypusHttpConstants.AGENT_NAME); addCookies(conn); if (lonHeaders != null) { lonHeaders.accept(conn); } // wait a result HttpResult res = completeRequest(request); conn.disconnect(); return res; }; httpResult = performer.call(onHeaders); } private HttpResult completeRequest(Request aRequest) throws Exception { int responseCode = conn.getResponseCode(); String authScheme = null; String redirectLocation = null; if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { authScheme = conn.getHeaderField(PlatypusHttpConstants.HEADER_WWW_AUTH); response = new AccessControlExceptionResponse(); ((AccessControlExceptionResponse) response).setErrorMessage(conn.getResponseCode() + " " + conn.getResponseMessage()); ((AccessControlExceptionResponse) response).setNotLoggedIn(true); } else if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP && conn.getHeaderField(PlatypusHttpConstants.HEADER_LOCATION) != null && conn.getHeaderField(PlatypusHttpConstants.HEADER_LOCATION).toLowerCase().contains(PlatypusHttpConstants.SECURITY_REDIRECT_LOCATION.toLowerCase())) { redirectLocation = PlatypusHttpConstants.SECURITY_REDIRECT_LOCATION; authScheme = PlatypusHttpConstants.FORM_AUTH_NAME; response = new AccessControlExceptionResponse(); ((AccessControlExceptionResponse) response).setErrorMessage(conn.getResponseCode() + " " + conn.getResponseMessage()); ((AccessControlExceptionResponse) response).setNotLoggedIn(true); } else if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { try (InputStream in = conn.getInputStream()) { responseBody = BinaryUtils.readStream(in, -1); if (checkIfSecirutyForm(responseBody)) { redirectLocation = PlatypusHttpConstants.SECURITY_REDIRECT_LOCATION; authScheme = PlatypusHttpConstants.FORM_AUTH_NAME; response = new AccessControlExceptionResponse(); ((AccessControlExceptionResponse) response).setErrorMessage(HttpURLConnection.HTTP_UNAUTHORIZED + ""); ((AccessControlExceptionResponse) response).setNotLoggedIn(true); } else { // background bio waiting thread will enqueue responseBody reading to a working thread PlatypusResponsesFactory responseFactory = new PlatypusResponsesFactory(); aRequest.accept(responseFactory); response = responseFactory.getResponse(); } } } else if (responseCode == HttpURLConnection.HTTP_CONFLICT && conn.getContentType().toLowerCase().startsWith("application/json")) { response = new JsonExceptionResponse(); try (InputStream in = conn.getErrorStream()) { responseBody = BinaryUtils.readStream(in, -1); } } else { Logger.getLogger(PlatypusHttpRequestWriter.class.getName()).log(Level.SEVERE, String.format("Server error %d. %s", conn.getResponseCode(), conn.getResponseMessage())); response = new ExceptionResponse(); ((ExceptionResponse) response).setErrorMessage(conn.getResponseCode() + " " + conn.getResponseMessage()); } return new HttpResult(responseCode, authScheme, redirectLocation); } public boolean checkIfSecirutyForm(byte[] aContent) throws IOException { String contentType = conn.getContentType(); if ("text/html".equalsIgnoreCase(contentType)) { String formContent = extractText(aContent); return formContent.toLowerCase().contains(PlatypusHttpRequestWriter.J_SECURITY_CHECK_ACTION_NAME); } else { return false; } } protected String extractText(byte[] aContent) throws IOException { String contentType = conn.getContentType(); String[] contentTypeCharset = contentType.split(";"); if (contentTypeCharset == null || contentTypeCharset.length == 0) { throw new IOException("Response must contain ContentType header with charset"); } if (!contentTypeCharset[0].toLowerCase().startsWith("text/") && !contentTypeCharset[0].toLowerCase().startsWith("application/json")) { throw new IOException("Response header 'ContentType' must be text/... or application/json"); } if (contentTypeCharset.length > 1) { String[] charsetNameValue = contentTypeCharset[1].split("="); if (charsetNameValue == null || charsetNameValue.length != 2) { throw new IOException("Response must contain ContentType header with charset=... clause"); } if (!charsetNameValue[0].equalsIgnoreCase("charset")) { throw new IOException("Response ContentType must be formatted as following: text/...;charset=..."); } return new String(aContent, charsetNameValue[1].trim()); } else { return new String(aContent, SettingsConstants.COMMON_ENCODING); } } private String assembleUrl() throws UnsupportedEncodingException { StringBuilder urlBuilder = new StringBuilder(); urlBuilder.append(url); if (!url.toString().endsWith("/")) { urlBuilder.append("/"); } if (uriPrefix != null) { urlBuilder.append(uriPrefix); } if (params.size() > 0) { urlBuilder.append("?"); int paramsCount = 0; for (Map.Entry<String, String> entry : params) { if (entry.getValue() != null) { urlBuilder.append(entry.getKey()).append("=").append(URLEncoder.encode(String.valueOf(entry.getValue()), SettingsConstants.COMMON_ENCODING)); if (paramsCount < params.size() - 1) { urlBuilder.append("&"); } } paramsCount++; } } return urlBuilder.toString(); } @Override public void visit(AppQueryRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.QUERY_ID, rq.getQueryName())); pushRequest(rq, rq.getTimeStamp() != null ? (HttpURLConnection aConn) -> { aConn.setIfModifiedSince(rq.getTimeStamp().getTime()); } : null); } @Override public void visit(ModuleStructureRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.MODULE_NAME, rq.getModuleName())); pushRequest(rq, null); } @Override public void visit(ResourceRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; String encodedResourceName = (new URI(null, null, sourcePath != null && !sourcePath.isEmpty() ? sourcePath + "/" + rq.getResourceName() : rq.getResourceName(), null)).toASCIIString(); String oldUriPrefix = uriPrefix; uriPrefix = encodedResourceName; try { pushRequest(rq, rq.getTimeStamp() != null ? (HttpURLConnection aConn) -> { aConn.setIfModifiedSince(rq.getTimeStamp().getTime()); } : null); } finally { uriPrefix = oldUriPrefix; } } @Override public void visit(CommitRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_POST; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); pushRequest(rq, (HttpURLConnection aConn) -> { aConn.setRequestProperty(PlatypusHttpConstants.HEADER_CONTENTTYPE, PlatypusHttpConstants.JSON_CONTENT_TYPE + ";charset=" + SettingsConstants.COMMON_ENCODING); try (OutputStream out = aConn.getOutputStream()) { byte[] bodyContent = rq.getChangesJson().getBytes(SettingsConstants.COMMON_ENCODING); out.write(bodyContent); } catch (IOException ex) { Logger.getLogger(PlatypusHttpRequestWriter.class.getName()).log(Level.SEVERE, null, ex); } }); } @Override public void visit(ServerModuleStructureRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.MODULE_NAME, rq.getModuleName())); pushRequest(rq, rq.getTimeStamp() != null ? (HttpURLConnection aConn) -> { aConn.setIfModifiedSince(rq.getTimeStamp().getTime()); } : null); } @Override public void visit(DisposeServerModuleRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.MODULE_NAME, rq.getModuleName())); pushRequest(rq, null); } @Override public void visit(ExecuteQueryRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.QUERY_ID, rq.getQueryName())); params.addAll(rq.getParamsJsons().entrySet()); pushRequest(rq, null); } @Override public void visit(RPCRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.MODULE_NAME, rq.getModuleName())); params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.METHOD_NAME, rq.getMethodName())); for (String argJson : rq.getArgumentsJsons()) { params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.PARAMS_ARRAY, argJson)); } pushRequest(rq, null); } @Override public void visit(LogoutRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); pushRequest(rq, null); } @Override public void visit(CredentialRequest rq) throws Exception { method = PlatypusHttpConstants.HTTP_METHOD_GET; params.add(new AbstractMap.SimpleEntry<>(PlatypusHttpRequestParams.TYPE, "" + rq.getType())); pushRequest(rq, null); } }