/* * SslPollHandler.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 2008-2009 Sun Microsystems, Inc. * * Sun Public License Notice * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is included as the file "license.terms", * and also available at http://www.sun.com/ * * The Original Code is from: * Brazil project web application toolkit release 2.3. * The Initial Developer of the Original Code is: suhler. * Portions created by suhler are Copyright (C) Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): suhler. * * Version: 1.3 * Created by suhler on 08/03/05 * Last modified by suhler on 09/01/20 16:07:59 * * Version Histories: * * 1.3 09/01/20-16:07:59 (suhler) * doc updates * . * * 1.2 08/03/06-09:29:45 (suhler) * move to ssl package, code cleanup * * 1.2 70/01/01-00:00:02 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 1 0 handlers/SslPollHandler.java * * 1.1 08/03/05-16:14:40 (suhler) * date and time created 08/03/05 16:14:40 by suhler * */ package sunlabs.brazil.ssl; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.SimpleTimeZone; import java.util.Date; import java.util.Enumeration; import java.util.Locale; import java.util.Properties; import java.util.StringTokenizer; import java.net.HttpURLConnection; import java.net.URL; import java.net.MalformedURLException; import sunlabs.brazil.server.Handler; import sunlabs.brazil.server.Request; import sunlabs.brazil.server.Server; import sunlabs.brazil.util.Format; import sunlabs.brazil.util.http.HttpUtil; import sunlabs.brazil.util.regexp.Regexp; import sunlabs.brazil.session.SessionManager; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; /** * Handler for periodically polling another web site, whose * results are (optionally) added to the server's properties. * This also includes the ability to request URL's on a cron-like schedule. *

* NOTE:
* This version uses the built-in HTTP. It requires Java 1.4 or newer, * doesn't cache connections, and doesn't support per connection proxies. * However, it does support SSL. * See also {@link sunlabs.brazil.handler.PollHandler}. These should be * combined by adding additional protocol support to HttpRequest. *

* UPDATED NOTE:
* The built-in HTTP stack now supports SSL * (see {@link sunlabs.brazil.util.http.HttpsRequest}), * so this class is deprecated. *
* The result of fetching the url is expected to be a text document in * java Properties format. *

* Properties: *

*
url
URL to fetch periodically. * any ${...} constructs are evaluated at each poll, with * the values in the server properties object. If the URL * starts with "/", then the current server is used. *
post
The "post" data, if any. ${...} are evaluates as per * url above. *
headers
A list of white space delimited tokens that refer to * additional HTTP headers that are added onto the polled * request. For each token the server properties * [token].name and [token].value * define a new http header. *
interval
The interval (in seconds) to fetch the url. Defaults * to 10 seconds. If match is specified, this is the * interval used to check for a time/date match. * At each "interval", the current time format is computed, based * on "format", below. If the computed format has not * changed since the previous poll, then no poll is done. * The interval is recalculated after each poll. *
fast
If set, don't wait "interval" before 1st poll. *
prepend
The string to prepend to the properties. * If not supplied no properties are loaded. *
namespace
The namespace to use to store the properties to. * If the sessionTable (see below)parameter is * identical to the sessionTable parameter of * the SetTemplate, then this specifies the * namespace parameter that may be used with * the SetTemplate "namespace" parameter to * obtain the extracted data. Defaults to the "prepend" parameter. *
match
If specified, a regular expression that must match * the current time for this URL to run. The format to match is * specified by the "format" parameter, below. * "EEE-dd-HH-mm" (eg: Thu-Dec, 14, 14:12 pm). *
format
a {@link java.text.SimpleDateFormat date format specifier} * to use for matching "match" * patterns. Defaults to "EE-MM-dd-HH-mm". *
sessionTable *
The name of the SessionManager table to use for * storing values. By default, properties are stored in * server.props. The value should match the sessionTable * used by the {@link sunlabs.brazil.template.SetTemplate} to allow * values obtained by this handler to be accessable from within * templates. *

* If the sessionTable is set, the namespace * value is used to name the table (e.g. the namespace * specified by {@link sunlabs.brazil.template.SetTemplate}. If no * namespace parameter is given, then prepend is used * as the namespace parameter. *

*

* If prepend is specified, the following additional * properties are * created, and added to the properties with the specified prefix. *

*
count.attempts
The total number of polls attemped. *
count.errors
The total number of poll failures. *
error.at
The poll attempt # for the last failure. *
error.msg
The message describing the last failure. *
error.time
The timestamp of the last failure. *
timestamp
The timestamp for the last successful poll. *
* * @author Stephen Uhler * @version %V% SslPollHandler.java 1.3 */ public class SslPollHandler extends Thread implements Handler { public String url; // url to fetch public String post; // post data to send public int interval; // how often to fetch it (ms) long pollCount = 0; // # of times polled long errorCount = 0; // # of times polls failed Server server; // our server. String prefix; // our prefix String prepend; // our prefix in the props table String namespace; // our namespace in the session manager Regexp re; // the re to match the date/time expression String then; // Last time we processed this url String sessionTable; // the "other" session key SimpleDateFormat date; // date format for "cron" matching String tokens; // our tokens in server.props String match; boolean fast; // make first poll withoug waiting HostnameVerifier verify = null; // our hostname verifier /** * Set up the initial configuration, and kick off a thread * to periodically fetch the url. */ public boolean init(Server server, String prefix) { this.server = server; this.prefix = prefix; prepend = server.props.getProperty(prefix + "prepend"); namespace = server.props.getProperty(prefix + "namespace", prepend); url = server.props.getProperty(prefix + "url"); post = server.props.getProperty(prefix + "post"); tokens = server.props.getProperty(prefix + "headers"); fast= (server.props.getProperty(prefix + "fast") != null); sessionTable = server.props.getProperty(prefix + "sessionTable"); server.log(Server.LOG_DIAGNOSTIC, prefix, "Polling: " + url); String format = server.props.getProperty(prefix + "format", "EE-MM-dd-HH-mm"); date = new SimpleDateFormat(format, Locale.US); date.setTimeZone(SimpleTimeZone.getDefault()); if (url == null) { server.log(Server.LOG_WARNING, prefix, "Invalid url"); return false; } /* * work on the match stuff */ match = server.props.getProperty(prefix + "match"); re = null; if (match != null) { try { re = new Regexp(match, true); } catch (Exception e) { server.log(Server.LOG_WARNING, prefix, "Bad expression:" + e); } } Thread poller = (Thread) this; poller.setDaemon(true); poller.start(); return true; } /** * This might allow control over the polling via requests at a later date. * For now, it always returns false. */ public boolean respond(Request request) { return false; } /** * Periodically poll the url, and copy the results into the server * properties. */ public void run() { Properties props; if (sessionTable != null) { props = (Properties) SessionManager.getSession(namespace, sessionTable, Properties.class); props.list(System.out); } else { props = new Properties(); } boolean first = fast; while(true) { interval=10; try { String str = server.props.getProperty(prefix + "interval"); interval = Integer.decode(str).intValue(); } catch (Exception e) {} if (interval < 1) { interval=10; } server.log(Server.LOG_DIAGNOSTIC, prefix, "Sleeping: " + interval); try { Thread.sleep(first ? 2 : interval * 1000); } catch (InterruptedException e) { break; } first = false; if (re != null) { String now = date.format(new Date(System.currentTimeMillis())); server.log(Server.LOG_DIAGNOSTIC, prefix, "Checking: " + now + " matches " + match); if (now.equals(then)) { server.log(Server.LOG_DIAGNOSTIC, prefix, " Already polled at: " + now); continue; } if (re.match(now) == null) { server.log(Server.LOG_DIAGNOSTIC, prefix, now + " match failed"); continue; } then = now; } url = Format.subst(server.props, url); if (url.startsWith("/")) { int port = server.listen.getLocalPort(); url = server.protocol + "://" + server.hostName + (port != 80 ? ":" + port : "") + url; } server.log(Server.LOG_DIAGNOSTIC, prefix, "Polling: " + url); pollCount++; if (sessionTable == null) { props.clear(); } URL target = null; HttpURLConnection con = null; try { target = new URL(url); } catch (MalformedURLException e) { server.log(Server.LOG_WARNING, prefix, "Invalid url: " + e.getMessage()); return; } try { con = (HttpURLConnection) target.openConnection(); } catch (IOException e) { server.log(Server.LOG_WARNING, prefix, "Invalid target " + target + ": " + e.getMessage()); return; } con.addRequestProperty("Via", "Brazil-Poll/" + Server.version); con.setInstanceFollowRedirects(true); con.setUseCaches(false); if (con instanceof HttpsURLConnection) { if (verify == null) { verify = new anyHost(server, prefix); } ((HttpsURLConnection)con).setHostnameVerifier(verify); } if (tokens != null) { StringTokenizer st = new StringTokenizer(tokens); while (st.hasMoreTokens()) { String token = st.nextToken(); String name = server.props.getProperty(token + ".name"); String value = server.props.getProperty(token + ".value"); if (name!=null && value!=null) { con.addRequestProperty(name, value); } } } if (con.getRequestProperty("host") == null) { con.addRequestProperty("host", HttpUtil.extractUrlHost(url)); } try { if (post != null) { con.setDoOutput(true); con.setRequestMethod("POST"); OutputStream out = con.getOutputStream(); out.write(post.getBytes()); out.close(); } else { con.setRequestMethod("GET"); } server.log(Server.LOG_DIAGNOSTIC, prefix, "sending headers: " + con.getRequestProperties()); con.connect(); server.log(Server.LOG_DIAGNOSTIC, prefix, "got headers: " + con.getHeaderFields()); if (prepend != null) { int code = con.getResponseCode(); if (code == 200) { fillProps(props, con); } else if (code > 299) { throw new Exception("Request failed, code: " + code); } } } catch (Exception e) { server.log(Server.LOG_DIAGNOSTIC, prefix, "target error: " + e); errorCount++; if (prepend != null) { props.put(prepend + "error.msg", e.getMessage()); props.put(prepend + "error.time", "" + System.currentTimeMillis()); props.put(prepend + "error.at", "" + pollCount); } } server.log(Server.LOG_DIAGNOSTIC, prefix, " found " + props.size() + " items"); if (prepend != null) { props.put("timestamp", "" + System.currentTimeMillis()); props.put("count.attempts", "" + pollCount); props.put("count.errors", "" + errorCount); } /* * Copy the properties into the server. */ if (prepend != null && sessionTable == null) { Enumeration enumer = props.propertyNames(); while(enumer.hasMoreElements()) { String key = (String) enumer.nextElement(); server.props.put(prepend + key, props.getProperty(key)); } } } } /** * Fill the properties from the Url Connection */ public void fillProps(Properties props, HttpURLConnection target) throws IOException { InputStream in = target.getInputStream(); props.load(in); in.close(); } /** * Verify all host names; log a warning for mismatches. */ static class anyHost implements HostnameVerifier { Server server; String prefix; public anyHost(Server server, String prefix) { this.server = server; this.prefix = prefix; } /** * Allow connection to any host, log a warning for a mis-match */ public boolean verify(String hostname, SSLSession session) { boolean ok = hostname.equals(session.getPeerHost()); server.log(ok? Server.LOG_DIAGNOSTIC : Server.LOG_WARNING, prefix, "Host match: " + hostname + " ?=? " + session.getPeerHost()); return true; } } }