HTTP Response Compression with Self Hosted

I don’t see a built-in flag to enable response compression when running a self hosted app, but I expect there are multiple hooks or extension points where I can implement this.

What would be the recommended approach to GZip the response body when running self hosted?

1 Like

Came across this IHttpModule example on request compression and this IHttpModule compression library, which shows how response compression can be implemented.

Is IHttpModule the way to go here?

That’s not going to work for self-hosting, so the easiest way is to use ToOptimizedResult APIs.

I’m only aware of ToOptimizedResult being used in the context of wrapping a DTO response object, which would mean we have to refactor all methods to return object for this to work?

This also won’t apply to anything outside of service methods e.g. JS, CSS files (main concern)?

Is there a way to get this done so we can compress static files and service responses without a lot of refactoring?

Not from within ServiceStack which would also require buffering the response that ServiceStack doesn’t do by default. The way to enable compression transparently would be to put the self-host behind a WebServer like nginx it and have it to serve the static assets instead, which is much better for performance as both files and compression are done in native code.

Okay, thanks for info.

Is this something that can be added to ServiceStack (feature request)? It would be great not having to put another layer/ dependency in the pipeline.

Yeah you can add it as a feature request, it’s a tricky solution to enable as it can’t be applied indiscriminately since it breaks features that need to write to the response immediately like Server Events.

We are using Server Events, is that something that needs special consideration (or an exclusion rule in nginx) if I do look into configuring nginx as a proxy?

Yeah any proxy used would need to have buffering disabled. Normally nginx would be setup as a reverse proxy which would forward most requests to your self-host except for the static files which you’d tell nginx to serve directly, ideally you would have static resources in a separate folder like /files, /img, /Content etc so it can be easy to write a rule for.

Thanks.

Just one last question, can you confirm there is nowhere currently in SS Self Host that I can hook in to the response, add a Content-Encoding header and and compress the stream for static files being returned?

You can add the Content-Encoding header in a response filter like any other header, but you wont be able to compress the response stream.

Adding an example config (reverse proxy with failover/ load balancing) for nginx, might help someone get started with nginx.

upstream site {
    server svr1.site.com;
    server svr2.site.com;
}

server {
    listen 80;        
    proxy_set_header Host site.com;
    
    location / {
        proxy_pass http://site;
    }

    # any urls starting with /event- are bound for the ServiceStack
    # Server Events service, these responses should NOT be buffered, compressed, chunked
    # https://github.com/ServiceStack/ServiceStack/wiki/Server-Events
    location ~* ^/event- {
        gzip  off;
        proxy_buffering off;
        chunked_transfer_encoding off;
        
        proxy_pass http://site; 
    }
}
3 Likes

Not sure if I am expected to create a new thread - but this seemed like the best option.

We have static content that is 10MB without compression and 2MB with.

I was hoping that I could subclass StaticFileHandler for now and override ProcessRequest but I’m stuck at the private field at HttpHandlerFactory.cs:23.

Is my assumption correct that there currently is no way to get my static content compressed without a proxy?

All caching returns an optimized response and with the latest v4.5.7 that’s on MyGet you can use the new [CompressResponse] Request Filter attribute to compress normal responses, so you could create a Service that returns a compressed response to clients that support it (e.g. browsers) with something like:

[Route("/compress/{Path*}")]
public class CompressFile
{
    public string Path { get; set; }
}

[CompressResponse]
public class CompressServices : Service
{
    public object Any(CompressFile request)
    {
        var file = VirtualFileSources.GetFile(request.Path);
        if (file == null)
            throw HttpError.NotFound(request.Path + " does not exist");

        return new HttpResult(file);
    }
}

Then call it with /compress/path/to.file.txt which will return a compressed file in clients that Accept gzip or deflate encoding.

I’ll also look into also making it configurable to compress static files as well.

@mapspiral I’ve added support for enabling compression of static files in this commit where you can specify which will let you specify which static files you want to compress with:

SetConfig(new HostConfig {
    CompressFilesWithExtensions = { "js", "css" },
    // (optional), only compress .js or .css files > 10k
    CompressFilesLargerThanBytes = 10 * 1024 
});

Which will compress the response with the optimal compression that the client supports.

If you need more fine-grained control you can override ShouldCompressFile() in your AppHost, e.g. you can use this to specify additional adhoc files to compress with:

public override bool ShouldCompressFile(IVirtualFile file)
{
    return base.ShouldCompressFile(file) || file.Name == "large.csv";
}

This change is available from v4.5.7 that’s now available on MyGet.