Skip to content

Commit 746ab0a

Browse files
authored
Add WebServer status event (#241)
1 parent 5e0136f commit 746ab0a

File tree

10 files changed

+196
-84
lines changed

10 files changed

+196
-84
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ With the previous example the following happens:
196196

197197
All up, this is an example to show how to use authentication, it's been defined to allow flexibility.
198198

199-
200199
## Managing incoming queries thru events
201200

202201
Very basic usage is the following:
@@ -394,6 +393,20 @@ using (WebServer server = new WebServer(443, HttpProtocol.Https)
394393

395394
You can of course use the routes as defined earlier. Both will work, event or route with the notion of controller.
396395

396+
## WebServer status
397+
398+
It is possible to subscribe to an event to get the WebServer status. That can be useful to restart the server, put in place a retry mechanism or equivalent.
399+
400+
```csharp
401+
server.WebServerStatusChanged += WebServerStatusChanged;
402+
403+
private static void WebServerStatusChanged(object obj, WebServerStatusEventArgs e)
404+
{
405+
// Do whatever you need like restarting the server
406+
Debug.WriteLine($"The web server is now {(e.Status == WebServerStatus.Running ? "running" : "stopped" )}");
407+
}
408+
```
409+
397410
## Feedback and documentation
398411

399412
For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home).

nanoFramework.WebServer.FileSystem/nanoFramework.WebServer.FileSystem.nfproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@
6868
<Compile Include="..\nanoFramework.WebServer\WebServer.cs">
6969
<Link>WebServer.cs</Link>
7070
</Compile>
71+
<Compile Include="..\nanoFramework.WebServer\WebServerStatus.cs">
72+
<Link>WebServerStatus.cs</Link>
73+
</Compile>
74+
<Compile Include="..\nanoFramework.WebServer\WebServerStatusEventArgs.cs">
75+
<Link>WebServerStatusEventArgs.cs</Link>
76+
</Compile>
7177
<None Include="..\key.snk" />
7278
</ItemGroup>
7379
<ItemGroup>

nanoFramework.WebServer/WebServer.cs

Lines changed: 117 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class WebServer : IDisposable
4343

4444
#region internal objects
4545

46-
private bool _cancel = false;
46+
private bool _cancel = true;
4747
private Thread _serverThread = null;
4848
private readonly ArrayList _callbackRoutes;
4949
private readonly HttpListener _listener;
@@ -92,6 +92,11 @@ public SslProtocols SslProtocols
9292
/// </summary>
9393
public string ApiKey { get; set; }
9494

95+
/// <summary>
96+
/// Gets a value indicating whether the web server is running.
97+
/// </summary>
98+
public bool IsRunning => !_cancel;
99+
95100
#endregion
96101

97102
#region Param
@@ -191,7 +196,7 @@ private void RegisterControllers(Type[] controllers)
191196
{
192197
return;
193198
}
194-
199+
195200
foreach (var controller in controllers)
196201
{
197202
var controlAttribs = controller.GetCustomAttributes(true);
@@ -216,7 +221,7 @@ private void RegisterControllers(Type[] controllers)
216221
{
217222
continue;
218223
}
219-
224+
220225
var callbackRoutes = new CallbackRoutes
221226
{
222227
Route = ((RouteAttribute)attrib).Route,
@@ -330,6 +335,8 @@ private Authentication ExtractAuthentication(string strAuth)
330335
/// <summary>
331336
/// Delegate for the CommandReceived event.
332337
/// </summary>
338+
/// <param name="obj">The source of the event.</param>
339+
/// <param name="e">A WebServerEventArgs that contains the event data.</param>
333340
public delegate void GetRequestHandler(object obj, WebServerEventArgs e);
334341

335342
/// <summary>
@@ -338,6 +345,18 @@ private Authentication ExtractAuthentication(string strAuth)
338345
/// </summary>
339346
public event GetRequestHandler CommandReceived;
340347

348+
/// <summary>
349+
/// Represents the method that will handle the WebServerStatusChanged event of a WebServer.
350+
/// </summary>
351+
/// <param name="obj">The source of the event.</param>
352+
/// <param name="e">A WebServerStatusEventArgs that contains the event data.</param>
353+
public delegate void WebServerStatusHandler(object obj, WebServerStatusEventArgs e);
354+
355+
/// <summary>
356+
/// Occurs when the status of the WebServer changes.
357+
/// </summary>
358+
public event WebServerStatusHandler WebServerStatusChanged;
359+
341360
#endregion
342361

343362
#region Public and private methods
@@ -353,8 +372,10 @@ public bool Start()
353372
}
354373

355374
bool bStarted = true;
375+
#if DEBUG
356376
// List Ethernet interfaces, so we can determine the server's address
357377
ListInterfaces();
378+
#endif
358379
// start server
359380
try
360381
{
@@ -368,6 +389,12 @@ public bool Start()
368389
_cancel = true;
369390
bStarted = false;
370391
}
392+
393+
if (bStarted)
394+
{
395+
WebServerStatusChanged?.Invoke(this, new WebServerStatusEventArgs(WebServerStatus.Running));
396+
}
397+
371398
return bStarted;
372399
}
373400

@@ -380,7 +407,8 @@ public void Stop()
380407
Thread.Sleep(100);
381408
_serverThread.Abort();
382409
_serverThread = null;
383-
Debug.WriteLine("Stopped server in thread ");
410+
// Event is generate in the running thread
411+
Debug.WriteLine("Stopped server in thread ");
384412
}
385413

