Java tutorial
/****************************************************************************** * LZHttpUtils.java * ****************************************************************************/ /* J_LZ_COPYRIGHT_BEGIN ******************************************************* * Copyright 2001-2008, 2010, 2011 Laszlo Systems, Inc. All Rights Reserved. * * Use is subject to license terms. * * J_LZ_COPYRIGHT_END *********************************************************/ package org.openlaszlo.utils; import java.io.File; import java.net.URL; import java.util.Locale; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.httpclient.HttpMethodBase; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.URI; import org.apache.commons.httpclient.URIException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.List; import java.util.HashMap; import java.util.StringTokenizer; import java.util.TimeZone; import java.util.Date; import java.util.Enumeration; import java.security.*; import org.apache.log4j.*; import org.openlaszlo.utils.ChainedException; /** * Utility class for http servlets */ public class LZHttpUtils { private static Logger mLogger = Logger.getLogger(LZHttpUtils.class); public static final String CONTENT_ENCODING = "Content-Encoding"; public static final String CONTENT_LENGTH = "Content-Length"; public static final String CONTENT_TYPE = "Content-Type"; public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; public static final String LAST_MODIFIED = "Last-Modified"; public static final String IF_NONE_MATCH = "If-None-Match"; public static final String TRANSFER_ENCODING = "Transfer-Encoding"; public static final String HOST = "Host"; public static final String CONNECTION = "Connection"; public static final String AUTHORIZATION = "Authorization"; public static final String COOKIE = "Cookie"; public static final String CACHE_CONTROL = "Cache-Control"; public static final String USER_AGENT = "User-Agent"; public static final String ACCEPT_ENCODING = "Accept-Encoding"; public static final String RANGE = "Range"; public static final String ACCEPT_RANGES = "Accept-Ranges"; public static final String IF_RANGE = "If-Range"; public static final String NO_STORE = "no-store"; public static final String NO_CACHE = "no-cache"; /** * @return the URL for the request // with the query string? * @param req the request */ public static URL getRequestURL(HttpServletRequest req) { StringBuffer surl = req.getRequestURL(); if (surl.indexOf("https") == 0) { try { System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol"); Class<?> provClass = Class.forName("com.sun.net.ssl.internal.ssl.Provider"); Provider provider = (Provider) provClass.newInstance(); Security.addProvider(provider); } catch (InstantiationException e) { throw new ChainedException(e); } catch (IllegalAccessException e) { throw new ChainedException(e); } catch (ClassNotFoundException e) { throw new ChainedException(e); } } // surl.append("?"); // surl.append(req.getQueryString()); try { return new URL(surl.toString()); } catch (Exception e) { throw new ChainedException(e); } } /** * For formatting HTTP dates */ /** * Return a formatter for HTTP date headers */ private static SimpleDateFormat getGMTFormatter() { SimpleDateFormat dateFormatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", java.util.Locale.US); TimeZone tz = TimeZone.getTimeZone("GMT"); dateFormatter.setTimeZone(tz); return dateFormatter; } /** * Convert a long utc value to an HTTP Date String (TZ must be GMT) */ public static String getDateString(long d) { SimpleDateFormat dateFormatter = getGMTFormatter(); return dateFormatter.format(new Date(d)); } /** * Convert an HTTP Date String to a long * @return the long or -1 if the string doesn't parse correctly. */ public static long getDate(String s) { if (s == null || "".equals(s)) { return -1; } try { SimpleDateFormat dateFormatter = getGMTFormatter(); return dateFormatter.parse(s).getTime(); } catch (java.text.ParseException e) { mLogger.warn( /* (non-Javadoc) * @i18n.test * @org-mes="bad date string" */ org.openlaszlo.i18n.LaszloMessages.getMessage(LZHttpUtils.class.getName(), "051018-136"), e); return -1; } } /** From RFC2616, 14.10: * * HTTP/1.1 proxies MUST parse the Connection header field before a message * is forwarded and, for each connection-token in this field, remove any * header field(s) from the message with the same name as the * connection-token. Connection options are signaled by the presence of a * connection-token in the Connection header field, not by any corresponding * additional header field(s), since the additional header field may not be * sent if there are no parameters associated with that connection * option. */ static public boolean allowForward(String header, Enumeration<?> connEnum) { if (header.toLowerCase(Locale.ENGLISH).startsWith("content-")) return false; if (header.equalsIgnoreCase(CONNECTION)) return false; if (header.equalsIgnoreCase(HOST)) return false; if (header.equalsIgnoreCase(TRANSFER_ENCODING)) return false; if (header.equalsIgnoreCase(IF_MODIFIED_SINCE)) return false; if (header.equalsIgnoreCase(LAST_MODIFIED)) return false; if (header.equalsIgnoreCase(IF_NONE_MATCH)) return false; if (header.equalsIgnoreCase(ACCEPT_ENCODING)) return false; // Someday we may allow these only when the proxy is non-transcoding if (header.equalsIgnoreCase(RANGE)) return false; // Someday we may allow these only when the proxy is non-transcoding if (header.equalsIgnoreCase(ACCEPT_RANGES)) return false; // Someday we may allow these only when the proxy is non-transcoding if (header.equalsIgnoreCase(IF_RANGE)) return false; // Don't forward any headers that have the same name as a connection // token. if (connEnum != null) { while (connEnum.hasMoreElements()) { String token = (String) connEnum.nextElement(); if (header.equalsIgnoreCase(token)) return false; } } return true; } /** Add request headers into method. * * @param req http servlet request object * @param method method to insert request headers into */ static public void proxyRequestHeaders(HttpServletRequest req, HttpMethodBase method) { mLogger.debug("proxyRequestHeaders"); // Copy all headers, if the servlet container allows, otherwise just // copy the cookie header and log a message. Enumeration<?> headerNames = req.getHeaderNames(); if (headerNames != null) { // Connection header tokens not to forward Enumeration<?> connEnum = req.getHeaders(CONNECTION); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); if (allowForward(key, connEnum)) { String val = (String) req.getHeader(key); method.addRequestHeader(key, val); mLogger.debug(" " + key + "=" + val); } } } else { mLogger.warn( /* (non-Javadoc) * @i18n.test * @org-mes="Can't get header names to proxy request headers" */ org.openlaszlo.i18n.LaszloMessages.getMessage(LZHttpUtils.class.getName(), "051018-237")); Enumeration<?> cookieEnum = req.getHeaders(COOKIE); if (cookieEnum != null) { while (cookieEnum.hasMoreElements()) { String val = (String) cookieEnum.nextElement(); method.addRequestHeader(COOKIE, val); mLogger.debug(" Cookie=" + val); } } Enumeration<?> authEnum = req.getHeaders(AUTHORIZATION); if (authEnum != null) { while (authEnum.hasMoreElements()) { String val = (String) authEnum.nextElement(); method.addRequestHeader(AUTHORIZATION, val); mLogger.debug(" Authorization=" + val); } } } } /** Pull response headers from method and put into * servlet response object. * * @param method method to proxy from * @param res http servlet response object to proxy to * @param isSecure true if get method is secure */ static public void proxyResponseHeaders(HttpMethodBase meth, HttpServletResponse res, boolean isSecure) { mLogger.debug("proxyResponseHeaders"); Header[] hedz = meth.getResponseHeaders(); for (int i = 0; i < hedz.length; i++) { String name = hedz[i].getName(); String value = hedz[i].getValue(); // Content length passed back to swf app will be different if (allowForward(name, null)) { // Don't send no-cache headers request is SSL; IE 6 has // problems. if (isSecure) { if (name.equals("Pragma") && value.equals("no-cache")) continue; if (name.equals("Cache-Control") && value.equals("no-cache")) continue; } mLogger.debug(" " + name + "=" + value); try { if (name.equals("Date") || name.equals("Server")) { res.setHeader(name, value); } else { res.addHeader(name, value); } } catch (Exception e) { mLogger.error( /* (non-Javadoc) * @i18n.test * @org-mes="Exception when proxying a response header: " + p[0] */ org.openlaszlo.i18n.LaszloMessages.getMessage(LZHttpUtils.class.getName(), "051018-304", new Object[] { e.getMessage() })); } } } } /** * Fetch cookie value for a particular cookie name. * * @param req servlet request. * @param name name of cookie key to fetch. */ static public String getCookie(HttpServletRequest req, String name) { javax.servlet.http.Cookie[] cookies = req.getCookies(); if (cookies != null) { for (int i = 0; i < cookies.length; i++) { javax.servlet.http.Cookie cookie = cookies[i]; if (cookie.getName().equals(name)) { return cookie.getValue(); } } } return null; } /** * Replace real path forward slash characters to back-slash for Windoze. * This is to get around a WebSphere problem (see bug 988). Note that if the * web application content is being served directly from a .war file, this * method will return null. See ServletContext.getRealPath() for more * details. * * @param ctxt servlet context * @param path virtual webapp path to resolve into a real path * @return the real path, or null if the translation cannot be performed */ static public String getRealPath(ServletContext ctxt, String path) { String realPath = ctxt.getRealPath(path); if (realPath != null && File.separatorChar == '\\') realPath = realPath.replace('/', '\\'); try { return new File(realPath).getCanonicalPath(); } catch (java.io.IOException e) { throw new org.openlaszlo.utils.ChainedException(e); } } /** * Replace real path forward slash characters to back-slash for Windoze. * * @param ctxt servlet context * @param req generating request * @return the real path, or null if the translation cannot be performed */ static public String getRealPath(ServletContext ctxt, HttpServletRequest req) { String uriwithoutcontext = req.getRequestURI().substring(req.getContextPath().length()); if (uriwithoutcontext != null && File.separatorChar == '\\') { uriwithoutcontext = uriwithoutcontext.replace('/', '\\'); } return getRealPath(ctxt, "/") + uriwithoutcontext; } private static String WEBAPP = "/@WEBAPP@/"; /** * If a URL contains <code>/@WEBAPP@</code>, replaces that string with * context path. If context path is <code>/</code>, the function just * removes the <code>/@WEBAPP@</code> string. * * @param url URL to check if <code>/@WEBAPP@</code> token exists. * @return if <code>/@WEBAPP@</code> exists, new modified URL else old URL. */ public static String modifyWEBAPP(HttpServletRequest req, String url) { mLogger.debug("modifyWEBAPP"); if (url.startsWith(WEBAPP)) { mLogger.debug(" Old URL: " + url); String protocol = (req.isSecure() ? "https" : "http"); String host = req.getServerName(); int port = req.getServerPort(); String cp = req.getContextPath(); url = protocol + "://" + host + ":" + port + cp + url.substring(WEBAPP.length() - 1); mLogger.debug(" New URL: " + url); } return url; } /** * Mark response with no-store cache control */ static public void noStore(HttpServletResponse res) { if (res.containsHeader(LZHttpUtils.CACHE_CONTROL)) { mLogger.warn( /* (non-Javadoc) * @i18n.test * @org-mes="over-riding back-end cache-control header to: no-store" */ org.openlaszlo.i18n.LaszloMessages.getMessage(LZHttpUtils.class.getName(), "051018-408")); } res.setHeader("Cache-Control", "cache, must-revalidate"); res.setHeader("Pragma", "public"); // res.setHeader(CACHE_CONTROL, NO_STORE); } /** * Return a URI object, escaping input only if needed */ static public URI newURI(String s) throws URIException { try { return new URI(s, true); } catch (URIException urie) { // Try escaping try { return new URI(s, false); } catch (Exception e) { // Escaping failed, throw the original error throw urie; } } } /** Parse out options value from lzoptions, of the form ?lzoptions=runtime(dhtml),wrapper(html),debug(false),proxy(true) For boolean options, we default that if they are present they are true, if not they are false so: ?lzoptions=runtime(dhtml),wrapper(html),proxy would be the same as the above. multiple comma separated values can be passed in args list, e.g., ?lzoptions=runtime(swf10),package(widget,android) @return hashmap of key=>String */ private static final int KEY = 1; private static final int ARGS = 2; public static HashMap<String, String> getLzRequestOptions(String query) { HashMap<String, String> options = new HashMap<String, String>(); int mystate = KEY; StringBuilder vals = null; String lastkey = null; int nvals = 0; StringTokenizer st = new StringTokenizer(query, ",()", true); while (st.hasMoreTokens()) { String token = st.nextToken(); switch (mystate) { case KEY: if (token.equals(",")) { // we only saw key name, but no value, so give it an implicit true value. if (lastkey != null && nvals == 0) { options.put(lastkey, "true"); } } else if (token.equals("(")) { mystate = ARGS; vals = new StringBuilder(); } else { lastkey = token; } break; case ARGS: if (token.equals(")")) { options.put(lastkey, vals.toString()); lastkey = null; mystate = KEY; nvals = 0; } else if (token.equals(",")) { vals.append(","); } else { vals.append(token); nvals++; } break; } } if (lastkey != null && nvals == 0) { options.put(lastkey, "true"); } return options; } /* look up KEY and return String containing comma separated list of values */ public static String getLzOption(String key, HashMap<String, String> options) { return options.get(key); } /* Returns list of one or more value for KEY */ public static List<String> getLzOptionList(String key, HashMap<String, String> options) { String vals = (options.get(key)); if (vals == null) { return null; } else { return Arrays.asList(vals.split(",")); } } /* mapping of new lzoption names to old query arg names. Keep this in sync with the map in WEB-INF/lps/lfc/services/LzBrowser.lzs #getLzOption */ static final HashMap<String, String> optionNameMap = new HashMap<String, String>(); static { optionNameMap.put("runtime", "lzr"); optionNameMap.put("backtrace", "lzbacktrace"); optionNameMap.put("proxied", "lzproxied"); optionNameMap.put("usemastersprite", "lzusemastersprite"); } // Look for new style lzoptions, then fall back to old style // discrete query args. // // Since we moved to the 'lzoptions' string, some options have // changed name. We make these substitutions if we're falling // back to look at query args instead of lzoptions string: // // runtime => lzr // backtrace => lzbacktrace // public static String getLzOption(String key, HttpServletRequest req) { String lzoptions = req.getParameter("lzoptions"); String val = null; if (lzoptions != null) { HashMap<String, String> optionsmap = LZHttpUtils.getLzRequestOptions(lzoptions); val = LZHttpUtils.getLzOption(key, optionsmap); if (val != null) { return val; } } // Fallback to looking up key in regular query args // "lzr" is the old way of specifying 'runtime' as a query arg String mkey = optionNameMap.get(key); if (mkey != null) { key = mkey; } return req.getParameter(key); } }