using System;
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 (Exception ex)
{
#if LOGGER
if (ex is IOException)
{
logger.Error("Unable start AGI Server: cannot to bind to " + address + ":" + port + ".", ex);
}
#endif
if (serverSocket != null)
{
serverSocket.Close();
serverSocket = null;
}
pool.Shutdown();
#if LOGGER
logger.Info("AGI Server shut down.");
#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("AGI Server shut down.");
#endif
}
}
#endregion
#region Stop()
public void Stop()
{
stopped = true;
if (serverSocket != null)
serverSocket.Close();
}
#endregion
}
}