2013-01-18 15:55:50 +00:00
|
|
|
using System.IO;
|
|
|
|
using System.Net;
|
|
|
|
using System.Text;
|
2015-01-03 15:37:29 +00:00
|
|
|
using AsterNET.FastAGI.MappingStrategies;
|
|
|
|
using AsterNET.IO;
|
|
|
|
using AsterNET.Util;
|
2013-01-18 15:55:50 +00:00
|
|
|
|
2014-01-08 14:16:39 +00:00
|
|
|
namespace AsterNET.FastAGI
|
2013-01-18 15:55:50 +00:00
|
|
|
{
|
2015-01-03 15:37:29 +00:00
|
|
|
public class AsteriskFastAGI
|
2013-08-21 10:31:26 +00:00
|
|
|
{
|
|
|
|
#region Flags
|
2015-01-03 15:37:29 +00:00
|
|
|
|
2013-08-21 10:31:26 +00:00
|
|
|
/// <summary>
|
2015-01-03 15:37:29 +00:00
|
|
|
/// 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
|
2013-08-21 10:31:26 +00:00
|
|
|
/// </summary>
|
|
|
|
public bool SC511_CAUSES_EXCEPTION = false;
|
|
|
|
|
|
|
|
/// <summary>
|
2015-01-03 15:37:29 +00:00
|
|
|
/// 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
|
2013-08-21 10:31:26 +00:00
|
|
|
/// </summary>
|
|
|
|
public bool SCHANGUP_CAUSES_EXCEPTION = false;
|
2015-01-03 15:37:29 +00:00
|
|
|
|
2013-08-21 10:31:26 +00:00
|
|
|
#endregion
|
2015-01-03 15:37:29 +00:00
|
|
|
|
2013-08-21 10:31:26 +00:00
|
|
|
#region Variables
|
2015-01-03 15:37:29 +00:00
|
|
|
|
2013-01-18 15:55:50 +00:00
|
|
|
#if LOGGER
|
2015-01-03 15:37:29 +00:00
|
|
|
private readonly Logger logger = Logger.Instance();
|
2013-01-18 15:55:50 +00:00
|
|
|
#endif
|
2015-01-03 15:37:29 +00:00
|
|
|
private ServerSocket serverSocket;
|
|
|
|
|
|
|
|
/// <summary> The port to listen on.</summary>
|
|
|
|
private int port;
|
|
|
|
|
|
|
|
/// <summary> The address to listen on.</summary>
|
|
|
|
private readonly string address;
|
|
|
|
|
|
|
|
/// <summary>The thread pool that contains the worker threads to process incoming requests.</summary>
|
|
|
|
private ThreadPool pool;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The number of worker threads in the thread pool. This equals the maximum number of concurrent requests this
|
|
|
|
/// AGIServer can serve.
|
|
|
|
/// </summary>
|
|
|
|
private int poolSize;
|
|
|
|
|
|
|
|
/// <summary> True while this server is shut down. </summary>
|
|
|
|
private bool stopped;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// The strategy to use for bind AGIRequests to AGIScripts that serve them.
|
|
|
|
/// </summary>
|
|
|
|
private IMappingStrategy mappingStrategy;
|
|
|
|
|
|
|
|
private Encoding socketEncoding = Encoding.ASCII;
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region PoolSize
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Sets the number of worker threads in the thread pool.<br />
|
|
|
|
/// This equals the maximum number of concurrent requests this AGIServer can serve.<br />
|
|
|
|
/// The default pool size is 10.
|
|
|
|
/// </summary>
|
|
|
|
public int PoolSize
|
|
|
|
{
|
|
|
|
set { poolSize = value; }
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region BindPort
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Sets the TCP port to listen on for new connections.<br />
|
|
|
|
/// The default bind port is 4573.
|
|
|
|
/// </summary>
|
|
|
|
public int BindPort
|
|
|
|
{
|
|
|
|
set { port = value; }
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region MappingStrategy
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Sets the strategy to use for mapping AGIRequests to AGIScripts that serve them.<br />
|
|
|
|
/// The default mapping is a MappingStrategy.
|
|
|
|
/// </summary>
|
|
|
|
/// <seealso cref="MappingStrategy" />
|
|
|
|
public IMappingStrategy MappingStrategy
|
|
|
|
{
|
|
|
|
set { mappingStrategy = value; }
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region SocketEncoding
|
|
|
|
|
|
|
|
public Encoding SocketEncoding
|
|
|
|
{
|
|
|
|
get { return socketEncoding; }
|
|
|
|
set { socketEncoding = value; }
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Constructor - AsteriskFastAGI()
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a new AsteriskFastAGI.
|
|
|
|
/// </summary>
|
|
|
|
public AsteriskFastAGI()
|
|
|
|
{
|
|
|
|
address = Common.AGI_BIND_ADDRESS;
|
|
|
|
port = Common.AGI_BIND_PORT;
|
|
|
|
poolSize = Common.AGI_POOL_SIZE;
|
|
|
|
mappingStrategy = new ResourceMappingStrategy();
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Constructor - AsteriskFastAGI()
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a new AsteriskFastAGI.
|
|
|
|
/// </summary>
|
|
|
|
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
|
2013-01-18 15:55:50 +00:00
|
|
|
|
2013-08-21 10:31:26 +00:00
|
|
|
#region Constructor - AsteriskFastAGI()
|
2015-01-03 15:37:29 +00:00
|
|
|
|
2013-08-21 10:31:26 +00:00
|
|
|
/// <summary>
|
2015-01-03 15:37:29 +00:00
|
|
|
/// Creates a new AsteriskFastAGI.
|
2013-08-21 10:31:26 +00:00
|
|
|
/// </summary>
|
|
|
|
public AsteriskFastAGI(IMappingStrategy mappingStrategy)
|
|
|
|
{
|
2015-01-03 15:37:29 +00:00
|
|
|
address = Common.AGI_BIND_ADDRESS;
|
|
|
|
port = Common.AGI_BIND_PORT;
|
|
|
|
poolSize = Common.AGI_POOL_SIZE;
|
2013-08-21 10:31:26 +00:00
|
|
|
this.mappingStrategy = mappingStrategy;
|
|
|
|
}
|
|
|
|
|
|
|
|
public AsteriskFastAGI(IMappingStrategy mappingStrategy, string ipaddress, int port, int poolSize)
|
|
|
|
{
|
2015-01-03 15:37:29 +00:00
|
|
|
address = ipaddress;
|
2013-08-21 10:31:26 +00:00
|
|
|
this.port = port;
|
|
|
|
this.poolSize = poolSize;
|
|
|
|
this.mappingStrategy = mappingStrategy;
|
|
|
|
}
|
2015-01-03 15:37:29 +00:00
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Constructor - AsteriskFastAGI(int port, int poolSize)
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a new AsteriskFastAGI.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="port">The port to listen on.</param>
|
|
|
|
/// <param name="poolSize">
|
|
|
|
/// The number of worker threads in the thread pool.
|
|
|
|
/// This equals the maximum number of concurrent requests this AGIServer can serve.
|
|
|
|
/// </param>
|
|
|
|
public AsteriskFastAGI(int port, int poolSize)
|
|
|
|
{
|
|
|
|
address = Common.AGI_BIND_ADDRESS;
|
|
|
|
this.port = port;
|
|
|
|
this.poolSize = poolSize;
|
|
|
|
mappingStrategy = new ResourceMappingStrategy();
|
|
|
|
}
|
|
|
|
|
2013-08-21 10:31:26 +00:00
|
|
|
#endregion
|
|
|
|
|
2015-01-03 15:37:29 +00:00
|
|
|
#region Constructor - AsteriskFastAGI(string address, int port, int poolSize)
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a new AsteriskFastAGI.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="ipaddress">The address to listen on.</param>
|
|
|
|
/// <param name="port">The port to listen on.</param>
|
|
|
|
/// <param name="poolSize">
|
|
|
|
/// The number of worker threads in the thread pool.
|
|
|
|
/// This equals the maximum number of concurrent requests this AGIServer can serve.
|
|
|
|
/// </param>
|
|
|
|
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;
|
2014-03-05 16:42:56 +00:00
|
|
|
}
|
|
|
|
|
2015-01-03 15:37:29 +00:00
|
|
|
#region Start()
|
|
|
|
|
|
|
|
public void Start()
|
|
|
|
{
|
|
|
|
stopped = false;
|
|
|
|
mappingStrategy.Load();
|
|
|
|
pool = new ThreadPool("AGIServer", poolSize);
|
2013-01-18 15:55:50 +00:00
|
|
|
#if LOGGER
|
2015-01-03 15:37:29 +00:00
|
|
|
logger.Info("Thread pool started.");
|
2013-01-18 15:55:50 +00:00
|
|
|
#endif
|
2015-01-03 15:37:29 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
var ipAddress = IPAddress.Parse(address);
|
|
|
|
serverSocket = new ServerSocket(port, ipAddress, SocketEncoding);
|
|
|
|
}
|
|
|
|
catch (IOException ex)
|
|
|
|
{
|
2013-01-18 15:55:50 +00:00
|
|
|
#if LOGGER
|
2015-01-03 15:37:29 +00:00
|
|
|
logger.Error("Unable start AGI Server: cannot to bind to " + address + ":" + port + ".", ex);
|
2013-01-18 15:55:50 +00:00
|
|
|
#endif
|
2015-01-03 15:37:29 +00:00
|
|
|
throw ex;
|
|
|
|
}
|
2013-01-18 15:55:50 +00:00
|
|
|
#if LOGGER
|
2015-01-03 15:37:29 +00:00
|
|
|
logger.Info("Listening on " + address + ":" + port + ".");
|
2013-01-18 15:55:50 +00:00
|
|
|
#endif
|
|
|
|
|
2015-01-03 15:37:29 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
SocketConnection socket;
|
|
|
|
while ((socket = serverSocket.Accept()) != null)
|
|
|
|
{
|
2013-01-18 15:55:50 +00:00
|
|
|
#if LOGGER
|
2015-01-03 15:37:29 +00:00
|
|
|
logger.Info("Received connection.");
|
2013-01-18 15:55:50 +00:00
|
|
|
#endif
|
2015-01-03 15:37:29 +00:00
|
|
|
var connectionHandler = new AGIConnectionHandler(socket, mappingStrategy, SC511_CAUSES_EXCEPTION,
|
|
|
|
SCHANGUP_CAUSES_EXCEPTION);
|
|
|
|
pool.AddJob(connectionHandler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (IOException ex)
|
|
|
|
{
|
|
|
|
if (!stopped)
|
|
|
|
{
|
2013-01-18 15:55:50 +00:00
|
|
|
#if LOGGER
|
2015-01-03 15:37:29 +00:00
|
|
|
logger.Error("IOException while waiting for connections (1).", ex);
|
2013-01-18 15:55:50 +00:00
|
|
|
#endif
|
2015-01-03 15:37:29 +00:00
|
|
|
throw ex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
if (serverSocket != null)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
serverSocket.Close();
|
|
|
|
}
|
2013-01-18 15:55:50 +00:00
|
|
|
#if LOGGER
|
2015-01-03 15:37:29 +00:00
|
|
|
catch (IOException ex)
|
|
|
|
{
|
|
|
|
logger.Error("IOException while waiting for connections (2).", ex);
|
|
|
|
}
|
2013-01-18 15:55:50 +00:00
|
|
|
#else
|
|
|
|
catch { }
|
|
|
|
#endif
|
2015-01-03 15:37:29 +00:00
|
|
|
}
|
|
|
|
serverSocket = null;
|
|
|
|
pool.Shutdown();
|
2013-01-18 15:55:50 +00:00
|
|
|
#if LOGGER
|
2015-01-03 15:37:29 +00:00
|
|
|
logger.Info("AGIServer shut down.");
|
2013-01-18 15:55:50 +00:00
|
|
|
#endif
|
2015-01-03 15:37:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Stop()
|
|
|
|
|
|
|
|
public void Stop()
|
|
|
|
{
|
|
|
|
stopped = true;
|
|
|
|
if (serverSocket != null)
|
|
|
|
serverSocket.Close();
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|