Simple web proxy in ASP.NET

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;
			}
		}
	}
}
Advertisement

Pracowity dzień z Subversion i PowerShellem

Ostatnio zainstalowałem lokalne repozytorium Subversion i wczekinowałem solucję nad którą ostatnio pracuję. Niestety, okazało się, że oprócz wartościowych plików VB/C# znalazły się tam śmieci typu bin/*.* (głównie dll-ki). Szybko naprawiłem błąd i zacząłem od nowa, tym razem ustawiając w TortoiseSvn ignorowanie plików dll. Wszystko było OK do czasu, aż zacząłem pracować z kodem – okazało się, że warto byłoby wyrzucić z repozytorium także katalogi obj/Debug/ – oprócz dll-ek znajdują się tam np. pliki .projdata, które Visual Studio chce lockować, co nie bardzo podoba się żółwikowi (Tortoise).

Niestety, w międzyczasie mam wprowadzone sporo zmian i nie chcę tworzyć repozytorium od nowa (w końcu po coś ono istnieje ;-)

Aby repozytorium było bez skazy, zacząłem mozolnie usuwać z niego niechciane foldery i pliki. Niestety, obecna solucja zawiera około 50 takich zestawów w poszczególnych podprojektach. Zamiast poświęcić pół godziny na wyszukiwanie i usuwanie tych śmieci, postanowiłem zaprząc do pracy…

PowerShell.

Czytałem swego czasu o projekcie nowego shella (command line’a) Microsoftu, pierwotnie nazywanego Monad. Przemianowany na PowerShell i wydany oficjalnie pod koniec 2006 coraz częściej przewija się na blogach i webcastach. Jest podobny do powłok *niksowych, duży nacisk położono w nim na przekazywanie wyników jednego polecenia do następnego (piping), przy czym ma jedną bardzo istotną zaletę – zamiast stringów przekazuje obiekty. To się wiąże z kolejną zaletą: działa na .necie i w pełni korzysta z jego dobrodziejstw.

W rezultacie dostajemy nowoczesny sposób dostępu do bardzo różnych struktur danych (system plików, rejestr, otoczenie sieciowe, różnorakie API programów…). Microsoft zobowiązał się publikować wszystkie nowe narzędzie administracyjne w formie nakładek GUI na właśnie skrypty i klasy (tzw. cmdlety)PowerShella. Tak właśnie wygląda panel administracyjny najnowszego MS Exchange Servera.

Kilka przykładów z wikipedii:

  • Stop all processes that begin with the letter “p”:
 PS> get-process p* | stop-process
  • Find the processes that use more than 1000 MB of memory and kill them:
 PS> get-process | where { $_.WS -gt 1000MB } | stop-process
  • Calculate the number of bytes in the files in a directory:
 PS> get-childitem | measure-object -property length -sum

Get-ChildItem .\ -include bin,obj -Recurse | foreach ($_) { remove-item $_.fullname -Force -Recurse }

O ile dobrze rozumiem (dopiero zaczynam się tym bawić), to teoretycznie używając składni z ostatniego przykładu i podając inne dane wejściowe, możemy np. usunąć wszystkie klucze rejestru lub np. userów z grupy. Raj dla adminów/skrypciarzy/hackerów :-)

Wracając do mojego Subversiona…

Po kilku godzinach :-D udało mi się usiągnąć cel. Znajduję wszystkie niechciane katalogi na dysku i na ich podstawie uzyskuję bliźniaczą listę takich katalogów, tyle że w Subversion. Pozostało tylko wykasować je z repozytorium, co uczyniłem z poczuciem dobrze spełnionego obowiązku. Co prawda w tym przypadku to była armata na muchy, ale za to mam zainstalowanego PowerShella, napisane pierwsze skrypty w nowym środowisku z użyciem pajpingu i iterowania, poustawiane kilka rzeczy w systemie na przyszłość no i …czyste repozytorium :-]

Get-ChildItem Src\ -include bin,obj -Recurse | foreach { 

 $fullPath = $_.FullName.Replace("\","/")
 $srcPath = (get-location).Path.Replace("\","/")
 $repoPath = "file:///c:/Repo/NexTra"
 $fullPath = $fullPath.Replace("$srcPath","$repoPath")
 svn delete --message "AutoDelete" --force --non-interactive "$fullPath"
}

Jak unikalny jest GUID?

Bardzo interesujący artykulik o GUIDach, czyli “z baardzo dużym prawdopodobieństwiem unikalnych” 16-bajtowych liczbach.
Podane wielkości dotyczą także numerów IPv6.

http://betterexplained.com/articles/the-quick-guide-to-guids/
“It seems we can squeeze over 600,000 GUIDs or IPv6 addresses per Earth’s square nanometer: http://tinyurl.com/2dvw4q

Przy okazji cała strona jest godna polecenia (teoria Bayesa a filtrowanie spamu, zasada Pareto, a nawet tak abstrakcyjne tematy jak.. cache’owanie stron www ;-)

Warto luknąć też na ich obliczeniowy silnik – InstaCalc (jest użyty w ilustrowaniu przykładów) – taki kalkulatoroexcelik w wersji web 2.0.