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; }
}
}
@joshhendo
Copy link

Hi, what is the license on this file?

@ca0v
Copy link

ca0v commented Feb 22, 2016

I think this proxy is designed to work in the same domain as the application server, but I'd like a pass-through proxy on a trusted domain that is different from the application. I'd like to tell the proxy which application servers can access the proxy. Any thoughts on what would need to change (if anything) to make this proxy work like that?

@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