/* * AsteriskAGIHandler.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 2005-2006 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.7 * Created by suhler on 05/06/06 * Last modified by suhler on 06/05/08 10:28:13 * * Version Histories: * * 1.7 06/05/08-10:28:13 (suhler) * add "agi_host" parameter containing the asterisk server's hostname * . * * 1.6 06/05/08-09:59:48 (suhler) * prefix session id with properties prefix * . * * 1.5 06/04/25-14:54:41 (suhler) * doc fixes * * 1.4 05/11/10-10:56:48 (suhler) * doc cleanup * * 1.3 05/06/07-15:58:07 (suhler) * redo the way the socket streams are handled * make sure commands are written in a single packet * misc cleanups * * 1.2 05/06/07-15:54:35 (suhler) * checkpoint * * 1.2 05/06/06-21:03:10 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 1 0 asterisk/AsteriskAGIHandler.java * * 1.1 05/06/06-21:03:09 (suhler) * date and time created 05/06/06 21:03:09 by suhler * */ package sunlabs.brazil.asterisk; import java.io.OutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import sunlabs.brazil.server.FileHandler; import sunlabs.brazil.server.Handler; import sunlabs.brazil.server.Request; import sunlabs.brazil.server.Server; import sunlabs.brazil.server.TestRequest; import sunlabs.brazil.template.RewriteContext; import sunlabs.brazil.template.Template; import sunlabs.brazil.template.TemplateRunner; import sunlabs.brazil.util.http.HttpInputStream; import sunlabs.brazil.util.http.MimeHeaders; /** * FAGI (fast AGI) handler and template for Asterisk. * This handler/template starts a server listening on the * FAGI port. * Anytime it gets an agi request from * it creates a dummy * request object (sort of like TestRequest) to simulate an http * request, reads a file implied by the request agi:... string, and * processes the file through the template runner. * The template can be used to interact with * via * standard agi commands, and the web via the SetTemplate and namespaces. * The template output is discarded (if debug is enables, it is printed on * the server console); everything is done via side effect. * This allows us to interact with the ordinary template variables and * namespaces. * I'm still not sure how to deal with sessions, so we'll use a * different one for each uniqueid in the agi request. (This is a bad idea * unless we delete completed sessions "by hand"). *

(Implementation notes)
* This class implements 4 different threads: * - handler/init: to get the config params and start the listening socket * - The thread that listens and accepts connections from * * - the threads that handle the incoming agi requests * - the threads that do the template stuff */ public class AsteriskAGIHandler extends Template implements Handler, Runnable { Server server; String prefix; Socket sock; // socket from * ServerSocket listen; // server socket // these are for tag_agi. HttpInputStream in = null; Request.HttpOutputStream out = null; public AsteriskAGIHandler() { listen = null; sock = null; } /** * Constructor to create the listener instance. */ AsteriskAGIHandler(Server server, String prefix, ServerSocket listen) { this.server = server; this.prefix = prefix; this.listen = listen; sock = null; } /** * Constructor to create an AGI request handler thread. */ AsteriskAGIHandler(Server server, String prefix, Socket sock) { this.server = server; this.prefix = prefix; this.sock = sock; listen = null; } /** * Start a Listening socket thread, and wait for AGI connections. */ public boolean init(Server server, String prefix) { this.server = server; this.prefix = prefix; int port = 4573; // default agi port try { String str = server.props.getProperty(prefix + "port"); port = Integer.decode(str).intValue(); } catch (Exception e) {} try { ServerSocket listen = new ServerSocket(port); new Thread(new AsteriskAGIHandler(server, prefix, listen)).start(); } catch (IOException e) { server.log(Server.LOG_ERROR, prefix, "Server socket failed: " + e); return false; } // We should verify we are one of the templates, otherwise this // isn't useful. String tokens = server.props.getProperty(prefix + "templates", ""); String token = prefix.substring(0, prefix.length()-1); if (tokens.indexOf(token) < 0) { server.log(Server.LOG_WARNING, prefix, "templates don't include: " + token); } return true; } /** * We don't handle any "normal" requests. * @return always false */ public boolean respond(Request request) throws IOException { return false; } /** * Open the socket's streams at top of page. * This will be used by the <agi> calls. */ public boolean init(RewriteContext hr) { try { in = new HttpInputStream(hr.request.sock.getInputStream()); out = new Request.HttpOutputStream( hr.request.sock.getOutputStream()); } catch (IOException e) { hr.request.log(Server.LOG_WARNING, "Oops getting * stream: " + e); return false; } return super.init(hr); } /** * Close the socket connection. */ public boolean done(RewriteContext hr) { try { in.close(); out.close(); hr.request.sock.close(); } catch (IOException e) { hr.request.log(Server.LOG_WARNING, "Oops closing * stream: " + e); } return true; } /** * Provide the 'agi' tag. * <agi command="agi command"> * The result is placed in "agi_result". * NOTE: the thread running this instance doesn't set * any of the instance variables. We get everything from "hr". */ public void tag_agi(RewriteContext hr) { debug(hr); hr.killToken(); String cmd = hr.get("command").trim(); if (cmd==null) { debug(hr, "No agi command!"); return; } String line; try { hr.server.log(Server.LOG_DIAGNOSTIC, hr.prefix, "Write: (" + cmd + ")"); writeLine(out, cmd); line = in.readLine(); debug(hr, line); hr.server.log(Server.LOG_DIAGNOSTIC, hr.prefix, "Read: (" + line + ")"); } catch (IOException e) { line="Error: " + e; } hr.request.props.put("agi_result", line); return; } /** * Write a NL string as ASCII in one network packet. * Some older Asterisk's AGI interface requires each agi command * must be in its own network packet. This tries to * insure that happens. */ void writeLine(OutputStream out, String s) throws IOException { int len = s.length(); byte[] b = new byte[len+1]; for (int i=0;i