Responding to HEAD Requests

Hello,

I have a service which I want to respond to GET or HEAD requests.

The DTO Looks like this:

[FallbackRoute("/SNIP/{fileName}")]
public class LegacyContentFileRequest : IReturn<IHttpResult>
{
    public string fileName { get; set; }
}

The Service function looks like this:

public Object Any(LegacyContentFileRequest request)
{
        if(File.Exists(somePath + request.fileName) == false)
        {
            return new HttpError(HttpStatusCode.NotFound, "File Not Found.");
        }

        FileInfo file = new FileInfo(somePath + request.fileName);
        if (this.Request.Verb == "HEAD")
        {
            HttpResult result = new HttpResult();
            result.StatusCode = HttpStatusCode.OK;
            result.Headers.Add("Content-Length", file.Length.ToString());
            result.Headers.Add("Last-Modified", file.LastWriteTimeUtc.ToString("r"));
            result.Headers.Add("Content-Type", "video/mp4");
            return result;
        }   

       return new HttpResult(file, "video/mp4");
}

GETting the file works correctly. However, when I make a HEAD request I get a 500 response instead of a 200:

HTTP/1.1 500 Internal Server Error
Content-Length: 5396291
Content-Type: video/mp4
Last-Modified: Thu, 05 Oct 2017 15:03:43 GMT
Vary: Accept
Server: Microsoft-HTTPAPI/2.0
X-Powered-By: ServiceStack/4.514 NET45 Win32NT/.NET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type
Date: Thu, 19 Oct 2017 14:18:33 GMT

Why would I get a 500 response?

Many thanks!

-Z

First thing you should try do is find out what the internal Exception is, does the Response Body contain any error details?

Otherwise see if you can catch the Exception by registering a callback in IAppHost.ServiceExceptionHandlers or IAppHost.UncaughtExceptionHandlers.

Note HttpResult is a server-side wrapper object that lets you customize and add additional headers to the HTTP Response, it’s not the response itself and should never be used on the client, i.e. IReturn<IHttpResult>. If this API returns binary data it should be labelled as IReturn<byte[]> instead. As a rule you IReturn<T> should never contain an interface, it normally contains the DTO Response Type or raw data like IReturn<string> or IReturn<byte[]>.

This endpoint is designed to return a file. Does that mean I should use IReturn<byte[]>?

If I do that can I still return new HttpResult(file, "video/mp4"); as a way of setting the output mime type and returning the file at the same time?

Thanks,

-Z

Yes, the IReturn<T> is used to tell the client what response it should expect and a binary file returns a binary response body.

I wired up an UncaughtExceptionHandler and found the following:

System.Net.ProtocolViolationException: Bytes to be written to the stream exceed the Content-Length bytes size specified.
at System.Net.HttpResponseStream.Write(Byte buffer, Int32 offset, Int32 size)
at ServiceStack.Formats.HtmlFormat.SerializeToStream(IRequest req, Object response, IResponse res)
at ServiceStack.Host.ContentTypes.SerializeToStream(IRequest req, Object response, Stream responseStream)
at ServiceStack.HttpResult.d__112.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.HttpResult.d__111.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ServiceStack.HttpResponseExtensionsInternal.d__2.MoveNext()
— End of stack trace from previous location where exception was thrown —
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at ServiceStack.HttpResponseExtensionsInternal.d__7.MoveNext()

I figured this was due to the fact that I was setting a header value for Content-Length. However, when I comment out this line I still get the same exception.

Is there some way for me to manipulate the headers and return no body?

Thanks!

I’ve found that this seems to work:

base.Response.ContentType = "video/mp4";
base.Response.StatusCode = 200;
base.Response.SetContentLength(file.Length);
base.Response.End();
return null;

Is this a reasonable way to solve the problem, or is it better for me to return something.

Thanks,

-Z

The docs contains different ways to write custom HTTP Headers in a Service, e.g. you could also write custom HTTP Headers using a Request Filter.

I’ve tracked down the issue to a HttpResult with null response which is resolved in this commit. This fix is available from v5.0.0 that’s now available on MyGet. Please note v5 changes before upgrading.

The way I’d handle a HEAD request is to use a separate method for the Service and write the HTTP Headers directly to the response. You’ll need to use the custom SetContentLength() API to set the Content-Length, e.g:

public void Head(LegacyContentFileRequest request)
{
    Response.AddHeader("Last-Modified", file.LastWriteTimeUtc.ToString("r"));
    Response.ContentType = "video/mp4";
    Response.SetContentLength(file.Length);
    Response.EndRequest();
}
1 Like

As always, thanks for your thoughtful answers!

1 Like