v.1.3
Sisk 1.3 is being released today! This update brings performance improvements, another way to write your favorite APIs, and something similar to dependency injection.
Sisk's commitment remains the same: to be simple to develop a quality HTTP application. Sisk has no dependencies other than the .NET ecosystem itself, and all its code is still less than 1KB in total. Sisk has a minimal footprint, focusing performance on your application's layer, not the server.
We would also like to showcase the new logo of the project. It is more modern while still referencing what the project used to be.
Performance improvements
Sisk has a limitation: it depends on the current implementation of HttpListener to work. This implementation is great on Windows and has performance comparable to ASP.NET, but in recent tests, we found that the managed implementation of HttpListener lags significantly behind ASP.NET's Kestrel, which happens outside Windows environments. Even so, Sisk still manages to be much more performant and efficient than many other development frameworks in other languages. Our goal is not to be better than ASP.NET, but the closer we get to its performance, the more viable the choice of Sisk becomes for more projects.
In version 1.3, we achieved an average performance increase of 15% overall for request processing. This is because we improved the way the server sends the content of a response to the client. The HttpResponse object depends on HttpContent to define content to send to the client, and previously, this object had to be serialized through HttpContent.ReadAsByteArrayAsync. The problem is that many contents are based on ByteArrayContent, and when calling this method on this type of content, a memory buffer was created, and the response bytes were copied to this buffer. Then, this buffer was copied to the response's output stream.
Thanks to UnsafeAccessor in .NET 8, we can access the response bytes directly, without the need to create this secondary buffer. This greatly improved overall performance.
In addition, there were performance improvements outside of the request processor as well. Improvements were made in the way the server writes messages to the access log and deserializes multipart content.
Static HttpContexts
An experimental feature is to expose the current HttpContext of a request in a local context of the execution thread. This allows you to expose members of a request outside the request method, greatly improving the readability of your code. The code snippet below shows an abstract class that functions as an API controller with an embedded database.
public abstract class Controller : RouterModule
{
public DbContext Database
{
// Create or get an new DbContext instance
get => HttpContext.Current.RequestBag.GetOrAdd(() => new DbContext());
}
// Exposing the HttpRequest instance is supported too
public HttpRequest Request { get => HttpContext.Current.Request; }
protected override void OnSetup(Router parentRouter)
{
base.OnSetup(parentRouter);
HasRequestHandler(RequestHandler.Create(
execute: (req, ctx) =>
{
// disposes the current DbContext if used
ctx.RequestBag.GetOrDefault<DbContext>()?.Dispose();
return null;
},
executionMode: RequestHandlerExecutionMode.AfterResponse));
}
}
And use this implementation with methods that do not have an HttpRequest parameter, as you already have a Request in your controller instance.
[RoutePrefix("/api/posts")]
public class PostsController : Controller
{
[RouteGet]
public IEnumerable<Blog> ListPosts()
{
return Database.Posts
.Where(post => post.AuthorId == AuthenticatedUser.Id)
.ToList();
}
[RouteGet("<id>")]
public Post GetPost()
{
int blogId = Request.RouteParameters["id"].GetInteger();
Post? post = Database.Posts
.FirstOrDefault(post => post.Id == blogId && post.AuthorId == AuthenticatedUser.Id);
return post ?? new HttpResponse(404);
}
}
You can read more about this feature in the documentation.
Full changelog
New features:
- New logo.
- Added support for .NET 9.
- Added the
StringKeyStore.SetRange
method. - Added the
TypedValueDictionary.GetOrDefault<T>
method. - Added the
TypedValueDictionary.GetOrAdd<T>
method. - Added the
TypedValueDictionary.GetOrAddAsync<T>
method. - Added the
HttpRequest.Uri
property. - Added the
HttpServerExecutionResult.Elapsed
property. - Added the
HttpContext.Current
static property. - Added the
HttpResponseExtensions.WithHeader(StringKeyStore)
method. - Added the
MimeHelper.DefaultMimeType
static property. - Added the
MimeHelper.IsBrowserKnownInlineMimeType(string)
method. - Added the
HttpServerFlags.PreventResponseContentsInProhibitedMethods
flag. - Added the
Route.Get
,Route.Post
,Route.Put
,Route.Delete
,Route.Head
,Route.Options
,Route.Any
andRoute.Patch
static methods. - Added the
RouterModule.OnSetup
virtual method. - Added the
CookieHelper
static class.
Changes:
- Improved the path matching algorithm.
- Improved the log writer algorithm.
- The HTTP server will now show an warning when it's starting without any route defined.
- Changed type from
Router.Action
fromRouteAction
toDelegate
. - Changed return type from
TypedValueDictionary.Unset
fromvoid
tobool
. - Fixed when the HTTP server was starting with one random port and defined ports when using portable configurations.
Fixes:
- Fixed the
TypedValueDictionary.IsSet<T>
attribute usage. - Fixed an issue where byte counts were being calculated with decimals in
SizeHelper.HumanReadableSize
.
Breaking changes:
- Removed the
HttpKnownHeaderNames.From
member. - Removed the
HttpRequest.GetQueryValue
methods. - Removed the
HttpServer.GetVersion()
method. You can still use theHttpServer.PoweredBy
property instead. - Route/paths parameters will not be added into
HttpRequest.Query
anymore. You should use theHttpRequest.RouteParameters
property instead. - Made
CookieHelper
and static class and removed it from all classes who used it. You still can use theSetCookie
methods from where it was defined. - Deprecated the
HttpServerConfiguration.DefaultEncoding
property.
Thank you for using Sisk!