embedio icon indicating copy to clipboard operation
embedio copied to clipboard

Get rid of URL prefixes

Open rdeago opened this issue 4 years ago • 12 comments

EmbedIO version 4 will not have URL prefixes: you'll just need a port number and, of course, a certificate if you want to serve HTTPS.

For those with a couple of minutes to spare, here's why we had prefixes to start with, and why they're going to disappear.

I took some liberties with both technical and historical details; feel free to correct me if you think I oversimplified anything. I'm not trying to write the history book on URL prefixes, though, so let's keep it manageable and understandable by non-hard-core-sysadmins such as myself.

TL;DR: Prefixes were a good idea turned sour. They didn't even work as advertised, unless under specific, rather uncommon, conditions.

In the beginning...

Although it feels like early Paleolithic now, there was a time before URL prefixes.

In order to receive any request, an HTTP server had to open a listening TCP socket on a given port. From that moment on, all incoming TCP connections on that port would be directed to that socket and, by consequence, served by that server.

Then all major HTTP servers had their internal mechanisms to "partition" URLs between different applications. This way you could, more or less, install WordPress on /blog without affecting your main website in /, as long as both were served by the same Web server, which took the burden of listening to ports, parsing requests, and sending responses.

Putting HTTP on steroids

In the first 2000s, as Internet traffic kept growing at crazy rates, HTTP servers had to handle more and more connections per second.

The problem was, they could optimize their code all they wanted, but they were still bound to make calls to the operating system's networking stack. This implied a lot of switching between "user mode" and "kernel mode" - a costly operation in terms of CPU.

"But wait" - someone at Microsoft probably said - "what if the HTTP protocol was completely implemented in a driver? Less switching, more performance!" Thus, http.sys was born: a driver that lifts HTTP protocol management away from programs. It was introduced with Windows Server 2003, as an essential component of IIS 6.0.

While they were at it, Microsoft developers also moved the "URL partitioning" part of IIS to the driver. This made a lot of sense for them, because it made IIS even more performant. It made sense for everyone else, too, because, as long as programs used http.sys's services (and who wouldn't want to?) they were automatically integrated with each other. You can have an IIS-served website in / and your C++ application serving URLs starting with /api/. Yay for Microsoft!

Of course the usual criticisms surfaced: programs that depend on http.sys will only run in Windows; yet another attempt at vendor lock-down; etc. etc. True, but can you really blame them for scratching their own itch and making their solution available to everyone else, for free?

The rise and fall of HttpListener

Such a fundamental piece of Windows API as the HTTP server API (the user-mode interface to http.sys) was too tempting not to write a managed wrapper for it. So HttpListener and related types, such as HttpListenerContext etc., were added to .NET 2.0.

Think about it: now you could write your own Web server in C#, or even Visual Basic! How cool is that?

HttpListener did not come without some drawbacks. It was obviously Windows-only, for starters, but the same went for all of .NET at the time.

It was not until 2018, in the age of cross-platform .NET Core, that someone proposed deprecating HttpListener in favor of the fully-managed, cross-platform Kestrel Web server.

Going cross-platform

Well before .NET Core, the Mono project was started by Miguel de Icaza at Ximian to create a fully open-source, cross-platform .NET runtime. Including HttpListener of course: they replicated HttpListener functionality in fully-managed, cross-platform code.

When you configure your EmbedIO WebServer with HttpListenerMode.EmbedIO (the default) you're using Mono's implementation of HttpListener.

A good idea gone sour

There are two problems with Mono's HttpListener.

The first problem is that, to achieve the same functionality as Microsoft's HttpListener, the Mono team also reimplemented URL prefix management. That's the code that lets you create a HttpListener for http://example.com/ and a different one for http://example.com/blog/, for example. It does so by creating an EndPointListener for every address / port pair specified in prefixes, which then dispatches each request according to the requested path. These listeners' lifetimes are managed by the static EndPointManager class.

It would be great, if only it wasn't useless. While Microsoft's code wraps calls to http.sys, which keeps track of listeners created by any running program, Mono's EndPointManager and EndPointListener only know about HttpListener instances created in the same program, or worse in the same AppDomain! If your Mono program wants to handle requests to http://example.com:80/blog/, but another program (say IIS, or Apache) is already listening for requests to http://example.com:80/, you're simply out of luck. The same program would work in .NET Framework (or .NET Core under Windows), because it would let http.sys manage URL prefixes globally.

The second problem with EndPointManager and EndPointListener is that they're pretty buggy. Issues #165, #332, #402, and #459 are but a few examples of how bugs in those two classes can haunt EmbedIO users.

Do we really need URL prefixes?

There are two possible use cases for URL prefixes in EmbedIO. Let's examine each of them.

  1. Programs that run under Windows and have to share an address / port pair with IIS or some other program using http.sys. I'd bet there are very, very few such programs out there.

  2. Programs that create multiple WebServer instances and want to share one or more address / port pairs between them. Such programs would be better off using ModuleGroup s anyway.

The great majority of EmbedIO users seem to just need listening to a port on any address. This does not require a dedicated dispatching layer: conflicts, if any, are detected by the OS's networking stack.

If your EmbedIO-based program absolutely needs URL prefixes, please comment on this issue and let's see if we can find a solution.

So what do we do now?

Starting with EmbedIO v4.0, URL prefixes will be abandoned. WebServerOptionsBase will have a Port property and probably an Address property; details are not yet finalized at the time of writing.

Those of you that are using prefixes of the form http://+:8080/ will only need to configure the port. Prefixes like http://example.com/somewhere/, i.e. those including a path, will not be supported, just as they are not really supported now unless your program runs under Windows and you explicitly use HttpListenerMode.Microsoft.

On the bright side, nobody's ever going to lose any more sleep on the choice between http://*:8080/, http://+:8080/, http://localhost:8080/, and http://0.0.0.0:8080/.

Obviously, HttpListenerMode.Microsoft will still use ~good~ old HttpListener, but prefixes will be automatically generated from your Web server's options, thus becoming just an internal implementation detail. In other words, you won't see them.

Future-proof

Nobody here wants EmbedIO to follow HttpListener on the way to obsolescence. We still don't know what will replace HttpListener in future versions of EmbedIO. Suggestions are welcome, by the way.

URL prefixes are a feature that binds EmbedIO to HttpListener without bringing much benefit. By removing them from our public API, we're preparing for whatever the future may bring.

rdeago avatar Mar 07 '20 13:03 rdeago

Pinging, in no particular order: @geoperez @mariodivece @JRosanowski @MrDigit @SaricVr @AbeniMatteo @bufferUnderrun @jusovsky @madnik7 @rocketraman

rdeago avatar Mar 07 '20 13:03 rdeago

I totally agree with this new direction!

SaricVr avatar Mar 07 '20 14:03 SaricVr

A well-explained issue, I love it.

Totally agree with you. Thanks for making EmbedIO a lot better project.

geoperez avatar Mar 07 '20 18:03 geoperez

Coming from a background of programming on other OSes with other languages, I have to admit the whole URL prefix thing was completely unintelligible and seemingly pointless to me when I first encountered it with EmbedIO, C#, and Windows. It makes a lot more sense now with @rdeago 's wonderful history lesson, but one shouldn't generally need history lessons to understand APIs... good change.

rocketraman avatar Mar 09 '20 19:03 rocketraman

Hi @rdeago , been very busy last months and i did not follow all the threads as i used to. Thanks to your ping and thanks for the time you spent describing the issue or working on the project. Same @geoperez and the others guys... 👍

I agree with your report :

The great majority of EmbedIO users seem to just need listening to a port on any address.

All my apps have theirs own listening port and the url routing is done inside modules :

  • StaticFileModule serve "/"
  • WebApiModule serve "/api/"
  • WebSocketModule : serve "/socket/"

In my opinion, this code removal (abandonned prefix) is more about what do we exactly want from EmbedIO.

I'm a long time IIS user. It grows up from few years to being fat, complicated but lack of cool features like websocket. IIS controls all websites it serve in a way that if things go wrong, all sites are down. I prefer running a separed lightweight process for each sites !

In my searchs, i found EmbedIO three years ago. Well integrated, lightweight, pretty fast and while it support many features i need it remains very simple ! All my webapps have been rewritten to be served by this project. Some for ours internals uses (i work for the city where i live) with approx 3000+ users, 80 sessions live and others for local residents (100000+), peak to 250 sessions.

Funny story for me but not for my coworker, a sysadmin : the first big release in prod based on EmbedIO have down the service. The guilty ? Not EmbedIO but our Apache reverse proxy which burn RAM and PROCESS.

As soon as theses principles (lightweight, simple) will be applied , i will use EmbedIO, one of the critical part of all my apps ! I believe in the choice you make.

bufferUnderrun avatar May 12 '20 08:05 bufferUnderrun

I remember the days before prefixes. Me and Stegosaurus used to do socket programming. 😂

Thank you for this awesome project! I used a couple of other projects as well, WiringPi and RaspberryIO, to build a sprinkler controller. I'm using EmbedIO to put together a very simple web API for it that I can control from a Flutter app. I love how quickly you can get something up and running with this - thank you for the detailed plan forward and for working on it!

Vinny636 avatar May 16 '20 16:05 Vinny636

@bufferUnderrun, @Prehistoricoder, thank you both for your kind words. I'm sure @geoperez and the rest of the team will agree that they mean an awful lot to us.

rdeago avatar May 21 '20 13:05 rdeago

Not my case but one thing to take into consideration: HTTP.sys allows you to handle and configure HTTPS and SSL certs externally through netsh http sslcert and optionally combined with ACME clients.

AbeniMatteo avatar Jun 06 '20 10:06 AbeniMatteo

@AbeniMatteo certificate management is both a thing that we will have to take into consideration, of course, as well as one of the worst aspects of the current version of EmbedIO, precisely because of the limitations it inherits from HTTP.sys via HttpListener: lack of multi-platform support, no simple (EmbedIO-style) way of using a certificate.

I still have no idea of how to implement HTTPS in a future, HttpListener-less version of EmbedIO, although probably Kestrel sources are a good place to look at for inspiration. Suggestions are very welcome, by the way.

rdeago avatar Jun 10 '20 09:06 rdeago

Adding my vote for this to the pile.. When's 4.0.0 due ? :D

CaiusJard avatar Aug 26 '21 18:08 CaiusJard

If URL prefixes go away, will there still be a way to only bind to localhost? Binding to localhost, i.e. new WebServer("http://localhost:8080") conveniently bypasses the Windows Firewall prompt, while new WebServer(8080) does produce a prompt (a misleading one, because denying it doesn't prevent local connections).

fatcerberus avatar Dec 18 '21 23:12 fatcerberus

Still voting for this! :)

CaiusJard avatar Apr 30 '22 16:04 CaiusJard