Showing posts with label HTTP. Show all posts
Showing posts with label HTTP. Show all posts

29 November 2008

Writing a simple HTTP proxy using WCF

I ran into a situation with the following network topology: The intranet server served PDF documents via IIS, but was only accessible from the WCF server - which, for technical and political reasons, could not run in IIS but had to be a Windows Managed service that hosted a WCF service. Yet I needed to be able to download files from the intranet server via an ordinary URL. It turns out that you can write a WCF service mimicking a HTTP server pretty easily. If you can utilize .NET 3.5SP1, that is. 1. Data contract There are few key points to the contract:
  • Make a reference to System.ServiceModel.Web.dll
  • Add a "using System.ServiceModel.Web"
  • Define a method returning a Stream
  • Decorate that method with a WebGet attribute
Your datacontract could look like this:
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace ProxyService
{
    /// 
    /// Service contract for a proxy that forwards a http request
    [ServiceContract]
    public interface IHttpProxy
    {
        [OperationContract, WebGet]
        Stream GetProxyRequest(string target);
    }
}
2. Implementation class This is basically a modified version of a solution based upon an ASP.NET page I described before.
using System.IO;
using System.Net;
using System.ServiceModel.Web;
using System.Web;

namespace ProxyService
{
    /// 
    /// A proxy that forwards a http request
    /// 
    public class HttpProxy : IHttpProxy
    {
        public Stream GetProxyRequest(string target)
        {
            var urlToLoadFrom = HttpUtility.UrlDecode(target);
            HttpWebRequest webRequest = 
               HttpWebRequest.Create(urlToLoadFrom) as HttpWebRequest;

            // Important! Keeps the request from blocking after the first
            // time!
            webRequest.KeepAlive = false;
            webRequest.Credentials = CredentialCache.DefaultCredentials;
            using (var backendResponse = 
              (HttpWebResponse)webRequest.GetResponse())
            {
                using (var receiveStream = 
                  backendResponse.GetResponseStream())
                {
                    var ms = new MemoryStream();
                    var response = 
                      WebOperationContext.Current.OutgoingResponse;
                    // Copy headers
                    // Check if header contains a contenth-lenght since IE
                    // goes bananas if this is missing
                    bool contentLenghtFound = false;
                    foreach (string header in backendResponse.Headers)
                    {
                        if (string.Compare(header, 
                          "CONTENT-LENGTH", true) == 0)
                        { 
                          contentLenghtFound = true;
                        }
                        response.Headers.Add(header, 
                          backendResponse.Headers[header]);
                    }

                    // Copy contents
                    var buff = new byte[1024];
                    var length = 0;
                    int bytes;
                    while ((bytes = receiveStream.Read(buff, 0, 1024)) > 0)
                    {
                        length += bytes;
                        ms.Write(buff, 0, bytes);
                    }

                    // Add contentlength if it is missing
                    if (!contentLenghtFound) response.ContentLength = length;

                    // Set the stream to the start
                    ms.Position = 0;
                    return ms;
                }
            }
        }
    }
}
3. Configuration settings To get it all to work, you will need some configuration settings in the App.config of the hosting application:
<system.serviceModel>
  <services>
    <service name="ProxyService.HttpProxy" >
      <endpoint address="" binding="webHttpBinding"
                behaviorConfiguration="WebHttpBehavior"
                contract="ProxyService.IHttpProxy" >
      </endpoint>

      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:8002/ProxyService.HttpProxy" />
        </baseAddresses>
      </host>
    </service>

  </services>
  <behaviors>
    <endpointBehaviors>
      <behavior name="WebHttpBehavior">
        <webHttp />
      </behavior>
    </endpointBehaviors>
  </behaviors>
</system.serviceModel>
Notice the endpointBehaviors section: this is really important to get the stuff to work. 4. Using the proxy You can now simply enter "http://yourhost:8002/ProxyService.HttpProxy/GetProxyRequest?target=urlencodedurl" in your browser, and the WebGet attributes will automatically make the GetProxyRequest method get called with the value of target as value for the target parameter. The code of the proxy expects target to contain an URLEncoded URL (use HttpUtility.UrlEncode to encode the actual url to something that can be passed as a parameter on an URL). Concluding remarks The magic of the WebGet attribute is barely scratched by this example, but it makes creating REST services with WCF a real piece of cake. I am not sure if it was intended to be used this way, but it sure works like hell ;-)

28 February 2008

Forcing a web service proxy to use a specific http version

A short one this time, and a rare one too, but taken from real life. Sometimes a web service is implemented in such a way that it only understands http 1.1 or 1.0 but the proxy class that Visual Studio.NET generated does not know that (because the service is badly implemented, or for some other reason). If you cannot change the service implementation, you will have to change your client implementation. Say, you added a web reference to a web service and called the generated proxy class "MyService". The only thing you need to do is:
using System;
using System.Net;

namespace MyApplication
{
  public class MyFixedHTTPService : MyService
  {
    protected override WebRequest GetWebRequest(Uri uri)
    {
      HttpWebRequest w = base.GetWebRequest(uri) as HttpWebRequest;
      w.ProtocolVersion = HttpVersion.Version10;
      return w;
    }
  }
}
and then use this class to call your service. This forces the proxy to use HTTP 1.0. The best thing about the subclassing approach is that you can simply regenerate the proxy class if you need to, while still retaining your modifications