ServiceStack JsonServiceClient, force traffic on the wire for localhost? - c#

This ServiceStack client code works:
var client = new JsonServiceClient("http://localhost:32949/test");
var request = new MyRequest { ClassificationId = new ClassificationId (21300) };
var response = client.Post(request);
However, when observing the traffic in Fiddler, I see nothing. I would like to observe the traffic to get a better idea on how to build the required JSON request for other clients I have to write.
To make the above code work, I had to reference the assembly that has the service, and I am suspecting that ServiceStack is making some clever calls to avoid sending a HTTP request. Is this the case ?
Why am I not seeing any traffic in Fiddler, and how do I force it ?
HTTP traffic to localhost endpoints via the browser is shown correctly.

Edit your hosts file, located at
C:\Windows\System32\drivers\etc\hosts
and add the following entry
127.0.0.1 mymachine.com
then point your client to mymachine.com instead of localhost

I will answer my own question here - commenter #wal pointed out the problem to me:
This has nothing to do with ServiceStack, and requests actually go over the http protocol. The problem was looping back to localhost did not send the traffic through fiddler. It is actually explained on the Fiddler2 FAQ page.

The other trick is to replace your "localhost" uri with your machine name, and that should work out of the box with Fiddler.
http://machinename:port/test

Related

Can WCF access a SOAP 1.1 service using https?

I have a SOAP service I need to talk to from a C# application. I have a PHP test application that is using the same SOAP service today, using the standard SoapClient.
It is used similar to this:
$options = array(
'login' => $username,
'password' => $password,
'location' => "https://$serveruri/soap",
);
$service = new SOAPClient($wsdl, $options);
$retval = $service->SomeMethod($parameter);
And this works just fine. As far as I understand (I am useless at PHP), since we're not setting an authentication option, it should go with Basic authentication.
I am trying to talk to the same endpoint in C#, and it keeps prompting me for authentication. I am not sure how to recreate the same authentication. I believe some of the problems come from using https - I have been able to talk to a similar system in the past using http and the BasicHttpBinding, but since I have to talk to this one across https, that is no good.
So I have generated a client proxy using SvcUtil, and I am trying to talk to it. Here is my current iteration of desperation:
var endpoint = new EndpointAddress(SoapUrl);
var binding = new WSHttpBinding(SecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.SendTimeout = new TimeSpan(0, 0, 10);
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
var client = new soapServiceClient(binding, endpoint);
if (client.ClientCredentials != null)
{
client.ClientCredentials.UserName.UserName = Settings.AuthenticationUsername;
client.ClientCredentials.UserName.Password = Settings.AuthenticationPassword;
}
var retval = client.SomeMethod(parameter);
And to address Jons comment below: client.ClientCredentials really is not null, ever, when I run this. ReSharper put that there to stop itself from nagging.
When I run this, I get a 401 Unauthorized from the server. I get the following in Visual Studio 2013:
Additional information: The HTTP request is unauthorized with client authentication scheme 'Basic'. The authentication header received from the server was 'Basic realm="bla bla service"'.
How can I get WCF to behave like PHPs SOAPClient in this case?
Is there another binding I should use? I have tried different SecurityMode values, I have tried Digest authentication, but they get me nowhere. Since this happens over https I guess I can't Wireshark it either..
Thankful for any insights!
EDIT: Jon suggested to give Fiddler2 a try. I turned on https decryption and gave it a go.
Two things jump out at me:
1) When I run the PHP script locally (using EasyPHP running on my machine), it contacts the SOAP server and gets data. However, Fiddler2 does not see this traffic in any way.
2) When I run my app in Visual Studio 2013, Fiddler2 does see the traffic, and it decrypts it. I see two attempts to contact the SOAP endpoint; Both get a 401 reply. The first contains no auth information (looking at the Inspectors -> Auth part of Fiddler2) and just says:
No Proxy-Authorization Header is present.
No Authorization Header is present.
Then the second request tries to fix that with an Authorization header that looks kinda like this:
Authorization Header is present: Basic [some hash data]
Decoded Username:Password= [correct username]:[correct password]
But as mentioned - this still provokes a 401 from the other side.
I have no idea why the PHP traffic doesn't show up in Fiddler2, but it very clearly receives live data from the other side. Perhaps PHP doesn't use the network stack in a way that Fiddler2 can pick up, I have no idea.
EDIT 2: After a bunch of filthy debugging on the PHP side, I finally got to compare the request/response cycle of the PHP app with the WCF one. That got me a lot closer (somehow I had gotten a sub character into the auth username, which caused the authentication issue), but now I am struggling with a ProtocolException, caused by my binding being set to Content-Type application/soap+xml, and the external service (I believe this to be Linux based) returning text/xml.
My understanding from abusing Google on this is that text/xml is common for SOAP 1.1, while application/soap+xml is what is used with SOAP 1.2.
Also, I understand that in WCF BasicHttpBinding supports SOAP 1.1, while WsHttpBinding supports SOAP 1.2.
Since this service is on the public internet, it requires https for security. I believe that the original SOAP service on the system itself uses HTTP, but it is behind a gateway that requires HTTPS and then passes it on as HTTP to the actual box serving the requests.
The core of the question then becomes: Is there a way for me to access a SOAP 1.1 service using HTTPS with WCF?
So I've come full circle on this. I started out this morning as a WCF newbie, and while I won't even claim to be a competent user yet, I understand a lot more about the bindings.
By using shotgun debugging I had actually done it correctly a few times, but I was tricked by an invisible sub character (^Z I believe) in the username I copied into my code.
Also, this was compounded by me seeing a lot of information about BasicHttpBinding not supporting https - it does, if you create it like this:
var binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
Credit to this relatively old article for educating me to the point of figuring it out:
https://msdn.microsoft.com/en-us/magazine/cc163394.aspx
From this article I also came to the understanding that had BasicHttpBinding not supported https, I could have built my own CustomBinding that worked the same with relatively little effort.
Also thanks to our overlord Jon Skeet for pushing me in the right direction of figuring out what was actually being sent across the wire!

Why would WCF fail to call a SOAP service when a 302 response is encountered?

I have written an application that starts with making a WCF call to login. I generated the client code with a service reference. It works fine for clients who have their services installed locally to their network. However, there is a saas environment, as well, where these same services are controlled by the corporate powers that be. In the saas environment, I was informed that the login was failing. Investigating using Fiddler, I found that the service call to login is returning HTML, specifically, the web page listing all the available methods from the .asmx.
The saas environment has one little quirk which may be causing the problem here, but I don't know how to verify that this is the problem, nor how to solve it if it is the problem. The quirk is that the server redirects (302) the call.
The client code:
client.Endpoint.Address = new EndpointAddress("http://" + settings.MyURL + "/myProduct/webservices/webapi.asmx");
client.DoLogin(username, password);
The raw data sent to the server, before the redirect, includes the s:Envelope XML tag. Notice the missing s:Envelope XML tag when sending to the redirected server:
GET https://www.myurl.com/myProduct/webservices/webapi.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
VsDebuggerCausalityData: uIDPo7TgjY1gCLFLu6UXF8SWAoEAAAAAQxHTAupeAkWz2p2l3jFASiUPHh+L/1xNpTd0YqI2o+wACQAA
SOAPAction: "http://Interfaces.myProduct.myCompany.com/DoLogin"
Accept-Encoding: gzip, deflate
Host: www.gotimeforce2.com
Connection: Keep-Alive
How do I get this silly thing working?
Edit: It is worth noting that I am using WCF/svcutil.exe/service-reference rather than the older ASMX/wsdl.exe/web-reference. Otherwise, for future readers of this topic, the wsdl solution suggested by Raj would have been a great solution. If you are seeing this issue and are using the wsdl technique, see Raj's excellent answer.
Edit2: After doing a bunch of research into WCF and 302, it sounds like they just don't play well together, nor does there appear to be a simple way of giving the WCF api custom code to handle the situation. As I have no control over the server, I have sucked it up and re-generated my api as a web-reference and am using Raj's solution.
Edit3: Updated the title to better reflect the solution, now that the cause of the issue is understood. Original title: Why would WCF not include s:Envelope on a redirect?
Ok, So I did some digging on this and tried to replicate the issue on my side. I was able to replicate the issue and find a solution to it as well. However I'm not sure how well this will apply in your case since it is dependent on interfacing with the server team that manages the load balancer. Here are the findings.
Looking at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html you notice the following addendum in the explanation for HTTP Status Codes 302 and 303.
302 Found
Note: RFC 1945 and RFC 2068 specify that the client is not allowed
to change the method on the redirected request. However, most
existing user agent implementations treat 302 as if it were a 303
response, performing a GET on the Location field-value regardless
of the original request method. The status codes 303 and 307 have
been added for servers that wish to make unambiguously clear which
kind of reaction is expected of the client.
303 See Other
Note: Many pre-HTTP/1.1 user agents do not understand the 303
status. When interoperability with such clients is a concern, the
302 status code may be used instead, since most user agents react
to a 302 response as described here for 303.
Further looking at http://en.wikipedia.org/wiki/List_of_HTTP_status_codes you notice the following explanation for the HTTP status codes 302, 303 and 307.
302 Found :
This is an example of industry practice contradicting the standard. The HTTP/1.0 specification (RFC 1945) required the client to perform a temporary redirect (the original describing phrase was "Moved Temporarily"), but popular browsers implemented 302 with the functionality of a 303 See Other. Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the two behaviors. However, some Web applications and frameworks use the 302 status code as if it were the 303.
303 See Other (since HTTP/1.1):
The response to the request can be found under another URI using a GET method. When received in response to a POST (or PUT/DELETE), it should be assumed that the server has received the data and the redirect should be issued with a separate GET message.
Here is the basic flow in a normal Client/Server Interaction
307 Temporary Redirect (since HTTP/1.1):
In this case, the request should be repeated with another URI; however, future requests should still use the original URI. In contrast to how 302 was historically implemented, the request method is not allowed to be changed when reissuing the original request. For instance, a POST request should be repeated using another POST request.
So according to this, we are able to explain the behavior of the WCF call which sends a GET request without the s:Envelope on the 302 redirect. This will undoubtedly fail on the client side.
The easiest way of fixing this is to have the server return a 307 Temporary Redirect instead of a 302 Found status code in the response. Which is where you need the help of the Server Team that manages the redirect rules on the load balancer. I tested this out locally and the client code consuming the service with the Service Reference seamlessly executes the call even with the 307 Temporary Redirect.
In fact you could test this all out with the solution I've uploaded to Github Here. I've updated this to illustrate the utilization of a service reference instead of a wsdl generated proxy class to consume the asmx service.
However if the change from 302 Found to 307 Temporary Redirect is not feasible in your environment, then I would suggest using either Solution 1 (which shouldn't have a problem whether it is a 302 or 307 status code in the response) or using my original answer which would resolve this by directly accessing the service at the correct URL based on the setting in the config file. Hope this helps!
Solution 1
If you do not have access to the config files on production or if you just plain don't want to use the multiple URLs in the config file, you could use this following approach. Link to Github repo containing sample solution Click Here
Basically, if you notice the file auto generated by wsdl.exe you will notice that the service proxy class derives from System.Web.Services.Protocols.SoapHttpClientProtocol. This class has a protected method System.Net.WebRequest GetWebRequest(Uri uri) that you can override. In here you could add a method to check to see if a 302 temporary redirect is the result of HttpWebRequest.GetResponse() method. If so, you can set the Url to the new Url returned in the Location header of the response as follows.
this.Url = new Uri(uri, response.Headers["Location"]).ToString();
So create a class called SoapHttpClientProtocolWithRedirect as follows.
public class SoapHttpClientProtocolWithRedirect :
System.Web.Services.Protocols.SoapHttpClientProtocol
{
protected override System.Net.WebRequest GetWebRequest(Uri uri)
{
if (!_redirectFixed)
{
FixRedirect(new Uri(this.Url));
_redirectFixed = true;
return base.GetWebRequest(new Uri(this.Url));
}
return base.GetWebRequest(uri);
}
private bool _redirectFixed = false;
private void FixRedirect(Uri uri)
{
var request = (HttpWebRequest)WebRequest.Create(uri);
request.CookieContainer = new CookieContainer();
request.AllowAutoRedirect = false;
var response = (HttpWebResponse)request.GetResponse();
switch (response.StatusCode)
{
case HttpStatusCode.Redirect:
case HttpStatusCode.TemporaryRedirect:
case HttpStatusCode.MovedPermanently:
this.Url = new Uri(uri, response.Headers["Location"]).ToString();
break;
}
}
}
Now comes the part that illustrates the advantage of using a proxy class manually generated using wsdl.exe instead of a service reference. In the manually created proxy class. modify the class declaration from
public partial class WebApiProxy : System.Web.Services.Protocols.SoapHttpClientProtocol
to
public partial class WebApiProxy : SoapHttpClientProtocolWithRedirect
Now invoke the DoLogin method as follows.
var apiClient = new WebApiProxy(GetServiceUrl());
//TODO: Add any required headers etc.
apiClient.DoLogin(username,password);
You will notice that the 302 redirect is handled smoothly by the code in your SoapHttpClientProtocolWithRedirect class.
One other advantage is that, by doing this, you will not have to fear that some other developer is going to refresh the service reference and lose the changes that you made to the proxy class since you had manually generated it. Hope this helps.
Original Answer
Why don't you just include the entire url for production/local service in the config file? That way you can initiate the call with the appropriate url in the appropriate location.
Also, I would refrain from using a service reference in any code destined for production. One way of using the asmx service without a service reference would be to generate the WebApiProxy.cs file using the wsdl.exe tool. Now you can just include the WebApiProxy.cs file in your project and instantiate as shown below.
var apiClient = new WebApiProxy(GetServiceUrl());
//TODO: Add any required headers etc.
apiClient.DoLogin(username,password);
Here is the GetServiceUrl() method. Use a Configuration Repository to further decouple and improve testability.
private string GetServiceUrl()
{
try
{
return
_configurationRepository.AppSettings[
_configurationRepository.AppSettings["WebApiInstanceToUse"]];
}
catch (NullReferenceException ex)
{
//TODO: Log error
return string.Empty;
}
}
Then your config file can contain the following information in the section.
<add key="StagingWebApiInstance" value="http://mystagingserver/myProduct/webservices/webapi.asmx "/>
<add key="ProductionWebApiInstance" value="https://www.myurl.com/myProduct/webservices/webapi.asmx"/>
<!-- Identify which WebApi.asmx instance to Use-->
<add key="WebApiInstanceToUse" value="ProductionWebApiInstance"/>
Also I would refrain from concatenating strings using the + overload. When doing it once, it doesn't come across as too much of a performance impact but if you have many concatenations like this throughout the code, it would lead to a big difference in execution times compared to using a StringBuilder. Check http://msdn.microsoft.com/en-us/library/ms228504.aspx for more information on why using a StringBuilder improves performance.

Consume a HTTP POST from a self hosted service

I have a self hosted service that needs to listen for upload notifications coming from a BITS server (they are a simple HTTP POST request with custom headers). If I was not self hosting my service and was using IIS I would just make a ASPX page and I could handle the incoming requests, but I am using self hosted WCF and I can not switch to IIS.
I looked in to using WebInvokeAttribute, however that appears to only be for sending JSON or XML as a reply and I need to follow the protocol spec. Also I did not see a way of pulling out the custom headers.
The next thing I looked in to was HttpListener and it appears to do what I need, however I did not see if there is a way to configure it via my app.config file like normal WCF endpoints.
Do I just add the address to my applicationSettings section or is there a better way to achieve what I am trying to do?
I ended up just using the Properties class and storing the url there.
//This is run on it's own thread
HttpListener listener = new HttpListener();
listener.Prefixes.Add(Properties.Settings.Default.BitsReplierAddress);
listener.Start();
while (_running)
{
// Note: The GetContext method blocks while waiting for a request.
// Could be done with BeginGetContext but I was having trouble
// cleanly shutting down
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
var requestUrl = request.Headers["BITS-Original-Request-URL"];
var requestDatafileName = request.Headers["BITS-Request-DataFile-Name"];
//(Snip...) Deal with the file that was uploaded
}
listener.Stop();

Http Post with Partial URL in C#.NET

I have a web application (which I have no control over) I need to send HTTP post programatically to. Currently I've using HttpWebRequest like
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://someserver.com/blah/blah.aspx");
However the application was returning a "Unknown Server Error (not the IIS error, a custom application error page)" when posting to data. Using Fiddler to compare my Post vs IE post I can see the only difference is in the POST line of the request:
In Internet Explorer Fiddler (RAW view) shows traffic
POST /blah/blah.aspx HTTP/1.1
In my C# program fiddler (RAW view) records traffic as
POST https://someserver.com/blah/blah.aspx HTTP/1.1
This is only difference from both both requests.
From what I've researched so far it seems there is no way to make HttpWebRequest.Create post the relative URL.Note: I see many posts on "how to use relative URLs" but these suggestions do not work, as the actual post is still done using an absolute URL (when you sniff the HTTP traffic)
What is simplest way to accomplish this post with relative URL?
(Traffic is NOT going through a proxy)
Update: For the time being I'm using IE automation to do scheduled perf test, instead of method above. I might look at another scripting language as I did want to test without any browser.
No, you can't do POST without server in a Url.
One possible reason your program fails is if it does not use correct proxy and as result can't resolve server name.
Note: Fiddler shows path and host separately in the view you are talking about.
Configure you program to use Fiddler as proxy (127.0.0.1:8888) and compare requests that you are making with browser's ones. Don't forget to switch Fiddler to "show all proceses".
Here is article on configuring Fiddler for different type of environment including C# code: Fiddler: Configuring clients
objRequest = (HttpWebRequest)WebRequest.Create(url);
objRequest.Proxy= new WebProxy("127.0.0.1", 8888);

WCF REST hosted in IIS 6 , not able to consume it on client

I have hosted my WCF REST Service in IIS6. But when I try to consume any method, it gives me an error 400.
However when I use the same url in IE, the desired response is achieved. How come I can't consume it in my client while I can hit it directly in IE.
Following is the Client code :
string xmlInputValue = XMLUtility<string>.GetDataContractXml("Testing", null);
WebClient wc = new WebClient();
wc.Headers["Content-Type"] = "application/octet-stream";
xmlInputValue = string.Empty;
byte[] buf = new byte[0x10000];
wc.UploadString(new Uri(#"http://localhost/FileUpload/UploadData/PingTest/?123"), "POST", "4567");
Got the answer. While using IIS for hosting the service, endpoint address should be blank
One quick question, why are you setting Content-type as "application/octet-stream"? are you sure service binding is expecting the same content-type. Anyways, I'd capture Fiddler trace (using fiddler tool: http://fiddler2.com) for IE (success scenario) and check how request is sent by IE which is accepted by service and compare with request sent by client application.
HTH,
Amit

Categories

Resources