-->

ServiceStack: Serving Static files from a director

2020-03-04 10:07发布

问题:

I am in the process of converting my stand-alone home grown web server to use ServiceStack for serving all pages and resources.

I see from this question

Serving a static file with servicestack

that it is easy to serve a single static file with Service Stack.

In my home grown implementation, after checking to see if a URL matches any particular handler (equivalent to ServiceStack routes), the default handler then checks for a static file in its HttpData directory to serve matching the URL.

If that file does not exist, it then generates a 404 error.

What is the best pattern to use with ServiceStack to serve files from the file system if no other service is matched? Note that I am using it in standalone mode without IIS.

These files can be HTML, PNG, JS, and a small handful of other content types.

Note: I see the ServiceStack.Razor package may assist my requiredments, but I cannot find the documentation on it. I will put in a separate question about that.

Edit: I found a reference in this question

Create route for root path, '/', with ServiceStack

indicating

Register a IAppHost.CatchAllHandlers - This gets called for un-matched requests.

So far, I have not found any documentation or example on how to to this registration. Note: I am running stand-alone, so it needs to be done in C#, not with XML.

回答1:

After much research, I have found the following that seems effective.

Configuration

In the AppHost constructor:

                CatchAllHandlers.Add(
                    (httpMethod, pathInfo, filePath) => 
                        Tims.Support.StaticFileHandler.Factory(
                            Params.Instance.HttpDataDir, 
                            "/", 
                            pathInfo
                    )
                );

The factory

Checks for the existance of the file, and returns the appropriate handler, or returns null if it is not handling the file (because it does not exist). This is important so that other urls (such as /metadata continue to work.

Handler

The core method of the actual handler is very simple. By overriding ProcessRequest and returnign the bytes of the file with an appropriate resource type, the job is done. This version, for simplicity, does not include any date handling for caching purposes.

Sample Code

public class StaticFileHandler : EndpointHandlerBase
{
    protected static readonly Dictionary<string, string> ExtensionContentType;

    protected FileInfo fi;

    static StaticFileHandler()
    {
        ExtensionContentType = new Dictionary<string, string>       (StringComparer.InvariantCultureIgnoreCase) 
        {
            { ".text", "text/plain" },
            { ".js", "text/javascript" },
            { ".css", "text/css" },
            { ".html", "text/html" },
            { ".htm", "text/html" },
            { ".png", "image/png" },
            { ".ico", "image/x-icon" },
            { ".gif", "image/gif" },
            { ".bmp", "image/bmp" },
            { ".jpg", "image/jpeg" }
        };
    }

    public string BaseDirectory { protected set; get; }
    public string Prefix { protected set; get; }

    public StaticFileHandler(string baseDirectory, string prefix) 
    {            
        BaseDirectory = baseDirectory;
        Prefix = prefix;
    }

    private StaticFileHandler(FileInfo fi)
    {
        this.fi = fi;
    }

    public static StaticFileHandler Factory(string baseDirectory, string prefix, string pathInfo)
    {
        if (!pathInfo.StartsWith(prefix, StringComparison.InvariantCultureIgnoreCase))
        {
            return null;
        }

        var fn = baseDirectory + "/" + pathInfo.After(prefix.Length);

        var fi = new System.IO.FileInfo(fn);

        if (!fi.Exists)
        {
            return null;
        }

       return new StaticFileHandler(fi);
    }   

    public override void ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
    {
        using (var source = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Open))
        {
            var bytes = source.ReadAllBytes();
            httpRes.OutputStream.Write(bytes, 0, bytes.Length);
        }

        // timeStamp = fi.LastWriteTime;                        

        httpRes.AddHeader("Date", DateTime.Now.ToString("R"));
        httpRes.AddHeader("Content-Type", ExtensionContentType.Safeget(fi.Extension) ?? "text/plain");                       
    }

    public override object CreateRequest(IHttpRequest request, string operationName)
    {
        return null;
    }

    public override object GetResponse(IHttpRequest httpReq, IHttpResponse httpRes, object request)
    {
        return null;
    }
}