I have silverlight app where I want to download assemblies from my server on demand rather than from within a xap. I would like the files to at least be compressed though over the wire. When I add the Accept-Encoding: gzip and then watch the output in
Fiddler I don't see any indication that the content was compressed and there was no Content-Encoding in the response header.
If I try to manually handle the Accept-Encoding and then returning a HttpResponseMessage with the encoded content and the Content-Encoding set I get the following exception:
[InvalidOperationException: Reading from the compression stream is not supported.]
System.Web.Http.WebHost.HttpControllerHandler.EndProcessRequest(IAsyncResult result) +210
System.Web.Http.WebHost.HttpControllerHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +39
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8971485
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
Asp.Net WebAPI does not support Compression (and hence the Accept-Encoding behavior) out of the box.
Can you show us the code where you are trying to compress the content?
Following is an example of how you can compress your resposne content:
In the below example, At the "EncodingDelegateHandler", we wrap the Content returned from upper layers into a CompressedContent. If you notice the Controller, we are returning 3 types of content here: StringContent, ObjectContent & StreamContent. All these
contents would be compressed by the CompressedContent.
public class AcceptEncodingTestsController : ApiController
{
[HttpGet]
public HttpResponseMessage GetStringContent()
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StringContent("Hello World!");
return response;
}
[HttpGet]
public HttpResponseMessage GetStreamContent()
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StreamContent(new FileStream(@"D\Sample.txt", FileMode.Open));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
return response;
}
[HttpGet]
public Customer GetCustomer() // the return value is actually converted to ObjectContent
{
return new Customer() { CustomerId = "A101" };
}
}
public class EncodingDelegateHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
{
HttpResponseMessage response = responseToCompleteTask.Result;
if (response.RequestMessage.Headers.AcceptEncoding != null)
{
string encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value;
response.Content = new CompressedContent(response.Content, encodingType);
}
return response;
},
TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
public class CompressedContent : HttpContent
{
private HttpContent originalContent;
private string encodingType;
public CompressedContent(HttpContent content, string encodingType)
{
if (content == null)
{
throw new ArgumentNullException("content");
}
if (encodingType == null)
{
throw new ArgumentNullException("encodingType");
}
originalContent = content;
this.encodingType = encodingType.ToLowerInvariant();
if (this.encodingType != "gzip" && this.encodingType != "deflate")
{
throw new InvalidOperationException(string.Format("Encoding '{0}' is not supported. Only supports gzip or deflate encoding.", this.encodingType));
}
// copy the headers from the original content
foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
{
this.Headers.AddWithoutValidation(header.Key, header.Value);
}
this.Headers.ContentEncoding.Add(encodingType);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
Stream compressedStream = null;
if (encodingType == "gzip")
{
compressedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
}
else if (encodingType == "deflate")
{
compressedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true);
}
return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
{
if (compressedStream != null)
{
compressedStream.Dispose();
}
});
}
}
I tried the following. Of course it could be incorrect:
var stream = this.repository.GetAssembly(name);
if (stream != null)
{
var preferredEncoder = (from p in this.Request.Headers.AcceptEncoding
orderby p.Quality descending
select p.Value).ToArray();
if (preferredEncoder.Length > 0)
{
var ms = new MemoryStream();
var buffer = new byte[stream.Length];
if (preferredEncoder[0] == "gzip")
{
var compressor = new GZipStream(ms, CompressionMode.Compress);
int bytesRead = 0;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
compressor.Write(buffer, 0, bytesRead);
}
response.Content = new StreamContent(compressor);
response.Content.Headers.Add("Content-Encoding", "gzip");
}
else if (preferredEncoder[0] == "deflate")
{
response.Content = new StreamContent(new DeflateStream(stream, CompressionMode.Compress));
response.Content.Headers.Add("Content-Encoding", "deflate");
}
}
else
{
// Use the stream directly.
response.Content = new StreamContent(stream);
}
response.Content.Headers.Add("Content-Type", "application/binary");
}
else
{
response.StatusCode = System.Net.HttpStatusCode.NotFound;
}
EDIT: Specifically the GZip path. I know that the deflate one doesn't work, I just haven't finished it yet.
It's odd that you say that it isn't supported. When reading JSON from the server, these are my response headers out of the box:
Cache-Control no-cache
Content-Encoding gzip
Content-Type application/json
Date Tue, 21 Feb 2012 19:32:40 GMT
Expires -1
Pragma no-cache
Server Microsoft-IIS/7.5
Transfer-Encoding chunked
Vary Accept-Encoding
X-AspNet-Version 4.0.30319
X-Powered-By ASP.NET
However, when reading output from a custom formatter, gzip is not used. The sample of the CompressedContent isn't very usable for me as I can't use StringContent and using StreamContent requires me to know the format I am serializing in the Controller,
which kinda beats the use of the formatters. I can easily write to a GZipStream in the WriteToStream method of my formatter, but then the client ends up with a compressed message, which is useless. Is there any way to get the intended behavior through the
use of a MessageHandler? Thanks in advance...
Edit: Just found out that it's actually IIS that's doing the compression for me, so I guess I'm barking up the wrong tree here...
@Darrel Miller : Thanks for pointing it out the problem. I have now updated the post with modified code.
@2LM: Yes, having at the message handler is a very convenient approach. In the updated code, you should see that the CompressedContent is used at the message handler level, which wraps contents like StringContent, StreamContent & ObjectContent returned from
the controller's actions.
I have an additional question though... My custom content is now nicely compressed, but the JSON message that was previously compressed to 165Kb by IIS, is now only compressed to 219Kb by the new message handler. Any ideas on where the difference of 54Kb
could be?
Member
2 Points
4 Posts
Accept-Encoding handling?
Feb 20, 2012 03:41 PM|clintsinger1|LINK
Hi,
I have silverlight app where I want to download assemblies from my server on demand rather than from within a xap. I would like the files to at least be compressed though over the wire. When I add the Accept-Encoding: gzip and then watch the output in Fiddler I don't see any indication that the content was compressed and there was no Content-Encoding in the response header.
If I try to manually handle the Accept-Encoding and then returning a HttpResponseMessage with the encoded content and the Content-Encoding set I get the following exception:
[InvalidOperationException: Reading from the compression stream is not supported.]
System.Web.Http.WebHost.HttpControllerHandler.EndProcessRequest(IAsyncResult result) +210
System.Web.Http.WebHost.HttpControllerHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +39
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8971485
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
What am I doing wrong?
Cheers,
Clint
Participant
781 Points
301 Posts
Microsoft
Re: Accept-Encoding handling?
Feb 20, 2012 04:42 PM|Kiran Challa|LINK
[EDITED]
Asp.Net WebAPI does not support Compression (and hence the Accept-Encoding behavior) out of the box.
Can you show us the code where you are trying to compress the content?
Following is an example of how you can compress your resposne content:
In the below example, At the "EncodingDelegateHandler", we wrap the Content returned from upper layers into a CompressedContent. If you notice the Controller, we are returning 3 types of content here: StringContent, ObjectContent & StreamContent. All these contents would be compressed by the CompressedContent.
public class AcceptEncodingTestsController : ApiController { [HttpGet] public HttpResponseMessage GetStringContent() { HttpResponseMessage response = new HttpResponseMessage(); response.Content = new StringContent("Hello World!"); return response; } [HttpGet] public HttpResponseMessage GetStreamContent() { HttpResponseMessage response = new HttpResponseMessage(); response.Content = new StreamContent(new FileStream(@"D\Sample.txt", FileMode.Open)); response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); return response; } [HttpGet] public Customer GetCustomer() // the return value is actually converted to ObjectContent { return new Customer() { CustomerId = "A101" }; } } public class EncodingDelegateHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) => { HttpResponseMessage response = responseToCompleteTask.Result; if (response.RequestMessage.Headers.AcceptEncoding != null) { string encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value; response.Content = new CompressedContent(response.Content, encodingType); } return response; }, TaskContinuationOptions.OnlyOnRanToCompletion); } } public class CompressedContent : HttpContent { private HttpContent originalContent; private string encodingType; public CompressedContent(HttpContent content, string encodingType) { if (content == null) { throw new ArgumentNullException("content"); } if (encodingType == null) { throw new ArgumentNullException("encodingType"); } originalContent = content; this.encodingType = encodingType.ToLowerInvariant(); if (this.encodingType != "gzip" && this.encodingType != "deflate") { throw new InvalidOperationException(string.Format("Encoding '{0}' is not supported. Only supports gzip or deflate encoding.", this.encodingType)); } // copy the headers from the original content foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers) { this.Headers.AddWithoutValidation(header.Key, header.Value); } this.Headers.ContentEncoding.Add(encodingType); } protected override bool TryComputeLength(out long length) { length = -1; return false; } protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) { Stream compressedStream = null; if (encodingType == "gzip") { compressedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true); } else if (encodingType == "deflate") { compressedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true); } return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk => { if (compressedStream != null) { compressedStream.Dispose(); } }); } }Kiran Challa
Member
2 Points
4 Posts
Re: Accept-Encoding handling?
Feb 20, 2012 05:21 PM|clintsinger1|LINK
var stream = this.repository.GetAssembly(name); if (stream != null) { var preferredEncoder = (from p in this.Request.Headers.AcceptEncoding orderby p.Quality descending select p.Value).ToArray(); if (preferredEncoder.Length > 0) { var ms = new MemoryStream(); var buffer = new byte[stream.Length]; if (preferredEncoder[0] == "gzip") { var compressor = new GZipStream(ms, CompressionMode.Compress); int bytesRead = 0; while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0) { compressor.Write(buffer, 0, bytesRead); } response.Content = new StreamContent(compressor); response.Content.Headers.Add("Content-Encoding", "gzip"); } else if (preferredEncoder[0] == "deflate") { response.Content = new StreamContent(new DeflateStream(stream, CompressionMode.Compress)); response.Content.Headers.Add("Content-Encoding", "deflate"); } } else { // Use the stream directly. response.Content = new StreamContent(stream); } response.Content.Headers.Add("Content-Type", "application/binary"); } else { response.StatusCode = System.Net.HttpStatusCode.NotFound; }Participant
781 Points
301 Posts
Microsoft
Re: Accept-Encoding handling?
Feb 20, 2012 05:40 PM|Kiran Challa|LINK
Using the CompressedContent class, you could change your implementation to the following:
var stream = this.repository.GetAssembly(name); if (stream != null) { var preferredEncoder = (from p in this.Request.Headers.AcceptEncoding orderby p.Quality descending select p.Value).ToArray(); StreamContent streamCtnt = new StreamContent(stream); if (preferredEncoder.Length > 0) { response.Content = new CompressedContent(streamCtnt, preferredEncoder[0]); } else { // Use the stream directly. response.Content = streamCtnt; } response.Content.Headers.Add("Content-Type", "application/binary"); } else { response.StatusCode = System.Net.HttpStatusCode.NotFound; }Kiran Challa
Member
80 Points
71 Posts
Re: Accept-Encoding handling?
Feb 20, 2012 06:14 PM|Darrel Miller|LINK
Out of curiosity, why do you Wait() on the CopyToAsync task in the SerializeToStreamAsync? Is that necessary?
None
0 Points
3 Posts
Re: Accept-Encoding handling?
Feb 21, 2012 02:48 PM|2LM|LINK
Hi,
It's odd that you say that it isn't supported. When reading JSON from the server, these are my response headers out of the box:
Cache-Control no-cache
Content-Encoding gzip
Content-Type application/json
Date Tue, 21 Feb 2012 19:32:40 GMT
Expires -1
Pragma no-cache
Server Microsoft-IIS/7.5
Transfer-Encoding chunked
Vary Accept-Encoding
X-AspNet-Version 4.0.30319
X-Powered-By ASP.NET
However, when reading output from a custom formatter, gzip is not used. The sample of the CompressedContent isn't very usable for me as I can't use StringContent and using StreamContent requires me to know the format I am serializing in the Controller, which kinda beats the use of the formatters. I can easily write to a GZipStream in the WriteToStream method of my formatter, but then the client ends up with a compressed message, which is useless. Is there any way to get the intended behavior through the use of a MessageHandler? Thanks in advance...
Edit: Just found out that it's actually IIS that's doing the compression for me, so I guess I'm barking up the wrong tree here...
Regards,
Andy
Participant
781 Points
301 Posts
Microsoft
Re: Accept-Encoding handling?
Feb 23, 2012 04:37 PM|Kiran Challa|LINK
@Darrel Miller : Thanks for pointing it out the problem. I have now updated the post with modified code.
@2LM: Yes, having at the message handler is a very convenient approach. In the updated code, you should see that the CompressedContent is used at the message handler level, which wraps contents like StringContent, StreamContent & ObjectContent returned from the controller's actions.
Updated post is : http://forums.asp.net/post/4841854.aspx
Kiran Challa
None
0 Points
3 Posts
Re: Accept-Encoding handling?
Feb 24, 2012 01:42 AM|2LM|LINK
Thanks a lot, this works like a charm!
I have an additional question though... My custom content is now nicely compressed, but the JSON message that was previously compressed to 165Kb by IIS, is now only compressed to 219Kb by the new message handler. Any ideas on where the difference of 54Kb could be?
Regards,
Andy
Member
80 Points
71 Posts
Re: Accept-Encoding handling?
Feb 24, 2012 09:50 AM|Darrel Miller|LINK
Excellent stuff. Thanks.