386414
/// <summary>
@@ -487,106 +515,117 @@ public static void SendFileOverHTTP(HttpListenerResponse response, string fileNa
487515

488516
private void StartListener()
489517
{
490-
_listener.Start();
491-
while (!_cancel)
518+
try
492519
{
493-
HttpListenerContext context = _listener.GetContext();
494-
if (context == null)
495-
{
496-
return;
497-
}
498-
499-
new Thread(() =>
520+
_listener.Start();
521+
while (!_cancel)
500522
{
501-
//This is for handling with transitory or bad requests
502-
if (context.Request.RawUrl == null)
523+
HttpListenerContext context = _listener.GetContext();
524+
if (context == null)
503525
{
504526
return;
505527
}
506528

507-
// Variables used only within the "for". They are here for performance reasons
508-
bool mustAuthenticate;
509-
bool isAuthOk;
510-
bool isRoute = false;
511-
512-
foreach (CallbackRoutes route in _callbackRoutes)
529+
new Thread(() =>
513530
{
514-
if (!IsRouteMatch(route, context.Request.HttpMethod, context.Request.RawUrl))
531+
//This is for handling with transitory or bad requests
532+
if (context.Request.RawUrl == null)
515533
{
516-
continue;
534+
return;
517535
}
518536

519-
isRoute = true;
520-
521-
// Check auth first
522-
mustAuthenticate = route.Authentication != null && route.Authentication.AuthenticationType != AuthenticationType.None;
523-
isAuthOk = !mustAuthenticate;
537+
// Variables used only within the "for". They are here for performance reasons
538+
bool mustAuthenticate;
539+
bool isAuthOk;
540+
bool isRoute = false;
524541

525-
if (mustAuthenticate)
542+
foreach (CallbackRoutes route in _callbackRoutes)
526543
{
527-
if (route.Authentication.AuthenticationType == AuthenticationType.Basic)
544+
if (!IsRouteMatch(route, context.Request.HttpMethod, context.Request.RawUrl))
528545
{
529-
var credSite = route.Authentication.Credentials ?? Credential;
530-
var credReq = context.Request.Credentials;
531-
532-
isAuthOk = credReq != null
533-
&& (credSite.UserName == credReq.UserName)
534-
&& (credSite.Password == credReq.Password);
546+
continue;
535547
}
536-
else if (route.Authentication.AuthenticationType == AuthenticationType.ApiKey)
537-
{
538-
var apikeySite = route.Authentication.ApiKey ?? ApiKey;
539-
var apikeyReq = GetApiKeyFromHeaders(context.Request.Headers);
540548

541-
isAuthOk = apikeyReq != null
542-
&& apikeyReq == apikeySite;
543-
}
544-
}
549+
isRoute = true;
545550

546-
if (!isAuthOk)
547-
{
548-
if (route.Authentication != null &&
549-
route.Authentication.AuthenticationType == AuthenticationType.Basic)
551+
// Check auth first
552+
mustAuthenticate = route.Authentication != null && route.Authentication.AuthenticationType != AuthenticationType.None;
553+
isAuthOk = !mustAuthenticate;
554+
555+
if (mustAuthenticate)
550556
{
551-
context.Response.Headers.Add("WWW-Authenticate",
552-
$"Basic realm=\"Access to {route.Route}\"");
557+
if (route.Authentication.AuthenticationType == AuthenticationType.Basic)
558+
{
559+
var credSite = route.Authentication.Credentials ?? Credential;
560+
var credReq = context.Request.Credentials;
561+
562+
isAuthOk = credReq != null
563+
&& (credSite.UserName == credReq.UserName)
564+
&& (credSite.Password == credReq.Password);
565+
}
566+
else if (route.Authentication.AuthenticationType == AuthenticationType.ApiKey)
567+
{
568+
var apikeySite = route.Authentication.ApiKey ?? ApiKey;
569+
var apikeyReq = GetApiKeyFromHeaders(context.Request.Headers);
570+
571+
isAuthOk = apikeyReq != null
572+
&& apikeyReq == apikeySite;
573+
}
553574
}
554575

555-
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
556-
context.Response.ContentLength64 = 0;
576+
if (!isAuthOk)
577+
{
578+
if (route.Authentication != null &&
579+
route.Authentication.AuthenticationType == AuthenticationType.Basic)
580+
{
581+
context.Response.Headers.Add("WWW-Authenticate",
582+
$"Basic realm=\"Access to {route.Route}\"");
583+
}
584+
585+
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
586+
context.Response.ContentLength64 = 0;
587+
588+
HandleContextResponse(context);
589+
return;
590+
}
557591

592+
InvokeRoute(route, context);
558593
HandleContextResponse(context);
559-
return;
560594
}
561595

562-
InvokeRoute(route, context);
563-
HandleContextResponse(context);
564-
}
565-
566-
if (!isRoute)
567-
{
568-
if (CommandReceived != null)
596+
if (!isRoute)
569597
{
570-
// Starting a new thread to be able to handle a new request in parallel
571-
CommandReceived.Invoke(this, new WebServerEventArgs(context));
572-
}
573-
else
574-
{
575-
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
576-
context.Response.ContentLength64 = 0;
598+
if (CommandReceived != null)
599+
{
600+
// Starting a new thread to be able to handle a new request in parallel
601+
CommandReceived.Invoke(this, new WebServerEventArgs(context));
602+
}
603+
else
604+
{
605+
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
606+
context.Response.ContentLength64 = 0;
607+
}
608+
609+
HandleContextResponse(context);
577610
}
611+
}).Start();
578612

579-
HandleContextResponse(context);
580-
}
581-
}).Start();
613+
}
582614

615+
if (_listener.IsListening)
616+
{
617+
_listener.Stop();
618+
}
583619
}
584-
if (_listener.IsListening)
620+
catch
585621
{
586-
_listener.Stop();
587-
}
622+
// If we are here then set the server state to not running
623+
_cancel = true;
624+
}
625+
626+
WebServerStatusChanged?.Invoke(this, new WebServerStatusEventArgs(WebServerStatus.Stopped));
588627
}
589-
628+
590629
/// <summary>
591630
/// Checks if route matches called resource.
592631
/// For internal use only.
@@ -601,14 +640,14 @@ public static bool IsRouteMatch(CallbackRoutes route, string method, string rawU
601640
{
602641
return false;
603642
}
604-
643+
605644
var urlParam = rawUrl.IndexOf(ParamStart);
606645
var incForSlash = route.Route.IndexOf('/') == 0 ? 0 : 1;
607646
var rawUrlToCompare = route.CaseSensitive ? rawUrl : rawUrl.ToLower();
608647
var routeToCompare = route.CaseSensitive ? route.Route : route.Route.ToLower();
609648
bool isFound;
610-
611-
if (urlParam > 0 )
649+
650+
if (urlParam > 0)
612651
{
613652
isFound = urlParam == routeToCompare.Length + incForSlash;
614653
}
@@ -771,6 +810,6 @@ protected virtual void Dispose(bool disposing)
771810
}
772811
}
773812

774-
#endregion
813+
#endregion
775814
}
776815
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) 2020 Laurent Ellerbach and the project contributors
2+
// See LICENSE file in the project root for full license information.
3+
4+
namespace nanoFramework.WebServer
5+
{
6+
/// <summary>
7+
/// Represents the status of the server.
8+
/// </summary>
9+
public enum WebServerStatus
10+
{
11+
/// <summary>
12+
/// The server is stopped.
13+
/// </summary>
14+
Stopped,
15+
16+
/// <summary>
17+
/// The server is running.
18+
/// </summary>
19+
Running,
20+
}
21+
}

0 commit comments

Comments
 (0)