How do I get a WCF client to process server responses which have been GZipped or Deflated by IIS?
On IIS, I've followed the instructions here on how to make IIS 6 gzip all responses (where the request contained "Accept-Encoding: gzip, deflate") emitted by .svc wcf services.
On the client, I've followed the instructions here and here on how to inject this header into the web request: "Accept-Encoding: gzip, deflate".
Fiddler2 shows the response is binary and not plain old Xml.
The client crashes with an exception which basically says there's no Xml header, which ofcourse is true.
In my IClientMessageInspector, the app crashes before AfterReceiveReply is called.
Some further notes:
(1) I can't change the WCF service or client as they are supplied by a 3rd party. I can however attach behaviors and/or message inspectors via configuration if this is the right direction to take.
(2) I don't want to compress/uncompress just the soap body, but the entire message.
Any ideas/solutions?
* SOLVED *
It was not possible to write a WCF extension to achieve these goals. Instead I followed this CodeProject article which advocate a helper class:
public class CompressibleHttpRequestCreator : IWebRequestCreate
{
public CompressibleHttpRequestCreator()
{
}
WebRequest IWebRequestCreate.Create(Uri uri)
{
HttpWebRequest httpWebRequest =
Activator.CreateInstance(typeof(HttpWebRequest),
BindingFlags.CreateInstance | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance,
null, new object[] { uri, null }, null) as HttpWebRequest;
if (httpWebRequest == null)
{
return null;
}
httpWebRequest.AutomaticDecompression =DecompressionMethods.GZip |
DecompressionMethods.Deflate;
return httpWebRequest;
}
}
and also, an addition to the application configuration file:
<configuration>
<system.net>
<webRequestModules>
<remove prefix="http:"/>
<add prefix="http:"
type="Pajocomo.Net.CompressibleHttpRequestCreator, Pajocomo" />
</webRequestModules>
</system.net>
</configuration>
What seems to be happening is that WCF eventually asks some factory or other deep down in system.net to provide an HttpWebRequest instance, and we provide the helper that will be asked to create the required instance.
In the WCF client configuration file, a simple basicHttpBinding is all that is required, without the need for any custom extensions.
When the application runs, the client Http request contains the header "Accept-Encoding: gzip, deflate", the server returns a gzipped web response, and the client transparently decompresses the http response before handing it over to WCF.
When I tried to apply this technique to Web Services I found that it did NOT work. Although the helper class was executed in the same was as when used by the WCF client, the http request did not contain the "Accept-Encoding: ..." header.
To make this work for Web Services, I had to edit the Web Proxy class, and add this method:
protected override System.Net.WebRequest GetWebRequest(Uri uri)
{
System.Net.HttpWebRequest rq = (System.Net.HttpWebRequest)base.GetWebRequest(uri);
rq.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
return rq;
}
Note that it did not matter whether the CompressibleHttpRequestCreator and block from the application config file were present or not. For web services, only overriding GetWebRequest in the Web Service Proxy worked.
Thanks for your WCF tip! We're going to be enabling IIS compression for services at my shop, and I'm hoping your solution will work.
By "To make this work for Web Services" - did you mean old school SoapHttpProtocol clients?
Because the SoapHttpProtocol class has a built-in EnableDecompression property, which will automatically handle the Compression header and response handling.
Here's an answer I gave to another question on the subject. That questio was asked from the perspective of ADO.NET Data Services, but my answer was purely about WCF.
Related
Have an ASP.NET Web API endpoint that generates JSON responses. But due to two factors can't be consumed directly from a browser.
cross-domain issues
need to provide session ticket for the API that is known only server side
So I need a lightweight server side proxy for client(browser) requests to extend the request with session key. Do not want to impose an overhead deserializing client JSON requests or Web API JSON responses in the proxy code. Would like to pass the payload "as is" and deserialize client requests only Web API side and the Web API responses only client (browser) side. That is the proxy takes json from the browser and passes it directly to Web API. It also passes the JSON response from the Web API to the browser directly without deserialization. Just a dummy proxy that does not know anything about the data it transfers.
Please suggest is it feasible and what is the best way to implement it. The existing web application (the one that is used to generate the client pages) is implemented using ASP.NET MVC 4.
Thanks in advance.
update for 2021:
You should probably be looking at https://microsoft.github.io/reverse-proxy/ if you have found your way here
old answer:
I wrote one for a previous version of WebApi. The code should be fairly easy to update for your purposes.
The basic idea is that you create a WebApi DelegatingHandler that passes the request on to an HttpClient:
public class ForwardProxyMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-Forwarded-For", request.GetClientIp());
if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Trace) request.Content = null;
request.RequestUri = new Uri(request.RequestUri.ToString().Replace(":3002", "")); //comes through with the port for the proxy, rewrite to port 80
request.Headers.AcceptEncoding.Clear();
var responseMessage = await new HttpClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
responseMessage.Headers.TransferEncodingChunked = null; //throws an error on calls to WebApi results
if (request.Method == HttpMethod.Head) responseMessage.Content = null;
return responseMessage;
}
}
I am having a few problems trying to connect to a ASP.NET webapi service (which I am running myself) from a sample console app using WebClient. The webapi is the typical sample site from MVC4:
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, new string[] { "value1", "value2" });
}
The Controller is decorated with a custom Authenticate attribute:
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization == null)
{
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.Add("WWW-Authenticate", "Basic realm=\"localhost\"");
actionContext.Response = response;
return;
}
}
The client code is the usual:
var wb = WebRequest.Create("http://localhost:64921/Values");
wb.Credentials = new NetworkCredential("xxx", "xxx");
var aaa = wb.GetResponse();
Console.WriteLine(aaa);
Console.ReadLine();
Now, I know that the WebClient or WebRequest are supposed to wait for a 401 before sending credentials and that is exactly what I am trying to do here.
Needless to say with the setup above nothing works. I have gone into the IIS express config and changed the following:
<basicAuthentication enabled="true" /> (in the security section)
<add name="BasicAuthenticationModule" lockItem="false" /> (in the modules section)
The problem that I am having is that the 401 gets returned even before the server code is actualy hit. I mean that if I stick a breakpoint into the Controller or the Attribute they are not hit. The details of the error are the usual long text about error 401.2 which I reckon is something to do with IIS configs, but using IIS express and not the nice IIS I do not have a nice GUI to fix this. Can anyone help?
Thanks a lot!
In the IIS config, you have enabled Basic auth processing, so IIS returns the 401 if there are no credentials or the credentials are invalid.
If you want your code to do the basic auth processing, then you need to tell IIS to allow anonymous access.
EDIT from comments
If you ask IIS to do basic auth it will check credentials against Windows accounts. This will act before the server code runs, so the Custom Auth Filter will not be hit. In this case the headers returned will be correct and you will see the WebClient performing the double request (one anonymous, one with credentials). If the WebClient does not use a computer or domain account (with read permissions on the folder where the site is located), the request will fail.
If you want to do authentication/authorization yourself, you need to tell IIS express not to do any auth and then do it all yourself... this basically means leaving everything as it is in the config (in your case reverting the pieces of config shown in the question) and sending the correct headers, which you already do. If you debug, you will see the Authenticate filter being hit twice, the first time it will be an anonymous that will go inside the if and generate your HTTP 401 Challenge response, the second time it will have credentials in the form of a standard Basic Authorization header: Basic <BASE64_ENCODED_CREDENTIALS>
I am working on the fastest way to write cookies from a WCF self hosted console app REST service with WebHttpBinding.
I prepare the "Set Cookies" but only the first cookie is set in the browser (using FireFox 11.0 on Windows) as well as the "Expires" value.
var expiry = DateTime.Now.AddMonths( 6 );
var outresp = WebOperationContext.Current.OutgoingResponse;
outresp.Headers.Add( HttpResponseHeader.SetCookie , "cookie1=fd9416ea-2705-4d44-be76-f2f7b95e6b13; cookie2=7d489e69-d9c9-4b8d-a4b1-bce0da1c5966;expires=" + expiry );
I also tried putting a comma between the first cookie and the second one, that did not work either:
outresp.Headers.Add( HttpResponseHeader.SetCookie , "cookie1=fd9416ea-2705-4d44-be76-f2f7b95e6b13;, cookie2=7d489e69-d9c9-4b8d-a4b1-bce0da1c5966;expires=" + expiry );
I cut this down, and fired up Fiddler:
outresp.Headers.Add( HttpResponseHeader.SetCookie , "foo=foovalue;,bar=barvalue;" );
and Fiddler shows this:
Response sent 27 bytes of Cookie data:
Set-Cookie: foo=foovalue;,bar=barvalue;
This response did not contain a P3P Header.
Validate P3P Policies at: http://www.w3.org/P3P/validator.html
Learn more at: http://www.fiddler2.com/redir/?id=p3pinfo
Which tells me that I am not formatting things on the .NET side to make the cookies acceptable to the browser.
How can I get all of the cookies into the browser insetad of just the first value? What did I miss?
Thanks.
[ EDIT ]
I took the issue to Microsoft for support, and learned this:
The fact that "Set-Cookie" is munged into a single cookie line is logged as a bug within Microsoft for .NET Framework 4.0 and also Framework 4.5.
Within the Microsoft WCF development group, the bug is listed as "closed" and "won't fix".
According to Microsoft, the only workaround is to move from self-hosted to hosting within IIS and then using the (IIS) Response object directly (different code path that does not have the bug).
Although this is an older post, since this is still an ongoing issue and using the AspNetCompatibilityMode with WCF has significant performance downside, the option that MSFT has supplied is not viable under many/most/any circumstances. I'm going to answer this question late since this issue is still lingering.
The only way to resolve this issue is to output the Set-Cookie response header just as you've shown, but to process that response header on the client side with javascript and place it in to the browser since, as you've noted, the browser will not handle this response. Neither will applying more than one Set-Cookie response header work as noted in this bug I created.
http://connect.microsoft.com/VisualStudio/feedback/details/779111/wcf-rest-service-two-set-cookie-http-headers-invalid-set-cookie-header-syntax
So, process the response header and use document.cookie in client-side javascript to place the cookies in the browser within the success handler of your jQuery .ajax request.
To expand on #jeff-fischer 's answer, AspNetCompatibilityMode does work and requires the following:
AspNetCompatibilityRequirements is set for the service class to either Allowed or Required e.g.:
[AspNetCompatibilityRequirements(RequirementsMode
= AspNetCompatibilityRequirementsMode.Allowed)]
public class AppService : IAppService
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" /> is set in <system.serviceModel>
This then gives access to HttpContext (you'll need using System.Web; to get access to this) and cookies can be set using:
var aCookie = new HttpCookie("foo")
{
HttpOnly = true,
Value = "bar",
Expires = DateTime.Now.AddDays(1)
};
HttpContext.Current.Response.Cookies.Add(aCookie);
This will then need to be run as an application on IIS rather than though the WCF launcher and if multiple cookies are set, multiple cookie headers will actually appear.
I've created a WCF REST service and initially used .Net Framework version 4. The service has two methods, one returns a plain string with the service status. The second allows a file to be uploaded. Both methods were working fine.
I was asked to see if the project could be moved back to only depend on .Net Framework 3.5 instead of version 4. I changed some references, and it built ok, and when I use the existing C++ client I can use the GetStatus method fine. However, now when a file gets uploaded, the client sees successful return codes to all methods, but, when I set a breakpoint at the start of WCF service's FileUpload method, it never gets executed. The file doesn't gets uploaded, it just disappears into the ether.
[ServiceContract]
internal interface IMyWebService
{
[OperationContract]
[WebGet(UriTemplate = "/Status", BodyStyle=WebMessageBodyStyle.Bare)]
Stream GetStatus();
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/FileUpload/{fileName}")]
Stream FileUpload(string fileName, Stream fileStream);
}
I've tried to use the WFetch tool as an alternative client. When I call the FileUpload method, I get this log:
started....WWWConnect::Close("localhost","80")\nclosed source port: 15800\r\n
WWWConnect::Connect("localhost","80")\nIP = "[::1]:80"\nsource port: 15866\r\n
REQUEST: **************\nPOST http://myMachine/MyService/FileUpload/hello.txt HTTP/1.1\r\n
Host: localhost\r\n
Accept: */*\r\n
Content-Length:11\r\n
\r\n
hello thereRESPONSE: **************\nHTTP/1.1 415 Missing Content Type\r\n
Content-Length: 0\r\n
Server: Microsoft-HTTPAPI/2.0\r\n
Date: Thu, 22 Sep 2011 13:26:09 GMT\r\n
\r\n
finished.
Could anybody give me any pointers for how I can diagnose this issue? I'm having trouble seeing where to start because no breakpoints get hit, and there are no error codes to investigate.
The WCF service must be doing something, because if I stop it, the client then fails to upload files, I just can't understand why execution never gets to the method that implements the POST operation.
Hmm, when using WFetch, even if I misspell the name of the method, it still seems to succeed with no error.
Thanks.
I'd start troubleshooting with the original 4.0 version of the service and a C# WCF-based test client to verify the service code will actually upload a file successfully. You could use the code in this Technet article for developing the test client.
Next, use your C++ client against the 4.0 service to verify your client will successfully send a file. Lastly, set the service back to 3.5 and see if it still works. To log messages the service is receiving for troubleshooting, look at this MSDN post to configure the built-WCF message logging capability.
I'm trying to create a simple JavaScript test front-end for my WCF service, but the front-end isn't working. I've traced the problem back to where the web page asks the service for http://..../myservice.svc/jsdebug (which, as I understand it, returns the JavaScript code for the proxy client that the web page can use to access the service) - this request is returning HTTP 400 -Bad Request.
In an effort to reduce the problem to its essentials, I've found I can replicate the problem simply:
Open Visual Studio 2010
Create a new project of
type WCF Service Application
Build
Run
In a browser navigate to http://localhost:portnumber/Service1.svc/jsdebug
The result is that the server returns HTTP 400 - Bad Request
What's going on here?
UPDATE:
The .svc file in is:
<%# ServiceHost Language="C#" Debug="true" Service="WebDataProxy.WebDataProxy" CodeBehind="WebDataProxy.svc.cs" %>
The service interface looks like:
namespace WebDataProxy
{
[ServiceContract(Namespace = "http://example.com/WebDataProxy")]
public interface IWebDataProxy
{
[OperationContract]
List<DataResponse> GetDataEx(List<DataRequest> requests);
[OperationContract]
DataResponse GetDataTest(DataRequest request);
[OperationContract]
string Hello(string input);
}
}
Hi you can check this post will help you
Article is about calling cross domain wcf service but it also works for same domain
http://pranayamr.blogspot.com/2011/06/calling-cross-domain-wcf-service-using.html