This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Net; | |
using System.Reactive.Linq; | |
using System.Reflection; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Amib.Threading; | |
using Funq; | |
using ServiceStack.Common.Web; | |
using ServiceStack.Logging; | |
using ServiceStack.ServiceHost; | |
using ServiceStack.ServiceInterface; | |
using ServiceStack.Text; | |
using ServiceStack.WebHost.Endpoints; | |
using ServiceStack.WebHost.Endpoints.Support; | |
namespace SSSelfHosted { | |
public class Program { | |
private static void Main(string[] args) { | |
var listeningOn = args.Length == 0 ? "http://*:9090/" : args[0]; | |
var appHost = new AppHost(); | |
appHost.Init(); | |
appHost.Start(listeningOn); | |
Console.WriteLine("AppHost Created at {0}, listening on {1}", DateTime.Now, listeningOn); | |
Console.ReadKey(); | |
} | |
} | |
public class AppHost : AppHostHttpListenerSmartThreadPool { | |
public AppHost() : base("StarterTemplate HttpListener", typeof(HelloService).Assembly) {} | |
public override void Configure(Funq.Container container) {} | |
} | |
[Route("/sayHello/{Name}")] | |
public class HelloRequest : IReturn<HelloResponse> { | |
public string Name { get; set; } | |
} | |
public class HelloResponse { | |
public string Message { get; set; } | |
public string Status { get; set; } | |
} | |
public class HelloService : Service, IGet<HelloRequest> { | |
private static Random r = new Random(); | |
public object Get(HelloRequest request) { | |
var timer = new Stopwatch(); | |
timer.Start(); | |
while(timer.ElapsedMilliseconds < 100) | |
Thread.SpinWait(1000000); | |
timer.Stop(); | |
return new HelloResponse() { | |
Message = "hello " + request.Name, | |
Status = "OK -> " + timer.ElapsedMilliseconds | |
}; | |
} | |
} | |
#region smartthreadpool | |
public abstract class AppHostHttpListenerSmartThreadPool : AppHostHttpListenerBase { | |
private readonly AutoResetEvent _listenForNextRequest = new AutoResetEvent(false); | |
private readonly SmartThreadPool _threadPoolManager; | |
private readonly ILog _log = LogManager.GetLogger(typeof(HttpListenerBase)); | |
private const int IdleTimeout = 300; | |
protected AppHostHttpListenerSmartThreadPool(int poolSize = 500) { | |
_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
protected AppHostHttpListenerSmartThreadPool(string serviceName, params Assembly[] assembliesWithServices) | |
: this(serviceName, 500, assembliesWithServices) {} | |
protected AppHostHttpListenerSmartThreadPool(string serviceName, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, assembliesWithServices) { | |
_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
protected AppHostHttpListenerSmartThreadPool(string serviceName, string handlerPath, params Assembly[] assembliesWithServices) | |
: this(serviceName, handlerPath, 500, assembliesWithServices) {} | |
protected AppHostHttpListenerSmartThreadPool(string serviceName, string handlerPath, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, handlerPath, assembliesWithServices) { | |
_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
private bool disposed = false; | |
protected override void Dispose(bool disposing) { | |
if(disposed) return; | |
lock(this) { | |
if(disposed) return; | |
if(disposing) | |
_threadPoolManager.Dispose(); | |
// new shared cleanup logic | |
disposed = true; | |
base.Dispose(disposing); | |
} | |
} | |
/// <summary> | |
/// Starts the Web Service | |
/// </summary> | |
/// <param name="urlBase"> | |
/// A Uri that acts as the base that the server is listening on. | |
/// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/ | |
/// Note: the trailing slash is required! For more info see the | |
/// HttpListener.Prefixes property on MSDN. | |
/// </param> | |
public override void Start(string urlBase) { | |
// *** Already running - just leave it in place | |
if(IsStarted) | |
return; | |
if(Listener == null) | |
Listener = new HttpListener(); | |
Listener.Prefixes.Add(urlBase); | |
IsStarted = true; | |
Listener.Start(); | |
ThreadPool.QueueUserWorkItem(Listen); | |
} | |
// Loop here to begin processing of new requests. | |
private void Listen(object state) { | |
while(Listener.IsListening) { | |
if(Listener == null) return; | |
try { | |
Listener.BeginGetContext(ListenerCallback, Listener); | |
_listenForNextRequest.WaitOne(); | |
} | |
catch(Exception ex) { | |
_log.Error("Listen()", ex); | |
return; | |
} | |
if(Listener == null) return; | |
} | |
} | |
// Handle the processing of a request in here. | |
private void ListenerCallback(IAsyncResult asyncResult) { | |
var listener = asyncResult.AsyncState as HttpListener; | |
HttpListenerContext context; | |
if(listener == null) return; | |
var isListening = listener.IsListening; | |
try { | |
if(!isListening) { | |
_log.DebugFormat("Ignoring ListenerCallback() as HttpListener is no longer listening"); | |
return; | |
} | |
// The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework, | |
// blocks until there is a request to be processed or some type of data is available. | |
context = listener.EndGetContext(asyncResult); | |
} | |
catch(Exception ex) { | |
// You will get an exception when httpListener.Stop() is called | |
// because there will be a thread stopped waiting on the .EndGetContext() | |
// method, and again, that is just the way most Begin/End asynchronous | |
// methods of the .NET Framework work. | |
string errMsg = ex + ": " + isListening; | |
_log.Warn(errMsg); | |
return; | |
} | |
finally { | |
// Once we know we have a request (or exception), we signal the other thread | |
// so that it calls the BeginGetContext() (or possibly exits if we're not | |
// listening any more) method to start handling the next incoming request | |
// while we continue to process this request on a different thread. | |
_listenForNextRequest.Set(); | |
} | |
_log.InfoFormat("{0} Request : {1}", context.Request.UserHostAddress, context.Request.RawUrl); | |
RaiseReceiveWebRequest(context); | |
_threadPoolManager.QueueWorkItem(() => { | |
try { | |
ProcessRequest(context); | |
} | |
catch(Exception ex) { | |
var error = string.Format("Error this.ProcessRequest(context): [{0}]: {1}", ex.GetType().Name, ex.Message); | |
_log.ErrorFormat(error); | |
try { | |
var sb = new StringBuilder(); | |
sb.AppendLine("{"); | |
sb.AppendLine("\"ResponseStatus\":{"); | |
sb.AppendFormat(" \"ErrorCode\":{0},\n", ex.GetType().Name.EncodeJson()); | |
sb.AppendFormat(" \"Message\":{0},\n", ex.Message.EncodeJson()); | |
sb.AppendFormat(" \"StackTrace\":{0}\n", ex.StackTrace.EncodeJson()); | |
sb.AppendLine("}"); | |
sb.AppendLine("}"); | |
context.Response.StatusCode = 500; | |
context.Response.ContentType = ContentType.Json; | |
byte[] sbBytes = sb.ToString().ToUtf8Bytes(); | |
context.Response.OutputStream.Write(sbBytes, 0, sbBytes.Length); | |
context.Response.Close(); | |
} | |
catch(Exception errorEx) { | |
error = string.Format("Error this.ProcessRequest(context)(Exception while writing error to the response): [{0}]: {1}", | |
errorEx.GetType().Name, errorEx.Message); | |
_log.ErrorFormat(error); | |
} | |
} | |
}); | |
} | |
} | |
#endregion | |
#region threadpool | |
public abstract class AppHostHttpListenerThreadPool : AppHostHttpListenerBase { | |
private readonly AutoResetEvent _listenForNextRequest = new AutoResetEvent(false); | |
//private readonly SmartThreadPool _threadPoolManager; | |
private readonly ILog _log = LogManager.GetLogger(typeof(HttpListenerBase)); | |
private const int IdleTimeout = 300; | |
protected AppHostHttpListenerThreadPool(int poolSize = 500) { | |
//_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
protected AppHostHttpListenerThreadPool(string serviceName, params Assembly[] assembliesWithServices) | |
: this(serviceName, 500, assembliesWithServices) {} | |
protected AppHostHttpListenerThreadPool(string serviceName, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, assembliesWithServices) { | |
//_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
protected AppHostHttpListenerThreadPool(string serviceName, string handlerPath, params Assembly[] assembliesWithServices) | |
: this(serviceName, handlerPath, 500, assembliesWithServices) {} | |
protected AppHostHttpListenerThreadPool(string serviceName, string handlerPath, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, handlerPath, assembliesWithServices) { | |
//_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
private bool disposed = false; | |
protected override void Dispose(bool disposing) { | |
if(disposed) return; | |
lock(this) { | |
if(disposed) return; | |
/*if (disposing) | |
_threadPoolManager.Dispose();*/ | |
// new shared cleanup logic | |
disposed = true; | |
base.Dispose(disposing); | |
} | |
} | |
/// <summary> | |
/// Starts the Web Service | |
/// </summary> | |
/// <param name="urlBase"> | |
/// A Uri that acts as the base that the server is listening on. | |
/// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/ | |
/// Note: the trailing slash is required! For more info see the | |
/// HttpListener.Prefixes property on MSDN. | |
/// </param> | |
public override void Start(string urlBase) { | |
// *** Already running - just leave it in place | |
if(IsStarted) | |
return; | |
if(Listener == null) | |
Listener = new HttpListener(); | |
Listener.Prefixes.Add(urlBase); | |
IsStarted = true; | |
Listener.Start(); | |
ThreadPool.QueueUserWorkItem(Listen); | |
} | |
// Loop here to begin processing of new requests. | |
private void Listen(object state) { | |
while(Listener.IsListening) { | |
if(Listener == null) return; | |
try { | |
Listener.BeginGetContext(ListenerCallback, Listener); | |
_listenForNextRequest.WaitOne(); | |
} | |
catch(Exception ex) { | |
_log.Error("Listen()", ex); | |
return; | |
} | |
if(Listener == null) return; | |
} | |
} | |
// Handle the processing of a request in here. | |
private void ListenerCallback(IAsyncResult asyncResult) { | |
var listener = asyncResult.AsyncState as HttpListener; | |
HttpListenerContext context; | |
if(listener == null) return; | |
var isListening = listener.IsListening; | |
try { | |
if(!isListening) { | |
_log.DebugFormat("Ignoring ListenerCallback() as HttpListener is no longer listening"); | |
return; | |
} | |
// The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework, | |
// blocks until there is a request to be processed or some type of data is available. | |
context = listener.EndGetContext(asyncResult); | |
} | |
catch(Exception ex) { | |
// You will get an exception when httpListener.Stop() is called | |
// because there will be a thread stopped waiting on the .EndGetContext() | |
// method, and again, that is just the way most Begin/End asynchronous | |
// methods of the .NET Framework work. | |
string errMsg = ex + ": " + isListening; | |
_log.Warn(errMsg); | |
return; | |
} | |
finally { | |
// Once we know we have a request (or exception), we signal the other thread | |
// so that it calls the BeginGetContext() (or possibly exits if we're not | |
// listening any more) method to start handling the next incoming request | |
// while we continue to process this request on a different thread. | |
_listenForNextRequest.Set(); | |
} | |
_log.InfoFormat("{0} Request : {1}", context.Request.UserHostAddress, context.Request.RawUrl); | |
RaiseReceiveWebRequest(context); | |
ThreadPool.QueueUserWorkItem(o => { | |
try { | |
ProcessRequest(context); | |
} | |
catch(Exception ex) { | |
var error = string.Format("Error this.ProcessRequest(context): [{0}]: {1}", ex.GetType().Name, ex.Message); | |
_log.ErrorFormat(error); | |
try { | |
var sb = new StringBuilder(); | |
sb.AppendLine("{"); | |
sb.AppendLine("\"ResponseStatus\":{"); | |
sb.AppendFormat(" \"ErrorCode\":{0},\n", ex.GetType().Name.EncodeJson()); | |
sb.AppendFormat(" \"Message\":{0},\n", ex.Message.EncodeJson()); | |
sb.AppendFormat(" \"StackTrace\":{0}\n", ex.StackTrace.EncodeJson()); | |
sb.AppendLine("}"); | |
sb.AppendLine("}"); | |
context.Response.StatusCode = 500; | |
context.Response.ContentType = ContentType.Json; | |
byte[] sbBytes = sb.ToString().ToUtf8Bytes(); | |
context.Response.OutputStream.Write(sbBytes, 0, sbBytes.Length); | |
context.Response.Close(); | |
} | |
catch(Exception errorEx) { | |
error = string.Format("Error this.ProcessRequest(context)(Exception while writing error to the response): [{0}]: {1}", | |
errorEx.GetType().Name, errorEx.Message); | |
_log.ErrorFormat(error); | |
} | |
} | |
}); | |
} | |
} | |
#endregion | |
#region task | |
public abstract class AppHostHttpListenerTask : AppHostHttpListenerBase { | |
private readonly AutoResetEvent _listenForNextRequest = new AutoResetEvent(false); | |
//private readonly SmartThreadPool _threadPoolManager; | |
private readonly ILog _log = LogManager.GetLogger(typeof(HttpListenerBase)); | |
private const int IdleTimeout = 300; | |
protected AppHostHttpListenerTask(int poolSize = 500) { | |
//_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
protected AppHostHttpListenerTask(string serviceName, params Assembly[] assembliesWithServices) | |
: this(serviceName, 500, assembliesWithServices) {} | |
protected AppHostHttpListenerTask(string serviceName, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, assembliesWithServices) { | |
//_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
protected AppHostHttpListenerTask(string serviceName, string handlerPath, params Assembly[] assembliesWithServices) | |
: this(serviceName, handlerPath, 500, assembliesWithServices) {} | |
protected AppHostHttpListenerTask(string serviceName, string handlerPath, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, handlerPath, assembliesWithServices) { | |
//_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
private bool disposed = false; | |
protected override void Dispose(bool disposing) { | |
if(disposed) return; | |
lock(this) { | |
if(disposed) return; | |
/*if (disposing) | |
_threadPoolManager.Dispose();*/ | |
// new shared cleanup logic | |
disposed = true; | |
base.Dispose(disposing); | |
} | |
} | |
/// <summary> | |
/// Starts the Web Service | |
/// </summary> | |
/// <param name="urlBase"> | |
/// A Uri that acts as the base that the server is listening on. | |
/// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/ | |
/// Note: the trailing slash is required! For more info see the | |
/// HttpListener.Prefixes property on MSDN. | |
/// </param> | |
public override void Start(string urlBase) { | |
// *** Already running - just leave it in place | |
if(IsStarted) | |
return; | |
if(Listener == null) | |
Listener = new HttpListener(); | |
Listener.Prefixes.Add(urlBase); | |
IsStarted = true; | |
Listener.Start(); | |
ThreadPool.QueueUserWorkItem(Listen); | |
} | |
// Loop here to begin processing of new requests. | |
private void Listen(object state) { | |
while(Listener.IsListening) { | |
if(Listener == null) return; | |
try { | |
Listener.BeginGetContext(ListenerCallback, Listener); | |
_listenForNextRequest.WaitOne(); | |
} | |
catch(Exception ex) { | |
_log.Error("Listen()", ex); | |
return; | |
} | |
if(Listener == null) return; | |
} | |
} | |
// Handle the processing of a request in here. | |
private void ListenerCallback(IAsyncResult asyncResult) { | |
var listener = asyncResult.AsyncState as HttpListener; | |
HttpListenerContext context; | |
if(listener == null) return; | |
var isListening = listener.IsListening; | |
try { | |
if(!isListening) { | |
_log.DebugFormat("Ignoring ListenerCallback() as HttpListener is no longer listening"); | |
return; | |
} | |
// The EndGetContext() method, as with all Begin/End asynchronous methods in the .NET Framework, | |
// blocks until there is a request to be processed or some type of data is available. | |
context = listener.EndGetContext(asyncResult); | |
} | |
catch(Exception ex) { | |
// You will get an exception when httpListener.Stop() is called | |
// because there will be a thread stopped waiting on the .EndGetContext() | |
// method, and again, that is just the way most Begin/End asynchronous | |
// methods of the .NET Framework work. | |
string errMsg = ex + ": " + isListening; | |
_log.Warn(errMsg); | |
return; | |
} | |
finally { | |
// Once we know we have a request (or exception), we signal the other thread | |
// so that it calls the BeginGetContext() (or possibly exits if we're not | |
// listening any more) method to start handling the next incoming request | |
// while we continue to process this request on a different thread. | |
_listenForNextRequest.Set(); | |
} | |
_log.InfoFormat("{0} Request : {1}", context.Request.UserHostAddress, context.Request.RawUrl); | |
RaiseReceiveWebRequest(context); | |
Task.Factory.StartNew(() => { | |
try { | |
ProcessRequest(context); | |
} | |
catch(Exception ex) { | |
var error = string.Format("Error this.ProcessRequest(context): [{0}]: {1}", ex.GetType().Name, ex.Message); | |
_log.ErrorFormat(error); | |
try { | |
var sb = new StringBuilder(); | |
sb.AppendLine("{"); | |
sb.AppendLine("\"ResponseStatus\":{"); | |
sb.AppendFormat(" \"ErrorCode\":{0},\n", ex.GetType().Name.EncodeJson()); | |
sb.AppendFormat(" \"Message\":{0},\n", ex.Message.EncodeJson()); | |
sb.AppendFormat(" \"StackTrace\":{0}\n", ex.StackTrace.EncodeJson()); | |
sb.AppendLine("}"); | |
sb.AppendLine("}"); | |
context.Response.StatusCode = 500; | |
context.Response.ContentType = ContentType.Json; | |
byte[] sbBytes = sb.ToString().ToUtf8Bytes(); | |
context.Response.OutputStream.Write(sbBytes, 0, sbBytes.Length); | |
context.Response.Close(); | |
} | |
catch(Exception errorEx) { | |
error = string.Format("Error this.ProcessRequest(context)(Exception while writing error to the response): [{0}]: {1}", | |
errorEx.GetType().Name, errorEx.Message); | |
_log.ErrorFormat(error); | |
} | |
} | |
}); | |
} | |
} | |
#endregion | |
public abstract class AppHostHttpListenerEventBased : AppHostHttpListenerBase { | |
private readonly ILog _log = LogManager.GetLogger(typeof(HttpListenerBase)); | |
private readonly SmartThreadPool _threadPoolManager; | |
private const int IdleTimeout = 300; | |
protected AppHostHttpListenerEventBased(int poolSize = 500) { | |
_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
protected AppHostHttpListenerEventBased(string serviceName, params Assembly[] assembliesWithServices) | |
: this(serviceName, 500, assembliesWithServices) {} | |
protected AppHostHttpListenerEventBased(string serviceName, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, assembliesWithServices) { | |
_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
protected AppHostHttpListenerEventBased(string serviceName, string handlerPath, params Assembly[] assembliesWithServices) | |
: this(serviceName, handlerPath, 500, assembliesWithServices) {} | |
protected AppHostHttpListenerEventBased(string serviceName, string handlerPath, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, handlerPath, assembliesWithServices) { | |
_threadPoolManager = new SmartThreadPool(IdleTimeout, poolSize); | |
} | |
private bool disposed = false; | |
private HttpServer _server = null; | |
private IDisposable _handler = null; | |
protected override void Dispose(bool disposing) { | |
if(disposed) return; | |
lock(this) { | |
if(disposed) return; | |
if(disposing) { | |
_handler.Dispose(); | |
_server.Dispose(); | |
} | |
// new shared cleanup logic | |
disposed = true; | |
base.Dispose(disposing); | |
} | |
} | |
/// <summary> | |
/// Starts the Web Service | |
/// </summary> | |
/// <param name="urlBase"> | |
/// A Uri that acts as the base that the server is listening on. | |
/// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/ | |
/// Note: the trailing slash is required! For more info see the | |
/// HttpListener.Prefixes property on MSDN. | |
/// </param> | |
public override void Start(string urlBase) { | |
// *** Already running - just leave it in place | |
if(IsStarted) | |
return; | |
_server = new HttpServer(urlBase); | |
_handler = _server.Where(ctx => true) | |
.Subscribe(ctx => _threadPoolManager.QueueWorkItem(() => DoRealWork(ctx.c))); | |
IsStarted = true; | |
} | |
public void DoRealWork(HttpListenerContext context) { | |
_log.InfoFormat("{0} Request : {1}", context.Request.UserHostAddress, context.Request.RawUrl); | |
RaiseReceiveWebRequest(context); | |
try { | |
ProcessRequest(context); | |
} | |
catch(Exception ex) { | |
var error = string.Format("Error this.ProcessRequest(context): [{0}]: {1}", ex.GetType().Name, ex.Message); | |
_log.ErrorFormat(error); | |
try { | |
var sb = new StringBuilder(); | |
sb.AppendLine("{"); | |
sb.AppendLine("\"ResponseStatus\":{"); | |
sb.AppendFormat(" \"ErrorCode\":{0},\n", ex.GetType().Name.EncodeJson()); | |
sb.AppendFormat(" \"Message\":{0},\n", ex.Message.EncodeJson()); | |
sb.AppendFormat(" \"StackTrace\":{0}\n", ex.StackTrace.EncodeJson()); | |
sb.AppendLine("}"); | |
sb.AppendLine("}"); | |
context.Response.StatusCode = 500; | |
context.Response.ContentType = ContentType.Json; | |
byte[] sbBytes = sb.ToString().ToUtf8Bytes(); | |
context.Response.OutputStream.Write(sbBytes, 0, sbBytes.Length); | |
context.Response.Close(); | |
} | |
catch(Exception errorEx) { | |
error = string.Format("Error this.ProcessRequest(context)(Exception while writing error to the response): [{0}]: {1}", | |
errorEx.GetType().Name, errorEx.Message); | |
_log.ErrorFormat(error); | |
} | |
} | |
} | |
} | |
public class HttpAsyncHost : AppHostHttpListenerBase { | |
private HttpListener _listener; | |
private readonly ILog _log = LogManager.GetLogger(typeof(HttpListenerBase)); | |
private readonly int _accepts; | |
/// <summary> | |
/// Creates an asynchronous HTTP host. | |
/// </summary> | |
/// <param name="handler">Handler to serve requests with</param> | |
/// <param name="accepts"> | |
/// Higher values mean more connections can be maintained yet at a much slower average response time; fewer connections will be rejected. | |
/// Lower values mean less connections can be maintained yet at a much faster average response time; more connections will be rejected. | |
/// </param> | |
/// | |
public HttpAsyncHost(int accepts = 4) { | |
_listener = new HttpListener(); | |
// Multiply by number of cores: | |
_accepts = accepts*Environment.ProcessorCount; | |
} | |
protected HttpAsyncHost(string serviceName, params Assembly[] assembliesWithServices) | |
: this(serviceName, 500, assembliesWithServices) { | |
_listener = new HttpListener(); | |
// Multiply by number of cores: | |
_accepts = 4*Environment.ProcessorCount; | |
} | |
protected HttpAsyncHost(string serviceName, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, assembliesWithServices) { | |
_listener = new HttpListener(); | |
// Multiply by number of cores: | |
_accepts = 4*Environment.ProcessorCount; | |
} | |
protected HttpAsyncHost(string serviceName, string handlerPath, params Assembly[] assembliesWithServices) | |
: this(serviceName, handlerPath, 500, assembliesWithServices) { | |
_listener = new HttpListener(); | |
// Multiply by number of cores: | |
_accepts = 4*Environment.ProcessorCount; | |
} | |
protected HttpAsyncHost(string serviceName, string handlerPath, int poolSize, params Assembly[] assembliesWithServices) | |
: base(serviceName, handlerPath, assembliesWithServices) { | |
_listener = new HttpListener(); | |
// Multiply by number of cores: | |
_accepts = 4*Environment.ProcessorCount; | |
} | |
public List<string> Prefixes { | |
get { return _listener.Prefixes.ToList(); } | |
} | |
public override void Start(string urlBase) { | |
//Profiler.Settings.ProfilerProvider = new SingletonProfilerProvider(); | |
//Profiler.Settings.Storage = new MemoryStorage(); | |
//this.Config.WebHostUrl = "/"; | |
// *** Already running - just leave it in place | |
if(IsStarted) | |
return; | |
Run(urlBase); | |
IsStarted = true; | |
} | |
public void Run(params string[] uriPrefixes) { | |
_listener.IgnoreWriteExceptions = true; | |
// Add the server bindings: | |
foreach(var prefix in uriPrefixes) | |
_listener.Prefixes.Add(prefix); | |
Task.Run(async () => { | |
// Configure the handler: | |
try { | |
// Start the HTTP listener: | |
_listener.Start(); | |
} | |
catch(HttpListenerException hlex) { | |
Console.Error.WriteLine(hlex.Message); | |
return; | |
} | |
// Accept connections: | |
// Higher values mean more connections can be maintained yet at a much slower average response time; fewer connections will be rejected. | |
// Lower values mean less connections can be maintained yet at a much faster average response time; more connections will be rejected. | |
var sem = new Semaphore(_accepts, _accepts); | |
while(true) { | |
sem.WaitOne(); | |
#pragma warning disable 4014 | |
_listener.GetContextAsync().ContinueWith(async (t) => { | |
string errMessage; | |
try { | |
sem.Release(); | |
var ctx = await t; | |
await ProcessListenerContext(ctx, this); | |
return; | |
} | |
catch(Exception ex) { | |
errMessage = ex.ToString(); | |
} | |
await Console.Error.WriteLineAsync(errMessage); | |
}); | |
#pragma warning restore 4014 | |
} | |
}).Wait(); | |
} | |
private void DoRealWork(HttpListenerContext context) { | |
try { | |
//req_start(); | |
ProcessRequest(context); | |
//req_stop(); | |
} | |
catch(Exception ex) { | |
string error = string.Format("Error this.ProcessRequest(context): [{0}]: {1}", ex.GetType().Name, ex.Message); | |
_log.ErrorFormat(error); | |
try { | |
var sb = new StringBuilder(); | |
sb.AppendLine("{"); | |
sb.AppendLine("\"ResponseStatus\":{"); | |
sb.AppendFormat(" \"ErrorCode\":{0},\n", ex.GetType().Name.EncodeJson()); | |
sb.AppendFormat(" \"Message\":{0},\n", ex.Message.EncodeJson()); | |
sb.AppendFormat(" \"StackTrace\":{0}\n", ex.StackTrace.EncodeJson()); | |
sb.AppendLine("}"); | |
sb.AppendLine("}"); | |
context.Response.StatusCode = 500; | |
context.Response.ContentType = ContentType.Json; | |
byte[] sbBytes = sb.ToString().ToUtf8Bytes(); | |
context.Response.OutputStream.Write(sbBytes, 0, sbBytes.Length); | |
context.Response.Close(); | |
} | |
catch(Exception errorEx) { | |
error = string.Format("Error this.ProcessRequest(context)(Exception while writing error to the response): [{0}]: {1}", | |
errorEx.GetType().Name, errorEx.Message); | |
_log.ErrorFormat(error); | |
} | |
} | |
} | |
private async Task ProcessListenerContext(HttpListenerContext listenerContext, HttpAsyncHost host) { | |
Debug.Assert(listenerContext != null); | |
try { | |
RaiseReceiveWebRequest(listenerContext); | |
// Get the response action to take: | |
var action = await Task<bool>.Factory.StartNew(() => { | |
//DoRealWork(listenerContext); | |
DoRealWork(listenerContext); | |
return true; | |
}); | |
// Close the response and send it to the client: | |
listenerContext.Response.Close(); | |
} | |
catch(HttpListenerException) { | |
// Ignored. | |
} | |
catch(Exception ex) { | |
// TODO: better exception handling | |
Trace.WriteLine(ex.ToString()); | |
} | |
} | |
public override void Configure(Container container) { | |
throw new NotImplementedException(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment