Skip to content

4.0.0.252

Compare
Choose a tag to compare
@scottoffen scottoffen released this 16 Feb 21:54
· 23 commits to master since this release

The Performance Update

I've been referring to this release in my head as The Performance Update for several months now. Almost every enhancement included a performance update component, so in addition to new and better functionality, things should also run faster.

Enhancements

#79 Support For Multiple Public Folders

I've used Apache for years, and I've always liked the ability it has to configure virtual directories. I wanted Grapevine to have that same functionality - mapping requests not just to specific methods in code, but to disparate file system locations - and now it does. This ended up being a complete refactor of the PublicFolder class to include some performance and security enhancements as well. If you want details, feel free to look over the associated pull request - we had quite the discussion on this one!

Public Folders Are Now Optional
In short, IRestServer now has a property named PublicFolders, which is a list of all configured public folders. If you don't add any to the list, none are created - not even the default public folder! When a request comes in, each public folder in turn (in the order they appear in the list) is checked for whether there is such a file or whether there should be a file (if a prefix was specified and if it appears as the first element in the path info of the request). The file is returned if it exists or an error is thrown if it should exist and no further routing is done. Otherwise, routing continues as usual.

Ensuring Backwards Compatibility
The PublicFolder property is still available on IRestServer, but the implementation on RestServer is as an alias for PublicFolders[0]. It is important to note that if you are counting on having a public folder automatically created by RestServer (which is the original implementation), it will still do that if you try to access the PublicFolder property and there is no value in PublicFolders[0].

Performance: Caching File Locations
It's much faster to scan an in-memory data structure than to use I/O to scan the file system. So now, when a PublicFolder object is created, a deep scan is done and paths to all files (including those in sub-folders) are cached. In order to keep that cache from getting stale, a file watcher is added. Whenever files are added, removed or renamed, the cache updates automatically.

This also resolves #127 PublicFolder.GetFilePath() may be vulnerable to directory traversal attack. While a vulnerability was never found, that method has not only been completely removed, but the approach it took has been replaced with the introduction of the cache.

#111 Add Before and After Start and Stop Server Event Handlers

Having only a single generic delegate to do everything you want to do before and after your server starts and stops can lead to unnecessarily tight coupling. In an attempt to reduce that, event handlers have been introduced. The old OnBefore* and OnAfter* properties still exist, but are deprecated and scheduled to be removed in a future release.

The delegate for the event handler takes the instance of the RestServer as a parameter, allowing the developer to modify or monitor the server at each stage of starting and stopping.

server.AfterStarting += restServer =>
{
    Console.WriteLine("started");
};

#112 Add Before and After Routing Event Handlers

In a similar fashion to adding the event handlers to the starting and stopping of the IRestServer, event handlers have been added for before and after routing. This is to facilitate those things that should always happen before routing and always happen after routing - typically meaning after a response has been sent, but not necessarily. An example use case would be to create and dispose of sessions and connections.

It is important to know that the AfterRouting handlers will be executed in the reverse order that they were added in.

The general expectation is that they can be added in pairs by extensions. Hence the following:

// First extension manages the database connection
server.Router.BeforeRouting += context => { Console.WriteLine("Connect to the database"); };
server.Router.AfterRouting += context => { Console.WriteLine("Disconnect from the database"); };

// Second extension does session management, may assume access to the database
server.Router.BeforeRouting += context => { Console.WriteLine("Fetch the session"); };
server.Router.AfterRouting += context => { Console.WriteLine("Store the session"); };

will produce the console output:

Connect to the database
Fetch the session
Store the session
Disconnect from the database

#143 Implement Route Caching

The first time a request is received, the http method and exact path info are cached along with the supplied list of routes that match. Subsequent requests that match the http method and exact path info will use the cached list of routes instead of scanning the routing table again. Any changes to the routing table will cause the cache to be cleared.

In the future, a toggle will be added to disable this caching, as it might be quicker to scan the routing table every time in certain scenarios.

#145 Implement Thread Pooling for Server Threading

When I initially wrote Grapevine, I was misinformed about how to properly manage thread resources. While the use cases I had for Grapevine never exposed these weaknesses, it was brought to my attention through several channels that a better, more reliable and more performant method existed. I finally bit the bullet and implemented it in this release.

#151 Change RestServer.ThreadSafeStop to Extension Method on IRestServer

This simplifies the interface and allows all instances of IRestServer to have the same implementation of the method without using an abstract class.

#153 Remove Routing Logic From RestServer class

The routing logic really didn't belong in the server, so I moved it. The logic itself didn't really change, except where noted by other issues.

#164 Refactor SendResponse()

An design flaw limited the number of ways in which responses could be sent (e.g. FileStream was the only type of Stream objects that could be sent). This change opens the door for responses to be sent in a manner that best suits your needs. The only method required on the interface is void SendResponse(byte[] contents). All previously existing methods have been turned into extensions methods on IHttpResponse, and are used to set properties and transform different objects into byte arrays before calling the interface implementation.

Defects

#161 Route Fails to Parse More Than One Parameter

This was a great catch by Meik Tranel. See the issue for more details.