【问题标题】:ServiceStack: Setting Response-Type in Handler?ServiceStack:在处理程序中设置响应类型?
【发布时间】:2025-12-30 16:00:09
【问题描述】:

在独立模式下使用 ServiceStack,我在我的 Apphost 中为任意文件名定义了一个包罗万象的处理程序(它只会从数据目录中提供文件)。

它的核心方法是(fiFileInfo成员变量,ExtensionContentTypeDictionary从扩展名到MIME类型):

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);

        Console.Write("StaticFileHandler.Factory fn=" + fn);

        Console.WriteLine("AbsoluteUri={0}", pathInfo);

        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))
        {
            source.CopyTo(httpRes.OutputStream);
            //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");
        //httpRes.ContentType = 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;
    }
}

当我使用标记为方法 1 或方法 2 的行运行时,未设置实际的 HTTP 响应类型标头。用 IE9 开发者工具调试显示根本没有设置响应类型。

从 catch-all 处理程序设置内容类型(和流内容)的正确方法是什么?

这不是标准服务,所以我不能只返回自定义的IHttpResponse,这似乎是服务的正常方法。

附加信息:日期标头也未设置...

【问题讨论】:

  • 您的 ProcessRequest 方法覆盖什么类/方法?我认为这将是 AppHostHttpListenerBase.ProcessRequest 但您的示例接受更多参数。另外,您可以尝试调用 httpRes.EndServiceStackRequest() 来结束请求。
  • @paaschpa 我已经更新了源代码以包含完整的课程。它继承自EndpointHandlerBase。响应发送到浏览器就好了,只是我的自定义标头不在其中。

标签: c# servicestack


【解决方案1】:

我认为问题出在这一行 source.CopyTo(httpRes.OutputStream);

如果你做类似的事情,标题应该填充

public override void ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
{
    var bytes = File.ReadAllBytes(fi.FullName);                   
    httpRes.AddHeader("Date", DateTime.Now.ToString("R"));
    httpRes.AddHeader("Content-Type", "text/plain");
    httpRes.AddHeader("TestHeader", "SomeValue");
    httpRes.OutputStream.Write(bytes, 0, bytes.Length);
}

用于测试答案的简单控制台应用

初始化 ServiceStack AppHost 并在控制台中运行它:

class Program
{
    static void Main(string[] args)
    {
        var appHost = new AppHost();
        appHost.Init();
        appHost.Start("http://*:1337/");
        System.Console.WriteLine("Listening on http://localhost:1337/ ...");
        System.Console.ReadLine();
        System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
    }
}

一些虚拟服务......对于这个答案并不是真正必要的:

[Route("/Hello/{Name}")]
public class Hello
{
    public string Name { get; set; }
}

public class HelloService : Service
{
    public string Any(Hello request)
    {
        return request.Name;
    }
}

配置 AppHost 并将“被测代码”添加到 CatchAllHanders:

public class AppHost : AppHostHttpListenerBase
{
    public AppHost() : base("Test Console", typeof(AppHost).Assembly) { }

    public override void Configure(Funq.Container container)
    {
        CatchAllHandlers.Add(StaticFileHandler.Factory);
    }

}

修改了'code under test' - 主要需要到达ProcessRequest()并返回一个文件:

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)
    {
        return new StaticFileHandler(new FileInfo(@"C:\Test.xml"));
    }

    public override void ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
    {
        var bytes = File.ReadAllBytes(fi.FullName);

        httpRes.AddHeader("Date", DateTime.Now.ToString("R"));
        httpRes.AddHeader("Content-Type", "text/plain");
        httpRes.AddHeader("Test", "SetThis");

        httpRes.OutputStream.Write(bytes, 0, bytes.Length);
    }

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

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

【讨论】:

  • 我已经有 AddHeader() 调用,但它们不起作用。或者您是否建议这取决于订单。
  • 尝试在写入内容流之前调用AddHeader;行为没有改变。
  • 你删除了source.CopyTo(httpRes.OutputStream);称呼?我在一个简单的控制台 AppHost 中添加了我的完整测试,它是您的示例的修改版本。
  • 不,我没有,但是 CopyTo 在 .NET 4.5 的底层进行字节复制。是否有理由怀疑它与问题有关?
  • 我在恢复 pre-CopyTo 代码后收回;清除浏览器缓存。它似乎确实与 CopyTo 有关,这是或应该是一个错误。