Simple web proxy in ASP.NET

2011-02-13 at 1:11 2 comments

In last two weeks, on two separate occasions, I needed a simple web service that would act as proxy to remote servers.

First, while writing my crude twitter ticker in Silverlight, I run into issue with lack of support for GIF images in Silverlight, and potentially also with accessing 3rd party servers. Normally, Silverlight and Flash (and also JavaScript) are able to freely contact ONLY their server of origin, but due to XSS attacks accessing other hosts is… less than ideal.

In this case I set up a simple ASP.NET handler that receives requests from my Silverlight program using URL like this:
http://localhost/imageproxy.ashx?url=http://remote.server.com/some/alien/avatar.gif

then the service contacts the URL specified in parameter, downloads the data and returns it to the original Silverlight app, in single trip. In my case the service also re-encodes the images as JPEG’s or PNG’s which are easier to work with in SL, but that step is optional, as most of the pictures are JPEG’s anyway and are passed through without changes. I used code from the article Silverlight: Handling Cross-Domain Images and Gifs by and modified it slightly.

A week later I was forced to overhear my unholy fallen .netless colleagues talking repeatedly about problems with accessing MS Reporting Services from Hell Java and blaming Integrated Windows Authentication (a.k.a. NTLM). When they started considering setting up ISA Server (aptly renamed to Microsoft Forefront Threat Management Gateway), I offered a single-file alternative that I could write in 5 minutes. Java would call this unsecured proxy, which in turn would apply security and access the MS Reporting server.

Two hours later the file was mostly done, tested and somewhat working. In order to host it under IIS on Windows you need to put it some existing “web folder”, e.g. c:\Inetpub\wwwroot\ or create separate one using IIS manager. No manual compilation is needed, just make sure the server has ASP.NET installed and enabled (tested on version 4.0 and IIS 7.5).

The usage is similar to the previous example, but it adds hard-coded credentials and POST support, used for talking to SOAP web services.

Please see the whole file below, including a funny fake password. Some cleaning and tuning advised, regarding error handling and security. I’m posting it anyway, even though one could probably write it from scratch faster than read this lengthy blog post.

The file should be saved with .ashx extension. Use at your own risk.

<%@ WebHandler Language="C#" Class="Ekus.WebServiceProxy" %>
using System;
using System.Collections;
using System.Data;
using System.Drawing;
using System.IO;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Net;
using System.Collections.Generic;

namespace Ekus
{

	/// <summary>
	/// Receives a web request with target URL, 
	/// invokes another web request to the target URL with added NTLM credentials, 
	/// and returns the response to the original caller.
	/// </summary>
	
	public class WebServiceProxy : IHttpHandler
	{
		public void ProcessRequest(HttpContext context)
		{
			string webServiceUrl = context.Request["url"].ToString();
			string proxy;
			// proxy = "127.0.0.1:8888"; used for testing with Fiddler2

			HttpWebRequest req = (HttpWebRequest)WebRequest.Create(webServiceUrl);
			if (proxy != null) req.Proxy = new WebProxy(proxy, true);
			// if SOAPAction header is required, add it here...
			req.Headers.Add("SOAPAction", context.Request.Headers["SOAPAction"]);
			req.ContentType = "text/xml;charset=\"utf-8\"";
			req.Accept = "text/xml";
			req.Method = context.Request.HttpMethod; // "POST";
			req.Credentials = new NetworkCredential(
				"joker", // username; didn't work when using domain\username format
				"funny",  // password
				"gotham"); // domain
			req.PreAuthenticate = true; // Cargo_cult_programming

			if (req.Method == "POST")
			{
				// copy original request "body" to the new request
				string input = new StreamReader(context.Request.InputStream).ReadToEnd();
				// encode it using the predefined encoding (see above, req.ContentType)
				System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
				byte[] bytesToSend = encoding.GetBytes(input);
				// Set the content length of the string being posted.
				req.ContentLength = bytesToSend.Length;
				Stream newStream = req.GetRequestStream(); // This method has the side effect of initiating delivery of the request in its current state to the server. Any properties like the request method, content type or content length as well as any custom headers need to be assigned before calling the GetRequestStream() method.
				newStream.Write(bytesToSend, 0, bytesToSend.Length);
				// Close the Stream object.
				newStream.Close();
			} // else GET, no body to send. Other verbs are not supported at the moment.

			WebResponse resp = req.GetResponse();
			Stream respStream = resp.GetResponseStream();
			StreamReader r = new StreamReader(respStream);
			// process SOAP return doc here. For now, we'll just send the XML out to the browser ...
			string output = r.ReadToEnd();
			context.Response.Write(output);
		}

		public bool IsReusable
		{
			get
			{
				return false;
			}
		}
	}
}
Advertisements

Entry filed under: ekus.net, programowanie. Tags: , .

The safest password Started playing with IfThisThenThat.

2 Comments


RSS Unknown Feed

  • An error has occurred; the feed is probably down. Try again later.

RSS Unknown Feed

  • An error has occurred; the feed is probably down. Try again later.

Feeds

del.icio.us/Ekus


%d bloggers like this: