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 }
|