Showing posts with label Proxy. Show all posts
Showing posts with label Proxy. 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

19 November 2007

Creating a simple proxy

Sometimes you want to give web browsers access to web resources that are best not opened indiscriminatly to the world. A good example are WMS servers: you want the users to be able to see maps, but putting a WMS server directly online is like putting a read-only sql client online. The solution is put the web resource on a server that is not accessible by the internet, but is accessible by the web application - i.e. the limited resource is put inside the DMZ, and create some kind of proxy page that streams data from the server inside the DMZ to the outside world - after doing some validations, of course. A setup for a proxy page (that only does the streaming, not the validation) may be as follows: Create an aspx page MyProxyPage.aspx and add the following code:
void ProcessProxyRequest( string request, HttpResponse response)
 {
   HttpWebRequest webRequest = HttpWebRequest.Create(request) as HttpWebRequest;
 
   // Important! Keeps the request from blocking after the first time!
   webRequest.KeepAlive = false;
   webRequest.Credentials = CredentialCache.DefaultCredentials;
   using (HttpWebResponse backendResponse = (HttpWebResponse)webRequest.GetResponse())
   {
     Stream receiveStream = backendResponse.GetResponseStream();
 
     // Clear whatever is already sent
     response.Clear();
     response.ClearContent();
     response.ClearHeaders();
 
     // 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.AppendHeader(header, backendResponse.Headers[header]);
     }
     // Copy content           
     byte[] buff = new byte[1024];
     long length = 0;
     int bytes;
     while ((bytes = receiveStream.Read(buff, 0, 1024)) > 0)
     {
       length += bytes;
       response.OutputStream.Write(buff, 0, bytes);
     }
 
     // Manually add content-lenght header to satisfy IE
     if (!contentLenghtFound) 
     {
       response.AppendHeader("Content-Length", 
         length.ToString());
     }
 
     receiveStream.Close();
     backendResponse.Close();
     response.Flush();
     response.Close();
   }
 }
Now of course, in the Page_Load you have to do something first:
protected void Page_Load(object sender, EventArgs e)
{
   string requestInsideDMZ = "http://somewhereinyourdmz/somepage.aspx";
   ProcessProxyRequest( requestInsideDMZ, 
                        Response);
}
If the user accesses MyProxyPage.aspx he gets the contents of http://somewhereinyourdmz/somepage.aspx instead.