Source code for /engineering/autohit-2003/src/autohit/call/modules/SimpleHttpModule.javaOriginal file SimpleHttpModule.java
   1 /**
   2  * AUTOHIT 2003
   3  * Copyright Erich P Gatejen (c) 1989,1997,2003,2004
   4  * 
   5  * This program is free software; you can redistribute it and/or modify 
   6  * it under the terms of the GNU General Public License as published by 
   7  * the Free Software Foundation; either version 2 of the License, or (at
   8  * your option) any later version.
   9  * This program is distributed in the hope that it will be useful, but
  10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  12  * more details.
  13  * 
  14  * You should have received a copy of the GNU General Public License along
  15  * with this program; if not, write to the Free Software Foundation, Inc.,
  16  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17  *
  18  * Additional license information can be found in the documentation.
  19  * @author Erich P Gatejen
  20  */
  21 package autohit.call.modules;
  22 
  23 import java.io.IOException;
  24 import java.util.Enumeration;
  25 import java.util.Hashtable;
  26 
  27 import org.apache.commons.httpclient.Credentials;
  28 import org.apache.commons.httpclient.HttpClient;
  29 import org.apache.commons.httpclient.HttpException;
  30 import org.apache.commons.httpclient.HttpMethod;
  31 import org.apache.commons.httpclient.HttpState;
  32 import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
  33 import org.apache.commons.httpclient.UsernamePasswordCredentials;
  34 import org.apache.commons.httpclient.cookie.CookiePolicy;
  35 import org.apache.commons.httpclient.methods.GetMethod;
  36 import org.apache.commons.httpclient.methods.PostMethod;
  37 import org.apache.commons.httpclient.protocol.Protocol;
  38 
  39 import autohit.call.CallException;
  40 import autohit.common.AutohitProperties;
  41 import autohit.common.Constants;
  42 import autohit.common.EasySSLProtocolSocketFactory;
  43 
  44 /**
  45  * Simple http module. There is a client/per session at this time. The property
  46  * "wire" sets if the very noisy HttpClient wire logging is turned on or not.
  47  * the most recent instantiation of this module will set the HttpClient property
  48  * for all instances.
  49  * 
  50  * @author Erich P. Gatejen
  51  * @version 1.0 <i>Version History </i> <code>EPG - Initial - 22Jun03<br>
  52  * EPG - Set wire property - 2Sep03</code>
  53  *  
  54  */
  55 public class SimpleHttpModule extends Module {
  56 
  57     private final static String myNAME = "SimpleHttp";
  58 
  59     /**
  60      * METHODS
  61      */
  62     private final static String method_SESSION = "start";
  63     private final static String method_SESSION_1_ADDRESS = "address";
  64     private final static String method_SESSION_2_PORT = "port";
  65     private final static String method_SESSIONHTTPS = "starthttps";
  66     private final static String method_SESSIONHTTPS_1_ADDRESS = "address";
  67     private final static String method_SESSIONHTTPS_2_PORT = "port";
  68     private final static String method_GET = "get";
  69     private final static String method_GET_1_URL = "url";
  70     private final static String method_DONE = "done";
  71     private final static String method_CREDENTIALS = "set_credentials";
  72     private final static String method_CREDENTIALS_1_UID = "uid";
  73     private final static String method_CREDENTIALS_2_PASS = "password";
  74     private final static String method_POST = "post";
  75     private final static String method_POST_1_URL = method_GET_1_URL;
  76     private final static String method_POST_2_TABLE = "table";
  77     private final static String method_TIMEOUT = "timeout";
  78     private final static String method_TIMEOUT_1_MILLIS = "millis";
  79 
  80     private final static int DEFAULT_TIMEOUT = 10000; // 10 seconds
  81     private final static int DEFAULT_HTTP = 80;
  82     private final static int DEFAULT_HTTPS = 443;    
  83     
  84     private HttpClient httpClient;
  85     private Credentials creds;
  86     boolean started;
  87 
  88     /**
  89      * Constructor
  90      */
  91     public SimpleHttpModule() {
  92 
  93     }
  94 
  95     // IMPLEMENTORS
  96 
  97     /**
  98      * Execute a named method. You must implement this method. You can call any
  99      * of the helpers for data and services. The returned object better be a
 100      * string (for now).
 101      * 
 102      * @param name
 103      *            name of the method
 104      * @see autohit.common.NOPair
 105      * @throws CallException
 106      */
 107     public Object execute_chain(String name) throws CallException {
 108 
 109         Object response = null;
 110         String param1;
 111         String param2;
 112         Object thingie;
 113         int port = DEFAULT_HTTP;
 114 
 115         if (name.equals(method_SESSION)) {
 116 
 117             param1 = (String) getParam(method_SESSION_1_ADDRESS);
 118             if (param1 == null) { throw buildException("Serious FAULT while creating session with start method.  Required 'address' parameter not provided",
 119                     CallException.CODE_MODULE_FAULT); }
 120 
 121             // port is optional
 122             param2 = (String) getParam(method_SESSION_2_PORT);
 123             if (param2 != null) {
 124                 try {
 125                     port = Integer.parseInt(param2);
 126                 } catch (Exception e) {
 127                     error("Bad port parameter for start method.  Defaulting to " + DEFAULT_HTTP + "  Errored parameter=" + param2);
 128                     port = DEFAULT_HTTP;
 129                 }
 130             }
 131 
 132             // Do it
 133             this.start(param1, port);
 134             response = Constants.EMPTY_LEFT;
 135 
 136         } else if (name.equals(method_SESSIONHTTPS)) {
 137 
 138             param1 = (String) getParam(method_SESSIONHTTPS_1_ADDRESS);
 139             if (param1 == null) { throw buildException("Serious FAULT while creating session with start method.  Required 'address' parameter not provided",
 140                     CallException.CODE_MODULE_FAULT); }
 141 
 142             // port is optional
 143             param2 = (String) getParam(method_SESSIONHTTPS_2_PORT);
 144             if (param2 != null) {
 145                 try {
 146                     port = Integer.parseInt(param2);
 147                 } catch (Exception e) {
 148                     error("Bad port parameter for start method.  Defaulting to " + DEFAULT_HTTPS + "  Errored parameter=" + param2);
 149                     port = DEFAULT_HTTP;
 150                 }
 151             }
 152 
 153             // Do it
 154             this.starthttps(param1, port);
 155             response = Constants.EMPTY_LEFT;
 156 
 157         } else if (name.equals(method_TIMEOUT)) {
 158             if (started == false) { throw buildException("module:SimpleHttp:Tried to set timeout when a session wasn't started.",
 159                     CallException.CODE_MODULE_FAULT); }
 160             param1 = (String) getParam(method_TIMEOUT_1_MILLIS);
 161             if (param1 == null) {
 162                 error("Missing 'millis' parameter for timeout method.");
 163             } else {
 164                 try {
 165                     httpClient.setConnectionTimeout(Integer.parseInt(param1));
 166                 } catch (Exception e) {
 167                     error("Paramater 'millis' for timeout method is malformed.");
 168                 }
 169             }
 170 
 171         } else if (name.equals(method_GET)) {
 172             param1 = (String) getParam(method_GET_1_URL);
 173             if (param1 == null) {
 174                 error("Missing 'url' parameter for get method.  Aborting get.");
 175             } else {
 176                 response = this.get(param1);
 177             }
 178 
 179         } else if (name.equals(method_POST)) {
 180             param1 = (String) getParam(method_POST_1_URL);
 181             param2 = (String) getParam(method_POST_2_TABLE);
 182             if ((param1 == null) || (param2 == null)) {
 183                 error("Missing parameter for post method.  Aborting post.");
 184             } else {
 185                 thingie = this.getPersist(param2);
 186                 if (thingie instanceof Hashtable) {
 187                     response = this.post(param1, (Hashtable) thingie);
 188                 } else {
 189                     throw buildException("Serious FAULT in method POST.  Expected " + param2
 190                             + " to be a TABLE, but it isn't.  Faulting to prevent runaway execution.", CallException.CODE_MODULE_FAULT);
 191                 }
 192             }
 193 
 194         } else if (name.equals(method_DONE)) {
 195             this.done();
 196             response = Constants.EMPTY_LEFT;
 197 
 198         } else if (name.equals(method_CREDENTIALS)) {
 199             param1 = (String) getParam(method_CREDENTIALS_1_UID);
 200             param2 = (String) getParam(method_CREDENTIALS_2_PASS);
 201 
 202             if ((param1 == null) || (param2 == null)) { throw buildException(
 203                     "Serious FAULT while setting credentials.  One or both of the required 'uid' and 'password' not provided", CallException.CODE_MODULE_FAULT); }
 204             this.set_credentials(param1, param2);
 205             response = Constants.EMPTY_LEFT;
 206 
 207         } else {
 208             error("Not a provided method.  method=" + name);
 209             response = Constants.EMPTY_LEFT;
 210         }
 211         return response;
 212     }
 213 
 214     /**
 215      * Allow the subclass a chance to initialize. At a minium, an implementor
 216      * should create an empty method.
 217      * 
 218      * @throws CallException
 219      * @return the name
 220      */
 221     protected String instantiation_chain() throws CallException {
 222 
 223         // Do we turn off that noisy HTTPClient logging. OFF by default
 224         try {
 225             String p = visSC.getInvokerProperties().getString(AutohitProperties.SYSTEM_WIRE_DEBUG);
 226             if ((p != null) && (p.equals("true"))) {
 227                 System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire", "debug");
 228             } else {
 229                 System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire", "error");
 230             }
 231         } catch (Exception e) {
 232             // Just not that important.
 233         }
 234 
 235         // Allocate a client
 236         started = false;
 237         return myNAME;
 238     }
 239 
 240     /**
 241      * Allow the subclass a chance to cleanup on free. At a minium, an
 242      * implementor should create an empty method.
 243      * 
 244      * @throws CallException
 245      */
 246     protected void free_chain() throws CallException {
 247         // just in case....
 248         httpClient = null;
 249     }
 250 
 251     // PRIVATE IMPLEMENTATIONS
 252 
 253     /**
 254      * Start method for an HTTP session. It will set the target address for the client, as well as
 255      * clearing any state.
 256      * 
 257      * @param addr
 258      *            the address. Do not include protocol, but you may add port
 259      *            (ie. "www.goatland.com:80").
 260      * @throws CallException
 261      */
 262     private void start(String addr, int port) throws CallException {
 263 
 264         try {
 265             httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
 266             creds = null;
 267             HttpState initialState = new HttpState();
 268             initialState.setCookiePolicy(CookiePolicy.COMPATIBILITY);
 269             httpClient.setState(initialState);
 270             httpClient.setConnectionTimeout(DEFAULT_TIMEOUT);
 271             httpClient.getHostConfiguration().setHost(addr, port, "http");
 272 
 273         } catch (Exception ex) {
 274             throw new CallException("Serious fault while creating session with start method.  Session is not valid.  error=" + ex.getMessage(),
 275                     CallException.CODE_MODULE_FAULT, ex);
 276         }
 277 
 278         // NO CODE AFTER THIS!
 279         started = true;
 280     }
 281 
 282     /**
 283      * Start method for an HTTPS session. It will set the target address for the client, as well as
 284      * clearing any state.
 285      * 
 286      * @param addr
 287      *            the address. Do not include protocol, but you may add port
 288      *            (ie. "www.goatland.com:443").
 289      * @throws CallException
 290      */
 291     private void starthttps(String addr, int port) throws CallException {
 292 
 293         try {
 294             // buidl protocol
 295             Protocol myhttps = new Protocol("https", new EasySSLProtocolSocketFactory(), port);
 296 
 297             httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
 298             creds = null;
 299             HttpState initialState = new HttpState();
 300             initialState.setCookiePolicy(CookiePolicy.COMPATIBILITY);
 301             httpClient.setState(initialState);
 302             httpClient.setConnectionTimeout(DEFAULT_TIMEOUT);
 303             httpClient.getHostConfiguration().setHost(addr, port, myhttps);
 304 
 305         } catch (Exception ex) {
 306             throw new CallException("Serious fault while creating session with start method.  Session is not valid.  error=" + ex.getMessage(),
 307                     CallException.CODE_MODULE_FAULT, ex);
 308         }
 309 
 310         // NO CODE AFTER THIS!
 311         started = true;
 312     }
 313 
 314     
 315     /**
 316      * Done method. Dispose of state and everything.
 317      * 
 318      * @throws CallException
 319      */
 320     private void done() throws CallException {
 321 
 322         // NO CODE BEFORE THIS!
 323         started = false;
 324         httpClient = null;
 325     }
 326 
 327     /**
 328      * Start method. It will set the target address for the client, as well as
 329      * clearing any state.
 330      * 
 331      * @param url
 332      *            the Url path, not to include protocol, address, and port (ie.
 333      *            "/goats/index.html").
 334      * @return the data from the page as a String
 335      * @throws CallException
 336      */
 337     private String get(String url) throws CallException {
 338 
 339         if (started == false) { throw buildException("module:SimpleHttp:Tried to get when a session wasn't started.", CallException.CODE_MODULE_FAULT); }
 340 
 341         String result = null;
 342 
 343         // Construct our method.
 344         HttpMethod method = new GetMethod(url);
 345         method.setFollowRedirects(true);
 346         method.setStrictMode(false);
 347 
 348         //execute the method
 349         try {
 350             // Do it
 351             debug("(get)get=" + url);
 352             httpClient.executeMethod(method);
 353 
 354             // Process result
 355             result = method.getResponseBodyAsString();
 356             log("(get)" + method.getStatusLine().toString() + " size=" + result.length());
 357 
 358         } catch (HttpException he) {
 359             // Bad but not fatal
 360             error("(get)Error on connect to url " + url + ".  Error=" + he.getMessage());
 361         } catch (IOException ioe) {
 362             // Fatal
 363             throw buildException("(get)Unable to connect.  Session is invalid.  message=" + ioe.getMessage(), CallException.CODE_MODULE_FAULT, ioe);
 364         } finally {
 365             try {
 366                 method.releaseConnection();
 367                 method.recycle();
 368             } catch (Exception e) {
 369                 // Already FUBAR
 370             }
 371         }
 372         return result;
 373     }
 374 
 375     /**
 376      * Post method. It will set the target address for the client, as well as
 377      * clearing any state.
 378      * 
 379      * @param url
 380      *            the Url path, not to include protocol, address, and port (ie.
 381      *            "/goats/index.html").
 382      * @param nv
 383      *            set of name/value pairs for the post. it can be empty.
 384      * @return the data from the page as a String
 385      * @throws CallException
 386      */
 387     private String post(String url, Hashtable nv) throws CallException {
 388 
 389         if (started == false) { throw buildException("Tried to post when a session wasn't started.", CallException.CODE_MODULE_FAULT); }
 390 
 391         String result = null;
 392         String name;
 393         Object value;
 394 
 395         // Construct our method.
 396         PostMethod method = new PostMethod(url);
 397         method.setFollowRedirects(true);
 398         method.setStrictMode(false);
 399 
 400         //build the rest of the method
 401         try {
 402             // Construct the headers
 403             Enumeration eNV = nv.keys();
 404             while (eNV.hasMoreElements()) {
 405                 name = (String) eNV.nextElement();
 406                 value = nv.get(name);
 407                 if (value instanceof String) {
 408                     // Only take it if it is a string
 409                     method.addParameter(name, (String) value);
 410                     debug("ADD POST - name=" + name + " value=" + (String) value);
 411                 }
 412             }
 413             //DEBUG
 414             debug("DUMP POST-------------------------------");
 415             debug(method.toString());
 416             debug("DUMP POST-------------------------------");
 417 
 418             // Do it
 419             debug("(post)post=" + url);
 420             httpClient.executeMethod(method);
 421 
 422             // Process result
 423             result = method.getResponseBodyAsString();
 424             log("(post)" + method.getStatusLine().toString() + " size=" + result.length());
 425 
 426         } catch (HttpException he) {
 427             // Bad but not fatal
 428             error("(post)Error on connect to url " + url + ".  Error=" + he.getMessage());
 429         } catch (IOException ioe) {
 430             // Fatal
 431             throw buildException("(post)Unable to connect.  Session is invalid.", CallException.CODE_MODULE_FAULT, ioe);
 432 
 433         } catch (Exception ex) {
 434             // Fatal
 435             throw buildException("(post)Serious general error.", CallException.CODE_MODULE_FAULT, ex);
 436         } finally {
 437             try {
 438                 method.releaseConnection();
 439                 method.recycle();
 440             } catch (Exception e) {
 441                 // Already FUBAR
 442             }
 443         }
 444         return result;
 445     }
 446 
 447     /**
 448      * Set credentials method. It will throw an exception if a session isn't
 449      * started.
 450      * 
 451      * @param uid
 452      *            User id
 453      * @param pass
 454      *            Password
 455      * @throws CallException
 456      */
 457     private void set_credentials(String uid, String pass) throws CallException {
 458 
 459         if (started) {
 460             creds = new UsernamePasswordCredentials(uid, pass);
 461             httpClient.getState().setCredentials(null, null, creds);
 462 
 463         } else {
 464             throw new CallException("Tried to set credentials before a session is started.", CallException.CODE_MODULE_REPORTED_ERROR);
 465         }
 466     }
 467 
 468 }