Web API - 处理HEAD请求并指定自定义Content-Length

时间:2015-08-19 18:17:48

标签: asp.net asp.net-web-api

我通过GET-Requests提供API控制器服务文件。我正在使用PushStreamContentResponse,效果很好。

我还可以在响应对象上设置Content-Length-Header。

现在我也想支持HEAD-Requests。我已经尝试了http://www.strathweb.com/2013/03/adding-http-head-support-to-asp-net-web-api/虽然这可能有用,但我需要一个解决方案,我不需要实际处理请求:检索文件并将其流式传输既费用昂贵,又需要获取元数据(长度等)实际上是一个无操作。

但是,当我尝试设置Content-Length标头时,它将被0覆盖。

我添加了请求跟踪,我看到我的处理程序返回的消息显示正确的URL,Content-Disposition和Content-Length。

我也尝试过使用自定义HttpResponse并实现TryComputeLength。虽然确实调用了这个方法,但结果会在管道中的某个点被丢弃。

有没有办法使用Web API支持这个?

3 个答案:

答案 0 :(得分:4)

虽然这可能是2015年,今天(2017年以后)的问题,但您可以这样做

[RoutePrefix("api/webhooks")]
public class WebhooksController : ApiController
{
    [HttpHead]
    [Route("survey-monkey")]
    public IHttpActionResult Head()
    {
        return Ok();
    }

    [HttpPost]
    [Route("survey-monkey")]
    public IHttpActionResult Post(object data)
    {
        return Ok();
    }
}

HEAD api/webhooks/survey-monkeyPOST api/webhooks/survey-monkey工作得很好。 (这是我刚刚为实现SurveyMonkey的webhooks所做的存根)

答案 1 :(得分:3)

最后,它非常简单。

  1. 为HEAD请求创建处理程序
  2. 返回至少包含一个字节内容的Body,将响应的Content-Length-Header设置为所需的长度。使用长度为零的Body将无效。
  3. 这是关键部分:禁用响应的输出缓冲。
  4. 默认情况下,WebAPI将禁用StreamContent和PushStreamContent的输出缓冲。但是,可以通过Application_Startup替换WebHostBufferPolicySelector来覆盖此行为:

    GlobalConfiguration.Configuration.Services.Replace(typeof (IHostBufferPolicySelector), new BufferlessHostBufferPolicySelector());
    

答案 2 :(得分:1)

另一种解决方案是创建一个自定义HttpContent来为您完成这项工作。如果您要遵循准则,则还需要自定义的IHttpActionResult

假设您有一个控制器,它针对给定的资源返回HEAD动作,如下所示:

[RoutePrefix("resources")]
public class ResourcesController : ApiController
{
    [HttpHead]
    [Route("{resource}")]
    public IHttpActionResult Head(string resource)
    {
        //  Get resource info here

        var resourceType = "application/json";
        var resourceLength = 1024;

        return Head(resourceType , resourceLength);
    }
}

我想出的解决方案如下:

负责人

internal abstract class HeadBase : IHttpActionResult
{
    protected HttpStatusCode Code { get; set; } = HttpStatusCode.OK;

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = null;

        try
        {
            response = new HttpResponseMessage(Code)
            {
                Content = new EmptyContent()
            };
            FillContentHeaders(response.Content.Headers);
            return Task.FromResult(response);
        }
        catch (Exception)
        {
            response?.Dispose();
            //  Good place to log here
            throw;
        }
    }

    protected abstract void FillContentHeaders(HttpContentHeaders contentHeaders);
}

// For current need
internal class Head : HeadBase
{
    public Head(string mediaType, long contentLength)
    {
        FakeLength = contentLength;
        MediaType = string.IsNullOrWhiteSpace(mediaType) ? "application/octet-stream" : mediaType;
    }

    protected long FakeLength { get; }

    protected string MediaType { get; }

    protected override void FillContentHeaders(HttpContentHeaders contentHeaders)
    {
        contentHeaders.ContentLength = FakeLength;
        contentHeaders.ContentType = new MediaTypeHeaderValue(MediaType);
    }
}

空白内容

internal sealed class EmptyContent : HttpContent
{
    public EmptyContent() : this(null, null)
    {
    }

    public EmptyContent(string mediaType, long? fakeContentLength)
    {
        if (string.IsNullOrWhiteSpace(mediaType)) mediaType = Constant.HttpMediaType.octetStream;
        if (fakeContentLength != null) Headers.ContentLength = fakeContentLength.Value;

        Headers.ContentType = new MediaTypeHeaderValue(mediaType);
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        //  Necessary to force send
        stream?.WriteByte(0);
        return Task.FromResult<object>(null);
    }

    protected override bool TryComputeLength(out long length)
    {
        length = Headers.ContentLength.HasValue ? Headers.ContentLength.Value : -1;
        return Headers.ContentLength.HasValue;
    }
}

缓冲策略选择器

internal class HostBufferPolicySelector : IHostBufferPolicySelector
{
    public bool UseBufferedInputStream(object hostContext)
    {
        if (hostContext == null) throw new ArgumentNullException(nameof(hostContext));

        return true;
    }

    public bool UseBufferedOutputStream(HttpResponseMessage response)
    {
        if (response == null) throw new ArgumentNullException(nameof(response));

        if (StringComparer.OrdinalIgnoreCase.Equals(response.RequestMessage.Method.Method, HttpMethod.Head.Method)) return false;

        var content = response.Content;
        if (content == null) return false;

        // If the content knows, then buffering is very likely
        var contentLength = content.Headers.ContentLength;
        if (contentLength.HasValue && contentLength.Value >= 0) return false;

        var buffering = !(content is StreamContent ||
                            content is PushStreamContent ||
                            content is EmptyContent);

        return buffering;
    }
}

应将缓冲策略设置为public static void Register(HttpConfiguration config)中调用的Application_Start()方法, 像这样:

config.Services.Replace(typeof(IHostBufferPolicySelector), new HostBufferPolicySelector());

还要检查服务器是否配置为接受HEAD


此解决方案有几个优点:

  • 可扩展:通过工厂和继承
  • 适应性:头部处理程序是在控制器动作内创建的,您在其中具有所有需要的信息来响应请求。
  • 资源消耗少且速度快:因为HEAD是通过WebAPI API处理的
  • 易于理解/维护:遵循WebAPI处理流程
  • 关注分离

我创建了一个Web API 2文件存储控制器,该控制器通过类似的机制支持HEAD

感谢Henning Krause的问题,以及answer导致我进入的问题。