I have problem with HttpListenerResponse class when client close connection I can´t copy response to outputStream it is logical because stream is interrupted. Is there some easy way how can fix it or handle this exceptions. I know HttpListener is not best solution for proxy server.
I try set up property IgnoreWriteExceptions on true but It´s not working.
public class Ask
{
private int requestCounter = 0;
private ManualResetEvent stopEvent = new ManualResetEvent(false);
public class HttpListenerCallbackState
{
private readonly HttpListener _listener;
private readonly System.Threading.AutoResetEvent _listenForNextRequest;
public HttpListenerCallbackState(HttpListener listener)
{
if (listener == null) throw new ArgumentNullException("listener");
_listener = listener;
_listenForNextRequest = new AutoResetEvent(false);
}
public HttpListener Listener { get { return _listener; } }
public AutoResetEvent ListenForNextRequest { get { return _listenForNextRequest; } }
}
public void ListenAsynchronously(IEnumerable<string> prefixes)
{
HttpListener listener = new HttpListener();
foreach (string s in prefixes)
{
listener.Prefixes.Add(s);
}
listener.IgnoreWriteExceptions = true;
listener.Start();
HttpListenerCallbackState state = new HttpListenerCallbackState(listener);
ThreadPool.QueueUserWorkItem(Listen, state);
}
public void StopListening()
{
stopEvent.Set();
}
private void Listen(object state)
{
HttpListenerCallbackState callbackState = (HttpListenerCallbackState)state;
while (callbackState.Listener.IsListening)
{
callbackState.Listener.BeginGetContext(new AsyncCallback(ListenerCallback), callbackState);
int n = WaitHandle.WaitAny(new WaitHandle[] { callbackState.ListenForNextRequest, stopEvent });
if (n == 1)
{
// stopEvent was signalled
callbackState.Listener.Stop();
break;
}
}
}
private void ListenerCallback(IAsyncResult ar)
{
HttpListenerCallbackState callbackState = (HttpListenerCallbackState)ar.AsyncState;
HttpListenerContext context = null;
int requestNumber = Interlocked.Increment(ref requestCounter);
try
{
context = callbackState.Listener.EndGetContext(ar);
}
catch (Exception ex)
{
return;
}
finally
{
callbackState.ListenForNextRequest.Set();
}
if (context == null) return;
HttpListenerRequest request = context.Request;
// add Proxy and network credentials
HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(request.Url.AbsoluteUri);
// get response
HttpWebResponse response = (HttpWebResponse)webReq.GetResponse();
Stream responseOut = context.Response.OutputStream;
int CopyByte = CopyStream(response, responseOut);
responseOut.Close();
}
}
}
Related
I have a problem: the server doesn't receive any data from the client.
Here is server initialization:
public void Start()
{
var listener = new TcpListener(IPAddress.Any, Port);
listener.Start();
Task.Run(
async () =>
{
while (!this.cancellationToken.IsCancellationRequested)
{
var client = await listener.AcceptTcpClientAsync();
var stream = client.GetStream();
string request = await ReceiveRequestAsync(stream);
await RequestHandlerAsync(request, stream);
}
listener.Stop();
}, this.cancellationToken);
}
Here is requesting client code (it is from unit test so server is initialized right here):
var server = new SimpleFtpServer();
server.Start();
using (TcpClient client = new TcpClient(RequestUri, Port))
{
NetworkStream stream = client.GetStream();
StreamWriter writer = new StreamWriter(stream)
{
AutoFlush = true,
};
writer.Write("zapros");
using (StreamReader reader = new StreamReader(stream))
{
Console.Writeline(reader.ReadToEnd());
}
}
server.Stop();
It worth saying that I have started learning async/await in C# really recently so probably the problem is in usage of them.
Thank you in advance!
its probably not perfect however I am in the same situation as you and created a Async TCP Client/Server for practice and experimentation.
The below is an excerpt of my implementation, it works s
Server:
public class AsyncServerDemo
{
private CancellationTokenSource cancel;
private readonly TcpListenerEx listener;
private Task WaitingForConnections;
private Timer timerCallAcceptClients;
public bool IsRunning { get; private set; }
public AsyncServerDemo(int port)
{
cancel = new CancellationTokenSource();
listener = new TcpListenerEx(IPAddress.Any, port);
}
private Task<string> WaitForMessageAsync(TcpClient client, CancellationToken token)
{
return Task.Run(() =>
{
StringBuilder sb = new StringBuilder();
bool dataAvailable = false;
while (!token.IsCancellationRequested)
{
while (client.Client.Available > 0)
{
dataAvailable = true;
int buffered = client.Client.Available;
byte[] buffer = new byte[buffered];
client.Client.Receive(buffer);
sb.Append(Encoding.ASCII.GetString(buffer));
}
if (dataAvailable)
{
dataAvailable = false;
return sb.ToString();
}
};
return string.Empty; //timeout
});
}
private Task AcceptClientAsync()
{
return Task.Factory.StartNew(async () =>
{
IsRunning = true && !cancel.IsCancellationRequested;
while (!cancel.IsCancellationRequested)
{
if (!listener.Pending())
{
continue;
}
TcpClient newClient = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
Stopwatch timeout = new Stopwatch();
timeout.Restart();
string message = await WaitForMessageAsync(newClient, new CancellationTokenSource(TimeSpan.FromSeconds(5)).Token);
if (message != null)
{
//TODO: Message recieved
}
timeout.Stop();
}
});
}
public void Start()
{
listener.Start();
timerCallAcceptClients = new Timer(new TimerCallback((state) =>
{
AcceptClientAsync();
}), null, 0, (int)TimeSpan.FromSeconds(1).TotalMilliseconds);
}
public async void Stop()
{
if (!IsRunning) return;
using (cancel)
cancel.Cancel();
timerCallAcceptClients.Dispose();
if (WaitingForConnections != null)
await WaitingForConnections;
cancel = null;
listener.Stop();
IsRunning = false;
cancel = new CancellationTokenSource();
}
}
Client:
public class ClientExDemo
{
private Task<string> WaitForMessage;
private NetworkStream currentStream;
private CancellationTokenSource messageToken;
public EventHandler<ClientEx> OnServerFound;
public TcpClient Connection;
public EventHandler<string> OnMessage;
public async Task StartListenAsync(CancellationTokenSource token = null)
{
if (token == null)
messageToken = new CancellationTokenSource();
else
messageToken = token;
currentStream = Connection.GetStream();
string message = "";
if (message.Length > 0)
OnMessage?.Invoke(this, message);
if (!messageToken.IsCancellationRequested)
{
await StartListenAsync(token);
}
Timeout();
}
protected virtual void Timeout()
{
}
public async Task WaitForServerAsync(string ip, int port)
{
do
{
try
{
await Connection.ConnectAsync(ip, port);
}
catch (SocketException x)
{
}
await Task.Delay(50);
} while (!Connection.Connected);
}
public void StopListen()
{
using (messageToken)
{
messageToken.Cancel();
}
try
{
WaitForMessage.GetAwaiter().GetResult();
}
catch (AggregateException)
{
}
currentStream.Close();
messageToken = null;
currentStream = null;
WaitForMessage = null;
}
public ClientExDemo()
{
Connection = new TcpClient();
OnServerFound += ServerFound;
}
private void ServerFound(object sender, ClientEx args)
{
}
public void Send(string message)
{
Connection.Client.Send(Encoding.ASCII.GetBytes(message));
}
}
You can send messages from the client in a simple console application:
ClientEx client= new ClientEx();
await client.WaitForServerAsync(ip, port);
string msg = string.Empty;
do
{
Console.Write("Send Message: ");
msg = Console.ReadLine();
shell.Send(msg);
} while (msg != "q");
Console.WriteLine();
Console.WriteLine("BYE");
Console.ReadKey();
By using this code I created a HttpListner to send custom messages to browser for some sites. The code works properly and message is getting displayed . But , when I am trying to stop the Httpserver the application gets blocked on this line :
_listenerThread.Join();
I am new to threading , so any help to how I can make my code work.
Below is my code
private readonly HttpListener _listener;
private readonly Thread _listenerThread;
private readonly Thread[] _workers;
private readonly ManualResetEvent _stop, _ready;
private Queue<HttpListenerContext> _queue;
public HttpServer(int MaxThreads)
{
_workers = new Thread[MaxThreads];
_queue = new Queue<HttpListenerContext>();
_stop = new ManualResetEvent(false);
_ready = new ManualResetEvent(false);
_listener = new HttpListener();
_listenerThread = new Thread(HandleRequests);
}
private void HandleRequests()
{
while (_listener.IsListening)
{
var context = _listener.BeginGetContext(GetContextCallBack, null);
if (0 == WaitHandle.WaitAny(new WaitHandle[] { _stop, context.AsyncWaitHandle }))
{
return;
}
}
}
private void GetContextCallBack(IAsyncResult result)
{
try
{
lock(_queue)
{
_queue.Enqueue(_listener.EndGetContext(result));
_ready.Set();
}
}
catch
{ return; }
}
public void Start(int port)
{
_listener.Prefixes.Add(String.Format(#"http://+:{0}/", port));
_listener.Start();
_listenerThread.Start();
for (int i = 0; i < _workers.Length; i++)
{
_workers[i] = new Thread(Worker);
_workers[i].Start();
}
}
private void Worker(object obj)
{
WaitHandle[] wait = new[] { _ready, _stop };
while (0 == WaitHandle.WaitAny(wait))
{
HttpListenerContext context;
lock (_queue)
{
if (_queue.Count > 0)
context = _queue.Dequeue();
else
{
_ready.Reset();
continue;
}
}
try
{
ProcessRequest(context);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message + ex.StackTrace);
}
}
}
public void Dispose()
{
Stop();
}
private void Stop()
{
try
{
_stop.Reset();
_listenerThread.Join();
foreach (Thread worker in _workers)
{
worker.Join();
}
_listener.Stop();
App.Current.MainWindow.Close();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message + ex.StackTrace);
}
}
public void ProcessRequest(HttpListenerContext context)
{
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
System.Text.StringBuilder sb = new StringBuilder();
sb.Append("");
sb.Append(string.Format("HttpMethod: {0}", request.HttpMethod));
sb.Append(string.Format("Uri: {0}", request.Url.AbsoluteUri));
sb.Append(string.Format("LocalPath: {0}", request.Url.LocalPath));
foreach (string key in request.QueryString.Keys)
{
sb.Append(string.Format("Query: {0} = {1}", key, request.QueryString[key]));
}
sb.Append("");
string responseString = sb.ToString();
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
using (System.IO.Stream outputStream = response.OutputStream)
{
outputStream.Write(buffer, 0, buffer.Length);
}
}
This code might help others too who are looking to send custom messages to web browser.
This is the first problem in the "stopping" thread:
_listenerThread.Join();
...
_listener.Stop();
You're waiting for the listener thread to stop before you tell the listener to stop listening... but the listener thread has:
private void HandleRequests()
{
while (_listener.IsListening)
{
...
}
}
... so it will only terminate after the listener has stopped. You've basically got a deadlock. You should change the order of the stopping code:
_listener.Stop();
_listenerThread.Join();
...
Additionally this line:
_stop.Reset();
should be:
_stop.Set();
as you want to signal that you're stopping - not clear the signal.
ALL,
I am using an Asynchronous socket in C#.
My problem is: I want to wait for a callback to finish with connection, because I need to immediately send the information to a server.
Here is a code snippet:
class InternetConnector
{
private struct ConnectionData
{
public Action<Exception> ErrorHandler { get; set; }
public Socket Socket { get; set; }
}
public void ConnectToHost(Action<Exception> errorHandler)
{
IPEndPoint ip = new IPEndPoint(IPAddress.Parse(connector_host), connector_port);
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var ConnectionData = new ConnectionData { ErrorHandler = errorHandler, Socket = client };
client.Blocking = true;
client.BeginConnect(ip, new AsyncCallback(ConnectCallback), ConnectionData);
connectDone.WaitOne(100);
}
private static void ConnectCallback(IAsyncResult ar)
{
ConnectionData connectionData = new ConnectionData();
try
{
connectionData = (ConnectionData)ar.AsyncState;
connectionData.Socket.EndConnect(ar);
connectDone.Set();
Connected = true;
}
catch (Exception e)
{
if (connectionData.ErrorHandler != null)
connectionData.ErrorHandler(e);
}
}
}
public partial class Form1 : Form
{
private bool isRunning = false;
private InternetConnector client = new InternetConnector();
private void AsyncErrorHandler(Exception e)
{
if (status.InvokeRequired)
{
status.BeginInvoke(new Action(() => AsyncErrorHandler(e)));
return;
}
InternetConnector.Connected = false;
isRunning = false;
startStop.Text = "Start";
status.ForeColor = Color.Red;
status.Text = "Socket Error: " + e.Message;
}
private void startStop_Click(object sender, EventArgs e)
{
if (!isRunning || !InternetConnector.Connected)
{
if (!InternetConnector.Connected)
{
client.SetAddress(ipAddress.Text);
client.SetPort(Convert.ToInt32(connectionport.Text));
client.ConnectToHost( AsyncErrorHandler );
status.Text = "Signals Receiver: Connected";
status.ForeColor = Color.Green;
startStop.Text = "Stop";
isRunning = true;
// if connection successful, send some data and start reading the socket
}
else
{
startStop.Text = "Start";
client.DisconnectFromHost(AsyncErrorHandler);
isRunning = false;
}
}
}
}
I can handle the exception in the connection. Now I need to handle the successful connection as well.
Thank you.
You can follow the same pattern, and supply a handler to be called on success as well as on error:
class InternetConnector
{
private struct ConnectionData
{
public Action<Socket> SuccessHandler { get; set; }
public Action<Exception> ErrorHandler { get; set; }
public Socket Socket { get; set; }
}
public void ConnectToHost(Action<Socket> successHandler, Action<Exception> errorHandler)
{
IPEndPoint ip = new IPEndPoint(IPAddress.Parse(connector_host), connector_port);
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var ConnectionData = new ConnectionData
{
SuccessHandler = successHandler,
ErrorHandler = errorHandler,
Socket = client
};
client.Blocking = true;
client.BeginConnect(ip, new AsyncCallback(ConnectCallback), connectionData); // <-- make sure to use the lower-case connectionData here! :)
connectDone.WaitOne(100);
}
private static void ConnectCallback(IAsyncResult ar)
{
ConnectionData connectionData = new ConnectionData();
try
{
connectionData = (ConnectionData)ar.AsyncState;
connectionData.Socket.EndConnect(ar);
connectDone.Set();
Connected = true;
if (connectionData.SuccessHandler != null)
connectionData.SuccessHandler(connectionData.Socket);
}
catch (Exception e)
{
if (connectionData.ErrorHandler != null)
connectionData.ErrorHandler(e);
}
}
}
The signature of the function you pass as a success handler must match the Action<Socket> delegate, which would look something like:
void MySuccessHandler(Socket socket)
{
// do stuff with the connected socket..
Console.WriteLine("Connected to {0}", socket.RemoteEndPoint);
}
void MyErrorHandler(Exception e)
{
Console.WriteLine("Connection error {0}", e.Message);
}
...
myConnector.ConnectToHost(MySuccessHandler, MyErrorHandler);
Maybe some variant of this extension method can inspire. It takes a an action and a timespan and waits for one of task or timeout to complete first.
In case the timeout wins the race, the supplied action is executed.
public static async Task<bool> OnTimeout<T>(this T t, Action<T> action, TimeSpan timespan) where T : Task
{
var timeout = Task.Delay(timespan);
if (await Task.WhenAny(t, timeout) == timeout)
{
//Enter here on timeout
action(t);
return true;
}
else
{
return false;
}
}
Used like this where some actions can be taken in case of a timeout.
await socket.ConnectAsync().OnTimeout(t => {
throw new TimeoutException();
}, TimeSpan.FromSeconds(5));
I have implemented an asynchronous http listener in c#.
I followed the tutorial provided here by Microsoft
and found another tutorial which i stupidly not bookmarked and now can't find again. Meaning that I have some code that I would not have written that way myself but the explanations provided made sense so I followed that.
Now I am facing two problems:
First, I have to restart the listener after each request with Listener.Stop() and then call the StartListening method and again and second, when I do this, I receive each request twice.
The request does net get sent twice, but I receive it twice.
It does not however get received twice when I pause the Thread I am listening on for about 2 seconds.
I am sorry if I am quite vague in my explanations, but so is my understanding of my problem, I have no idea what is causing it.
Since the callback method is where most of the stuff happens, I will just post it, please tell me if you need any more code.
Any help will be gladly appreciated, since I am really stuck on this one.
public void ListenAsynchronously()
{
if (listener.Prefixes.Count == 0) foreach (string s in prefixes) listener.Prefixes.Add(s);
try
{
listener.Start();
}
catch (Exception e)
{
Logging.logException(e);
}
System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(Listen));
}
private void Listen(object state)
{
while (listener.IsListening)
{
listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener);
listenForNextRequest.WaitOne();
}
}
private void ListenerCallback(IAsyncResult ar)
{
HttpListener httplistener = ar.AsyncState as System.Net.HttpListener;
System.Net.HttpListenerContext context = null;
int requestNumber = System.Threading.Interlocked.Increment(ref requestCounter);
if (httplistener == null) return;
try
{
context = httplistener.EndGetContext(ar);
}
catch(Exception ex)
{
return;
}
finally
{
listenForNextRequest.Set();
}
if (context == null) return;
System.Net.HttpListenerRequest request = context.Request;
if (request.HasEntityBody)
{
using (System.IO.StreamReader sr = new System.IO.StreamReader(request.InputStream, request.ContentEncoding))
{
string requestData = sr.ReadToEnd();
//Stuff I do with the request happens here
}
}
try
{
using (System.Net.HttpListenerResponse response = context.Response)
{
//response stuff happens here
}
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.LongLength;
response.OutputStream.Write(buffer, 0, buffer.Length);
response.Close();
StopListening();
//If I dont set the thread to sleep here, I receive the double requests
System.Threading.Thread.Sleep(2500);
ListenAsynchronously();
}
}
catch (Exception e)
{
}
}
I am not sure why you are calling StopListening() and ListenAsynchronously() in your ListenerCallback() method. The Listen() method is being run in a thread and will continue to get each next incoming request. If I was writing this, I would not be using a instance variable of HttpListener. Create a new one in your ListenAsynchronously method and pass it in your state object, for example,
public class HttpListenerCallbackState
{
private readonly HttpListener _listener;
private readonly AutoResetEvent _listenForNextRequest;
public HttpListenerCallbackState(HttpListener listener)
{
if (listener == null) throw new ArgumentNullException("listener");
_listener = listener;
_listenForNextRequest = new AutoResetEvent(false);
}
public HttpListener Listener { get { return _listener; } }
public AutoResetEvent ListenForNextRequest { get { return _listenForNextRequest; } }
}
public class HttpRequestHandler
{
private int requestCounter = 0;
private ManualResetEvent stopEvent = new ManualResetEvent(false);
public void ListenAsynchronously(IEnumerable<string> prefixes)
{
HttpListener listener = new HttpListener();
foreach (string s in prefixes)
{
listener.Prefixes.Add(s);
}
listener.Start();
HttpListenerCallbackState state = new HttpListenerCallbackState(listener);
ThreadPool.QueueUserWorkItem(Listen, state);
}
public void StopListening()
{
stopEvent.Set();
}
private void Listen(object state)
{
HttpListenerCallbackState callbackState = (HttpListenerCallbackState)state;
while (callbackState.Listener.IsListening)
{
callbackState.Listener.BeginGetContext(new AsyncCallback(ListenerCallback), callbackState);
int n = WaitHandle.WaitAny(new WaitHandle[] { callbackState.ListenForNextRequest, stopEvent});
if (n == 1)
{
// stopEvent was signalled
callbackState.Listener.Stop();
break;
}
}
}
private void ListenerCallback(IAsyncResult ar)
{
HttpListenerCallbackState callbackState = (HttpListenerCallbackState)ar.AsyncState;
HttpListenerContext context = null;
int requestNumber = Interlocked.Increment(ref requestCounter);
try
{
context = callbackState.Listener.EndGetContext(ar);
}
catch (Exception ex)
{
return;
}
finally
{
callbackState.ListenForNextRequest.Set();
}
if (context == null) return;
HttpListenerRequest request = context.Request;
if (request.HasEntityBody)
{
using (System.IO.StreamReader sr = new System.IO.StreamReader(request.InputStream, request.ContentEncoding))
{
string requestData = sr.ReadToEnd();
//Stuff I do with the request happens here
}
}
try
{
using (HttpListenerResponse response = context.Response)
{
//response stuff happens here
string responseString = "Ok";
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.LongLength;
response.OutputStream.Write(buffer, 0, buffer.Length);
response.Close();
}
}
catch (Exception e)
{
}
}
}
I have a listener:
listener = new HttpListener();
listener.Prefixes.Add(#"http://+:8077/");
listener.Start();
listenerThread = new Thread(HandleRequests);
listenerThread.Start();
And I am handling requests:
private void HandleRequests()
{
while (listener.IsListening)
{
var context = listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener);
context.AsyncWaitHandle.WaitOne();
}
}
private void ListenerCallback(IAsyncResult ar)
{
var listener = ar.AsyncState as HttpListener;
var context = listener.EndGetContext(ar);
//do some stuff
}
I would like to write void Stop() in such a way, that:
It will block until all currently handled requests will end (ie. will wait for all threads to "do some stuff").
While it will wait for already started requests, it will not allow any more requests (ie. return at the beginning of ListenerCallback).
After that it will call listener.Stop() (listener.IsListening became false).
How could it be write?
EDIT: What do you think about this solution? Is it safe?
public void Stop()
{
lock (this)
{
isStopping = true;
}
resetEvent.WaitOne(); //initially set to true
listener.Stop();
}
private void ListenerCallback(IAsyncResult ar)
{
lock (this)
{
if (isStopping)
return;
resetEvent.Reset();
numberOfRequests++;
}
var listener = ar.AsyncState as HttpListener;
var context = listener.EndGetContext(ar);
//do some stuff
lock (this)
{
if (--numberOfRequests == 0)
resetEvent.Set();
}
}
For completeness, here is what it would look like if you manage your own worker threads:
class HttpServer : IDisposable
{
private readonly HttpListener _listener;
private readonly Thread _listenerThread;
private readonly Thread[] _workers;
private readonly ManualResetEvent _stop, _ready;
private Queue<HttpListenerContext> _queue;
public HttpServer(int maxThreads)
{
_workers = new Thread[maxThreads];
_queue = new Queue<HttpListenerContext>();
_stop = new ManualResetEvent(false);
_ready = new ManualResetEvent(false);
_listener = new HttpListener();
_listenerThread = new Thread(HandleRequests);
}
public void Start(int port)
{
_listener.Prefixes.Add(String.Format(#"http://+:{0}/", port));
_listener.Start();
_listenerThread.Start();
for (int i = 0; i < _workers.Length; i++)
{
_workers[i] = new Thread(Worker);
_workers[i].Start();
}
}
public void Dispose()
{ Stop(); }
public void Stop()
{
_stop.Set();
_listenerThread.Join();
foreach (Thread worker in _workers)
worker.Join();
_listener.Stop();
}
private void HandleRequests()
{
while (_listener.IsListening)
{
var context = _listener.BeginGetContext(ContextReady, null);
if (0 == WaitHandle.WaitAny(new[] { _stop, context.AsyncWaitHandle }))
return;
}
}
private void ContextReady(IAsyncResult ar)
{
try
{
lock (_queue)
{
_queue.Enqueue(_listener.EndGetContext(ar));
_ready.Set();
}
}
catch { return; }
}
private void Worker()
{
WaitHandle[] wait = new[] { _ready, _stop };
while (0 == WaitHandle.WaitAny(wait))
{
HttpListenerContext context;
lock (_queue)
{
if (_queue.Count > 0)
context = _queue.Dequeue();
else
{
_ready.Reset();
continue;
}
}
try { ProcessRequest(context); }
catch (Exception e) { Console.Error.WriteLine(e); }
}
}
public event Action<HttpListenerContext> ProcessRequest;
}
Well there are several ways to solve this... This is a simple example that uses a semaphore to track ongoing work, and a signal that is raised when all workers are finished. This should give you a basic idea to work from.
The solution below is not ideal, ideally we should acquire the semaphore before calling BeginGetContext. That makes shutdown more difficult, so I've chosen to use this more simplified approach. If I were doing this for 'real' I'd probably write my own thread management rather than relying on the ThreadPool. This would allow a more dependable shutdown.
Anyway here is the complete example:
class TestHttp
{
static void Main()
{
using (HttpServer srvr = new HttpServer(5))
{
srvr.Start(8085);
Console.WriteLine("Press [Enter] to quit.");
Console.ReadLine();
}
}
}
class HttpServer : IDisposable
{
private readonly int _maxThreads;
private readonly HttpListener _listener;
private readonly Thread _listenerThread;
private readonly ManualResetEvent _stop, _idle;
private readonly Semaphore _busy;
public HttpServer(int maxThreads)
{
_maxThreads = maxThreads;
_stop = new ManualResetEvent(false);
_idle = new ManualResetEvent(false);
_busy = new Semaphore(maxThreads, maxThreads);
_listener = new HttpListener();
_listenerThread = new Thread(HandleRequests);
}
public void Start(int port)
{
_listener.Prefixes.Add(String.Format(#"http://+:{0}/", port));
_listener.Start();
_listenerThread.Start();
}
public void Dispose()
{ Stop(); }
public void Stop()
{
_stop.Set();
_listenerThread.Join();
_idle.Reset();
//aquire and release the semaphore to see if anyone is running, wait for idle if they are.
_busy.WaitOne();
if(_maxThreads != 1 + _busy.Release())
_idle.WaitOne();
_listener.Stop();
}
private void HandleRequests()
{
while (_listener.IsListening)
{
var context = _listener.BeginGetContext(ListenerCallback, null);
if (0 == WaitHandle.WaitAny(new[] { _stop, context.AsyncWaitHandle }))
return;
}
}
private void ListenerCallback(IAsyncResult ar)
{
_busy.WaitOne();
try
{
HttpListenerContext context;
try
{ context = _listener.EndGetContext(ar); }
catch (HttpListenerException)
{ return; }
if (_stop.WaitOne(0, false))
return;
Console.WriteLine("{0} {1}", context.Request.HttpMethod, context.Request.RawUrl);
context.Response.SendChunked = true;
using (TextWriter tw = new StreamWriter(context.Response.OutputStream))
{
tw.WriteLine("<html><body><h1>Hello World</h1>");
for (int i = 0; i < 5; i++)
{
tw.WriteLine("<p>{0} # {1}</p>", i, DateTime.Now);
tw.Flush();
Thread.Sleep(1000);
}
tw.WriteLine("</body></html>");
}
}
finally
{
if (_maxThreads == 1 + _busy.Release())
_idle.Set();
}
}
}
I have consulted my code in EDIT part of my question and I've decided to accept it with some modifications:
public void Stop()
{
lock (locker)
{
isStopping = true;
}
resetEvent.WaitOne(); //initially set to true
listener.Stop();
}
private void ListenerCallback(IAsyncResult ar)
{
lock (locker) //locking on this is a bad idea, but I forget about it before
{
if (isStopping)
return;
resetEvent.Reset();
numberOfRequests++;
}
try
{
var listener = ar.AsyncState as HttpListener;
var context = listener.EndGetContext(ar);
//do some stuff
}
finally //to make sure that bellow code will be executed
{
lock (locker)
{
if (--numberOfRequests == 0)
resetEvent.Set();
}
}
}
Simply calling listener.Stop() should do the trick. This will not terminate any connections that have already been established but will prevent any new connections.
This uses the BlockingCollection typed queue to service requests. It is usable as is. You should derive a class from this one and override Response.
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Text;
using System.Threading;
namespace Service
{
class HttpServer : IDisposable
{
private HttpListener httpListener;
private Thread listenerLoop;
private Thread[] requestProcessors;
private BlockingCollection<HttpListenerContext> messages;
public HttpServer(int threadCount)
{
requestProcessors = new Thread[threadCount];
messages = new BlockingCollection<HttpListenerContext>();
httpListener = new HttpListener();
}
public virtual int Port { get; set; } = 80;
public virtual string[] Prefixes
{
get { return new string[] {string.Format(#"http://+:{0}/", Port )}; }
}
public void Start(int port)
{
listenerLoop = new Thread(HandleRequests);
foreach( string prefix in Prefixes ) httpListener.Prefixes.Add( prefix );
listenerLoop.Start();
for (int i = 0; i < requestProcessors.Length; i++)
{
requestProcessors[i] = StartProcessor(i, messages);
}
}
public void Dispose() { Stop(); }
public void Stop()
{
messages.CompleteAdding();
foreach (Thread worker in requestProcessors) worker.Join();
httpListener.Stop();
listenerLoop.Join();
}
private void HandleRequests()
{
httpListener.Start();
try
{
while (httpListener.IsListening)
{
Console.WriteLine("The Linstener Is Listening!");
HttpListenerContext context = httpListener.GetContext();
messages.Add(context);
Console.WriteLine("The Linstener has added a message!");
}
}
catch(Exception e)
{
Console.WriteLine (e.Message);
}
}
private Thread StartProcessor(int number, BlockingCollection<HttpListenerContext> messages)
{
Thread thread = new Thread(() => Processor(number, messages));
thread.Start();
return thread;
}
private void Processor(int number, BlockingCollection<HttpListenerContext> messages)
{
Console.WriteLine ("Processor {0} started.", number);
try
{
for (;;)
{
Console.WriteLine ("Processor {0} awoken.", number);
HttpListenerContext context = messages.Take();
Console.WriteLine ("Processor {0} dequeued message.", number);
Response (context);
}
} catch { }
Console.WriteLine ("Processor {0} terminated.", number);
}
public virtual void Response(HttpListenerContext context)
{
SendReply(context, new StringBuilder("<html><head><title>NULL</title></head><body>This site not yet implementd.</body></html>") );
}
public static void SendReply(HttpListenerContext context, StringBuilder responseString )
{
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString.ToString());
context.Response.ContentLength64 = buffer.Length;
System.IO.Stream output = context.Response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
}
}
}
This is a sample of how to use it. No need to use events or any lock blocks. The BlockingCollection solves all these problems.
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
namespace Service
{
class Server
{
public static void Main (string[] args)
{
HttpServer Service = new QuizzServer (8);
Service.Start (80);
for (bool coninute = true; coninute ;)
{
string input = Console.ReadLine ().ToLower();
switch (input)
{
case "stop":
Console.WriteLine ("Stop command accepted.");
Service.Stop ();
coninute = false;
break;
default:
Console.WriteLine ("Unknown Command: '{0}'.",input);
break;
}
}
}
}
}