Skip to content

Instantly share code, notes, and snippets.

@anth-3
Last active April 24, 2019 11:18
Show Gist options
  • Save anth-3/6169292 to your computer and use it in GitHub Desktop.
Save anth-3/6169292 to your computer and use it in GitHub Desktop.
Defines a simple pass-through proxy interface for C#.NET (IIS) Server applications and web sites.
<%@ WebHandler Language="C#" Class="PassThruProxy" Debug="true" %>
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Web;
/**
* <summary><c>PassThruProxy</c> is a simple pass-through proxy interface for C#.NET (IIS Servers).</summary>
* <remarks>
* The <c>PassThruProxy</c> class acts as a simple pass-through proxy as it does not have a mechanism for
* filtering a passed URL through a human-moderated list. The pass-through proxy works best on trusted
* networks where only trusted programs would ever access the proxy. For any outward-facing applications with
* a need for a proxy, a more robust solution with URL filtering would be suggested.
* </remarks>
*/
public class PassThruProxy : IHttpHandler {
/**
* <summary>The constructor for the <see cref="PassThruProxy"/> Class.</summary>
*/
public PassThruProxy( ) { }
/**
* <summary>
* <c>ProcessRequest</c> overrides the <see cref="IHttpHandler.ProcessRequest"/> method to process a
* request.
* </summary>
* <remarks>
* The <c>ProcessRequest</c> method enables the processing of HTTP Web requests on the passed
* <see cref="HttpContext"/> so that it can be analyzed, parsed, and processed for the proxy to
* forward the request.
* </remarks>
* <param name="Context">The <see cref="HttpContext"/> that needs to be processed for proxying.</param>
* <seealso cref="IHttpHandler.ProcessRequest"/>
*/
public void ProcessRequest( HttpContext Context ) {
/* Create variables to hold the request and response. */
HttpRequest Request = Context.Request;
HttpResponse Response = Context.Response;
string URI = null;
/* Attempt to get the URI the proxy is to pass along or fail. */
try {
URI = Request.Url.Query.Substring( 1 );
} catch ( Exception Passless ) {
Response.StatusCode = 500;
Response.StatusDescription = "Parameter Missing";
Response.Write( "The parameter that makes this proxy worthwhile and functioning was not given." );
Response.End( );
return;
}
/* Create an HttpWebRequest to send the URI on and process results. */
System.Net.HttpWebRequest ProxyRequest = ( System.Net.HttpWebRequest )System.Net.HttpWebRequest.Create( URI );
/* Set the appropriate values to the request methods. */
ProxyRequest.Method = Request.HttpMethod;
ProxyRequest.ServicePoint.Expect100Continue = false;
ProxyRequest.Referer = Request.Headers[ "referer" ];
/* Set the body of ProxyRequest for POST requests to the proxy. */
if ( Request.InputStream.Length > 0 ) {
/*
* Since we are using the same request method as the original request, and that is
* a POST, the values to send on in the new request must be grabbed from the
* original POSTed request.
*/
byte[ ] Bytes = new byte[ Request.InputStream.Length ];
Request.InputStream.Read( Bytes, 0, ( int )Request.InputStream.Length );
ProxyRequest.ContentLength = Bytes.Length;
string ContentType = Request.ContentType;
if ( String.IsNullOrEmpty( ContentType ) ) {
ProxyRequest.ContentType = "application/x-www-form-urlencoded";
} else {
ProxyRequest.ContentType = ContentType;
}
using ( Stream OutputStream = ProxyRequest.GetRequestStream( ) ) {
OutputStream.Write( Bytes, 0, Bytes.Length );
}
} else {
/*
* When the original request is a GET, things are much easier, as we need only to
* pass the URI we collected earlier which will still have any parameters
* associated with the request attached to it.
*/
ProxyRequest.Method = "GET";
}
System.Net.WebResponse ServerResponse = null;
/* Send the proxy request to the remote server or fail. */
try {
ServerResponse = ProxyRequest.GetResponse( );
} catch ( System.Net.WebException WebEx ) {
Response.StatusCode = 500;
Response.StatusDescription = WebEx.Status.ToString( );
Response.Write( WebEx.Response );
Response.End( );
return;
}
/* Set up the response to the client if there is one to set up. */
if ( ServerResponse != null ) {
Response.ContentType = ServerResponse.ContentType;
using ( Stream ByteStream = ServerResponse.GetResponseStream( ) ) {
/* What is the response type? */
if ( ServerResponse.ContentType.Contains( "text" ) ||
ServerResponse.ContentType.Contains( "json" ) ||
ServerResponse.ContentType.Contains( "xml" ) ) {
/* These "text" types are easy to handle. */
using (StreamReader Reader = new StreamReader( ByteStream ) ) {
string ResponseString = Reader.ReadToEnd( );
/*
* Tell the client not to cache the response since it
* could easily be dynamic, and we do not want to mess
* that up!
*/
Response.CacheControl = "no-cache";
Response.Write( ResponseString );
}
} else {
/*
* Handle binary responses (image, layer file, other binary
* files) differently than text.
*/
BinaryReader BinReader = new BinaryReader( ByteStream );
byte[] BinaryOutputs = BinReader.ReadBytes( ( int )ServerResponse.ContentLength );
BinReader.Close( );
/*
* Tell the client not to cache the response since it could
* easily be dynamic, and we do not want to mess that up!
*/
Response.CacheControl = "no-cache";
/*
* Send the binary response to the client.
* (Note: if large images/files are sent, we could modify this to
* send back in chunks instead...something to think about for
* future.)
*/
Response.OutputStream.Write( BinaryOutputs, 0, BinaryOutputs.Length );
}
ServerResponse.Close( );
}
}
Response.End( );
}
/**
* <summary>
* <c>IsReusable</c> overrides the <see cref="IHttpHandler.IsReusable"/> property to indicate if
* another request can use this instance.
* </summary>
* <remarks>
* <c>IsReusable</c> gets a value indicating whether or not another request can use the
* <see cref="IHttpHandler"/> instance.
* </remarks>
*/
public bool IsReusable {
get { return false; }
}
}
@anth-3
Copy link
Author

anth-3 commented Jul 6, 2016

This is Public Domain.

@tuxfamily
Copy link

Hi, thanks for sharing this. I just want to point that,
byte[] BinaryOutputs = BinReader.ReadBytes( ( int )ServerResponse.ContentLength );
can raise an exception if ServerResponse.ContentLength is < 1, so I think it's best to make it conditional, like this

else if ((int)ServerResponse.ContentLength > 0)
{                        
    BinaryReader BinReader = new BinaryReader(ByteStream);
    byte[] BinaryOutputs = BinReader.ReadBytes((int)ServerResponse.ContentLength);
    BinReader.Close();
    Response.CacheControl = "no-cache";
    Response.OutputStream.Write(BinaryOutputs, 0, BinaryOutputs.Length);
}
else
{
    // return empty content (if you prefer, you can return a status code 204)
    string ResponseString = "";
    Response.CacheControl = "no-cache";
    Response.Write(ResponseString);
}

@cheeseweasel
Copy link

@tuxfamily, you cannot trust ContentLength, a more reliable approach might be to read the response stream into memory first:

var resultStream = new MemoryStream();
using (var respStream = ServerResponse.GetResponseStream())
{
    respStream.CopyTo(resultStream);
}
var BinaryOutputs = resultStream.ToArray();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment