The Life and Times of a Dev

Yes, we're really that weird
posts - 190 , comments - 343 , trackbacks - 106

My Links

News

Twitter












Tag Cloud

Archives

Post Categories

Play

Work

Well, that was fun! An adventure in WCF, SSL, and Host Headers

Had a problem with WCF that I thought I should blog about.

We're in the scenario where we have a WCF service that needs to call another service to do some calculating and then return the result back to the client.  For now, the service is hosted on the same server, but eventually, it could get expensive, so we'll push out that calculation to another server.

The WCF configuration also has an endpoint address that is not the same as the base address reported to it by IIS; IIS is of course reporting the machine name for the base address.  So here's the problem, you'd browse to the service url https://my.url.com/service/service.svc and you'd get the nice service page, but the url for the WSDL would be wrong, it'd be https://servername.anotherurl.com/service/service.svc?wsdl, and when the service would try and call the first url, you'd get weird 404 File Not Found errors, even though you could browse to the service and it'd work fine!

Turns out the 404 error was somewhat of a bad error.  The file was being found, but the XSD's that were generated were not, because they were at the wrong url.  If you clicked on the wsdl location, you'd see urls for https://servername.anotherurl.com/service/service.svc?xsd=xsd0 or something like that.

To make matters worse, the ssl certificate didn't match the servername url, which caused problems.  I didn't discover this until I tried to run svcutil on the service and came up with:

Attempting to download metadata from 'https://my.url.com/service/service.svc?wsdl' using WS-Metadata Exchange or DISCO.
Microsoft (R) Service Model Metadata Tool
[Microsoft (R) Windows (R) Communication Foundation, Version 3.0.4506.648]
Copyright (c) Microsoft Corporation.  All rights reserved.

Error: Cannot obtain Metadata from https://my.url.com/service/service.svc?wsdl

If this is a Windows (R) Communication Foundation service to which you have access, please check that you have enabled metadata publishing at the specified address.  For help enabling metadata publishing, please refer to the MSDN documentation at http://go.microsoft.com/fwlink/?LinkId=65455.

WS-Metadata Exchange Error
    URI: https://my.url.com/service/service.svc?wsdl

    Metadata contains a reference that cannot be resolved: 'https://my.url.com/service/service.svc?wsdl'.

    There was no endpoint listening at https://my.url.com/service/service.svc?wsdl that could accept the message. This is often
caused by an incorrect address or SOAP action. See InnerException, if present,
for more details.

    The remote server returned an error: (404) Not Found.

HTTP GET Error
    URI: https://my.url.com/service/service.svc?wsdl

    The document was understood, but it could not be processed.
  - The WSDL document contains links that could not be resolved.
  - There was an error downloading 'https://servername.anotherurl.com/service/service.svc?xsd=xsd0'.
  - The underlying connection was closed: Could not establish trust relationship
for the SSL/TLS secure channel.
  - The remote certificate is invalid according to the validation procedure.

 

After digging forever, I went down the path of seeing if I could modify the base address.  Nope, that failed, IIS provides the base address and what you set in the config is ignored.  So I tried modifying the behavior to have a specific behavior for this service.  That fails, because the URL is then created twice and you'll get an error.  Finally, I discovered that you can indeed use host headers on ssl!

Following the instructions here, I set the host header and after restarting IIS, the service page displayed the right urls, and the service worked like a charm.  You can get the service ID from IIS by clicking the Web Sites folder.  Also, the adsutil script is in the AdminScripts folder under inetpub.

I also discovered that you can't have more than one host header for IIS or wcf will break.  Bummer.  You can fix it with an article by Rob Zelt.  I don't like how he arbitrarily picks a host header; that seems rather fragile.  I'd instead read the base address out of a config file and then iterate through until I found the one that matched (or even just set it without using the passed in host headers at all).

Anyway, now I know!

Update (7/7/08): You also need to not have a value for the endpoint address.  Not sure why, but that seems to make it work.

Technorati Tags: ,,

Print | posted on Thursday, July 3, 2008 11:49 AM |

Feedback

Gravatar

# re: Well, that was fun! An adventure in WCF, SSL, and Host Headers

Rob,

Taking Rob Zelt's article mentioned in your post above, I have made it less fragile. This binds WCF within IIS only to host-header entries that match the host name in BindToHost app setting. It also ensures only one of each scheme (http/https) is registered whether or not BindToHost is used.

Hope this helps. Remember to point your .svc file at this factory.

public class MultipleIISBindingSupportServiceHostFactory : ServiceHostFactory
{

protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
Uri[] requiredAddress = GetAppropriateBase(baseAddresses);
return base.CreateServiceHost(serviceType, requiredAddress);
}

private Uri[] GetAppropriateBase(Uri[] baseAddresses)
{
List<string> schemesRegistered = new List<string>();
List<Uri> retAddress = new List<Uri>();

string bindToHost = System.Configuration.ConfigurationManager.AppSettings["BindToHost"];

foreach (Uri address in baseAddresses)
{
if (schemesRegistered.Contains(address.Scheme))
continue;

if (!string.IsNullOrEmpty(bindToHost))
{
if (address.DnsSafeHost == bindToHost.Trim())
{
schemesRegistered.Add(address.Scheme);
retAddress.Add(address);
}
}
else
{
schemesRegistered.Add(address.Scheme);
retAddress.Add(address);
}
}

if (!schemesRegistered.Contains("https"))
{
// add https using bindToHost FQDN

foreach (Uri address in retAddress)
{
if (address.DnsSafeHost == bindToHost.Trim())
{
Uri httpsUri = new Uri("https://" + bindToHost + address.AbsolutePath);
retAddress.Add(httpsUri);
break;
}
}
}

return retAddress.ToArray();
}
}
9/24/2008 2:01 PM | Darius
Gravatar

# re: Well, that was fun! An adventure in WCF, SSL, and Host Headers

I had a similar problem, turned out this bit was key for me:

Update (7/7/08): You also need to not have a value for the endpoint address. Not sure why, but that seems to make it work.


I had two services, one worked one didn't. Removing the endpoint address from the one that didn't work got it going.
4/7/2011 3:56 PM | kelvin
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 

Powered by: