using System.IO; using System.Net; using System.Text; using AsterNET.FastAGI.MappingStrategies; using AsterNET.IO; using AsterNET.Util; namespace AsterNET.FastAGI { public class AsteriskFastAGI { #region Flags /// /// If set to true, causes the AGIChannel to throw an exception when a status code of 511 (Channel Dead) is returned. /// This is set to false by default to maintain backwards compatibility /// public bool SC511_CAUSES_EXCEPTION = false; /// /// If set to true, causes the AGIChannel to throw an exception when return status is 0 and reply is HANGUP. /// This is set to false by default to maintain backwards compatibility /// public bool SCHANGUP_CAUSES_EXCEPTION = false; #endregion #region Variables #if LOGGER private readonly Logger logger = Logger.Instance(); #endif private ServerSocket serverSocket; /// The port to listen on. private int port; /// The address to listen on. private readonly string address; /// The thread pool that contains the worker threads to process incoming requests. private ThreadPool pool; /// /// The number of worker threads in the thread pool. This equals the maximum number of concurrent requests this /// AGIServer can serve. /// private int poolSize; /// True while this server is shut down. private bool stopped; /// /// The strategy to use for bind AGIRequests to AGIScripts that serve them. /// private IMappingStrategy mappingStrategy; private Encoding socketEncoding = Encoding.ASCII; #endregion #region PoolSize /// /// Sets the number of worker threads in the thread pool.
/// This equals the maximum number of concurrent requests this AGIServer can serve.
/// The default pool size is 10. ///
public int PoolSize { set { poolSize = value; } } #endregion #region BindPort /// /// Sets the TCP port to listen on for new connections.
/// The default bind port is 4573. ///
public int BindPort { set { port = value; } } #endregion #region MappingStrategy /// /// Sets the strategy to use for mapping AGIRequests to AGIScripts that serve them.
/// The default mapping is a MappingStrategy. ///
/// public IMappingStrategy MappingStrategy { set { mappingStrategy = value; } } #endregion #region SocketEncoding public Encoding SocketEncoding { get { return socketEncoding; } set { socketEncoding = value; } } #endregion #region Constructor - AsteriskFastAGI() /// /// Creates a new AsteriskFastAGI. /// public AsteriskFastAGI() { address = Common.AGI_BIND_ADDRESS; port = Common.AGI_BIND_PORT; poolSize = Common.AGI_POOL_SIZE; mappingStrategy = new ResourceMappingStrategy(); } #endregion #region Constructor - AsteriskFastAGI() /// /// Creates a new AsteriskFastAGI. /// public AsteriskFastAGI(string mappingStrategy) { address = Common.AGI_BIND_ADDRESS; port = Common.AGI_BIND_PORT; poolSize = Common.AGI_POOL_SIZE; this.mappingStrategy = new ResourceMappingStrategy(mappingStrategy); } #endregion #region Constructor - AsteriskFastAGI() /// /// Creates a new AsteriskFastAGI. /// public AsteriskFastAGI(IMappingStrategy mappingStrategy) { address = Common.AGI_BIND_ADDRESS; port = Common.AGI_BIND_PORT; poolSize = Common.AGI_POOL_SIZE; this.mappingStrategy = mappingStrategy; } public AsteriskFastAGI(IMappingStrategy mappingStrategy, string ipaddress, int port, int poolSize) { address = ipaddress; this.port = port; this.poolSize = poolSize; this.mappingStrategy = mappingStrategy; } #endregion #region Constructor - AsteriskFastAGI(int port, int poolSize) /// /// Creates a new AsteriskFastAGI. /// /// The port to listen on. /// /// The number of worker threads in the thread pool. /// This equals the maximum number of concurrent requests this AGIServer can serve. /// public AsteriskFastAGI(int port, int poolSize) { address = Common.AGI_BIND_ADDRESS; this.port = port; this.poolSize = poolSize; mappingStrategy = new ResourceMappingStrategy(); } #endregion #region Constructor - AsteriskFastAGI(string address, int port, int poolSize) /// /// Creates a new AsteriskFastAGI. /// /// The address to listen on. /// The port to listen on. /// /// The number of worker threads in the thread pool. /// This equals the maximum number of concurrent requests this AGIServer can serve. /// public AsteriskFastAGI(string ipaddress, int port, int poolSize) { address = ipaddress; this.port = port; this.poolSize = poolSize; mappingStrategy = new ResourceMappingStrategy(); } #endregion public AsteriskFastAGI(string ipaddress = Common.AGI_BIND_ADDRESS, int port = Common.AGI_BIND_PORT, int poolSize = Common.AGI_POOL_SIZE, bool sc511_CausesException = false, bool scHangUp_CausesException = false) { address = ipaddress; this.port = port; this.poolSize = poolSize; mappingStrategy = new ResourceMappingStrategy(); SC511_CAUSES_EXCEPTION = sc511_CausesException; SCHANGUP_CAUSES_EXCEPTION = scHangUp_CausesException; } #region Start() public void Start() { stopped = false; mappingStrategy.Load(); pool = new ThreadPool("AGIServer", poolSize); #if LOGGER logger.Info("Thread pool started."); #endif try { var ipAddress = IPAddress.Parse(address); serverSocket = new ServerSocket(port, ipAddress, SocketEncoding); } catch (IOException ex) { #if LOGGER logger.Error("Unable start AGI Server: cannot to bind to " + address + ":" + port + ".", ex); #endif throw ex; } #if LOGGER logger.Info("Listening on " + address + ":" + port + "."); #endif try { SocketConnection socket; while ((socket = serverSocket.Accept()) != null) { #if LOGGER logger.Info("Received connection."); #endif var connectionHandler = new AGIConnectionHandler(socket, mappingStrategy, SC511_CAUSES_EXCEPTION, SCHANGUP_CAUSES_EXCEPTION); pool.AddJob(connectionHandler); } } catch (IOException ex) { if (!stopped) { #if LOGGER logger.Error("IOException while waiting for connections (1).", ex); #endif throw ex; } } finally { if (serverSocket != null) { try { serverSocket.Close(); } #if LOGGER catch (IOException ex) { logger.Error("IOException while waiting for connections (2).", ex); } #else catch { } #endif } serverSocket = null; pool.Shutdown(); #if LOGGER logger.Info("AGIServer shut down."); #endif } } #endregion #region Stop() public void Stop() { stopped = true; if (serverSocket != null) serverSocket.Close(); } #endregion } }