44

I have a web application that communicates between two different web applications (one receiver and one sender, the sender communicates with my application, and my application communicates with both).

A regular scenario is that the sender sends a HttpRequest to my application, and I receive it in an HttpHandler. This in turn sends the HttpContext to some businesslogic to do some plumbing.

After my business classes are finished storing data (some logging etc), I want to relay the same request with all the headers, form data etc to the receiver application. This must be sent from the class, and not the HttpHandler.

The question is really - how can I take a HttpContext object, and forward/relay the exact same request only modifying the URL from http://myserver.com/ to http://receiver.com.

Any code examples in preferable c# would be great!

John Sheehan
  • 77,456
  • 30
  • 160
  • 194
El Che
  • 1,301
  • 3
  • 15
  • 33

5 Answers5

46

I have an extension method on HttpResponseBase to copy an incoming request to an outgoing request.

Usage:

    var externalRequest = (HttpWebRequest)WebRequest.Create("http://stackoverflow.com");
    this.Request.CopyTo(externalRequest);
    var externalResponse = (HttpWebResponse)externalRequest.GetResponse();

Source:

/// <summary>
/// Copies all headers and content (except the URL) from an incoming to an outgoing
/// request.
/// </summary>
/// <param name="source">The request to copy from</param>
/// <param name="destination">The request to copy to</param>
public static void CopyTo(this HttpRequestBase source, HttpWebRequest destination)
{
    source.InputStream.Position = 0;
    destination.Method = source.HttpMethod;

    // Copy unrestricted headers (including cookies, if any)
    foreach (var headerKey in source.Headers.AllKeys)
    {
        switch (headerKey)
        {
            case "Connection":
            case "Content-Length":
            case "Date":
            case "Expect":
            case "Host":
            case "If-Modified-Since":
            case "Range":
            case "Transfer-Encoding":
            case "Proxy-Connection":
                // Let IIS handle these
                break;

            case "Accept":
            case "Content-Type":
            case "Referer":
            case "User-Agent":
                // Restricted - copied below
                break;

            default:
                destination.Headers[headerKey] = source.Headers[headerKey];
                break;
        }
    }

    // Copy restricted headers
    if (source.AcceptTypes.Any())
    {
        destination.Accept = string.Join(",", source.AcceptTypes);
    }
    destination.ContentType = source.ContentType;
    destination.Referer = source.UrlReferrer.AbsoluteUri;
    destination.UserAgent = source.UserAgent;

    // Copy content (if content body is allowed)
    if (source.HttpMethod != "GET"
        && source.HttpMethod != "HEAD"
        && source.ContentLength > 0)
    {
        var destinationStream = destination.GetRequestStream();
        source.InputStream.CopyTo(destinationStream);
        destinationStream.Close();
    }
}
Romias
  • 13,783
  • 7
  • 56
  • 85
diachedelic
  • 2,195
  • 1
  • 24
  • 28
  • 5
    source.InputStream.Position = 0; is needed before source.InputStream.CopyTo – bfi Oct 20 '14 at 12:52
  • 9
    Just a small change, for the Headers part is safer and more complete this way: **foreach (var headerKey in source.Headers.AllKeys) { if (!WebHeaderCollection.IsRestricted(headerKey)) { target.Headers[headerKey] = source.Headers[headerKey]; } }** – Nuno Rodrigues Jun 11 '15 at 10:37
  • Coping a cookie using the headers like you suggest does not includes the cookie in the forwarded request. – johni Aug 15 '16 at 08:48
  • This code is not compatible with ASP.NET Core/.NET 6. HttpRequestBase is part of the System.Web namespace, System.Web is not available in ASP.NET CORE/.Net 6. – Jacob Apr 07 '23 at 08:10
28

Actually, something like this worked well

HttpRequest original = context.Request;
HttpWebRequest newRequest = (HttpWebRequest)WebRequest.Create(newUrl);

newRequest .ContentType = original.ContentType;
newRequest .Method = original.HttpMethod;
newRequest .UserAgent = original.UserAgent;

byte[] originalStream = ReadToByteArray(original.InputStream, 1024);

Stream reqStream = newRequest .GetRequestStream();
reqStream.Write(originalStream, 0, originalStream.Length);
reqStream.Close();


newRequest .GetResponse();

edit: ReadToByteArray method just makes a byte array from the stream

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
El Che
  • 1,301
  • 3
  • 15
  • 33
4

Here's some good relay code in VB.NET using MVC.

GLOBAL.ASAX.VB

Public Class MvcApplication
    Inherits System.Web.HttpApplication

    Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
        routes.MapRoute("Default", "{*s}", New With {.controller = "Home", .action = "Index"})
    End Sub

    Sub Application_Start()
        RegisterRoutes(RouteTable.Routes)
    End Sub
End Class

HomeController.vb

Option Explicit On
Option Strict On

Imports System.Net

<HandleError()> _
Public Class HomeController
    Inherits System.Web.Mvc.Controller

    Function Index(ByVal s As String) As ActionResult
        Server.ScriptTimeout = 60 * 60
        If Request.QueryString.ToString <> "" Then s = s + "?" + Request.QueryString.ToString
        Dim req As HttpWebRequest = CType(WebRequest.Create("http://stackoverflow.com/" + s), HttpWebRequest)
        req.AllowAutoRedirect = False
        req.Method = Request.HttpMethod
        req.Accept = Request.Headers("Accept")
        req.Referer = Request.Headers("Referer")
        req.UserAgent = Request.UserAgent
        For Each h In Request.Headers.AllKeys
            If Not (New String() {"Connection", "Accept", "Host", "User-Agent", "Referer"}).Contains(h) Then
                req.Headers.Add(h, Request.Headers(h))
            End If
        Next
        If Request.HttpMethod <> "GET" Then
            Using st = req.GetRequestStream
                StreamCopy(Request.InputStream, st)
            End Using
        End If
        Dim resp As WebResponse = Nothing
        Try
            Try
                resp = req.GetResponse()
            Catch ex As WebException
                resp = ex.Response
            End Try

            If resp IsNot Nothing Then
                Response.StatusCode = CType(resp, HttpWebResponse).StatusCode
                For Each h In resp.Headers.AllKeys
                    If Not (New String() {"Content-Type"}).Contains(h) Then
                        Response.AddHeader(h, resp.Headers(h))
                    End If
                Next
                Response.ContentType = resp.ContentType

                Using st = resp.GetResponseStream
                    StreamCopy(st, Response.OutputStream)
                End Using
            End If
        Finally
            If resp IsNot Nothing Then resp.Close()
        End Try
        Return Nothing
    End Function
    Sub StreamCopy(ByVal input As IO.Stream, ByVal output As IO.Stream)
        Dim buf(0 To 16383) As Byte
        Using br = New IO.BinaryReader(input)
            Using bw = New IO.BinaryWriter(output)
                Do
                    Dim rb = br.Read(buf, 0, buf.Length)
                    If rb = 0 Then Exit Do
                    bw.Write(buf, 0, rb)
                Loop
            End Using
        End Using
    End Sub
End Class
Hafthor
  • 16,358
  • 9
  • 56
  • 65
1

HttpContext includes the Request property, which in turn contains the Headers collection. It should be all the information you need.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • 1
    HttpWebRequest relayingRequest = (HttpWebRequest)WebRequest.Create(relayUrl); relayingRequest.Headers.Add(context.Request.Headers); This does not work - I also need params, httpmethod etc. The request should be identical... – El Che Mar 30 '09 at 13:53
  • It's all there, in HttpContext.Request, almost "by definition". Consider that this is how a page or other handler knows what to do. There's a QueryString and Form property to Context.Request as well. Look up the properties, and you'll see. – John Saunders Mar 30 '09 at 14:26
  • Yeah I know they're all there, but remember that context.Request is not a System.Net.HttpWebRequest, but a System.Web.HttpRequest. I'm not sure it it's as easy as just looking up the props and getting them into the new request. – El Che Mar 30 '09 at 14:46
  • 1
    I _am_ sure. Why are you looking for something more complicated? Take a look, try it out, and see. – John Saunders Mar 30 '09 at 17:40
  • I'll check more tomorrow. It seems I need to read the inputstream from the original request and write it to the new one. Also I need to copy some of the properties like you say. Weird that there are no method to duplicate a request without this much hassle. – El Che Mar 30 '09 at 19:14
  • It's not something people have to do very often. This is something more often done at a lower level of the network infrastructure. – John Saunders Mar 30 '09 at 19:31
-1

possiblly something like:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("www.testing.test");
request.Headers = (WebHeaderCollection)Request.Headers;

Then call the get response

HttpWebResponse response  = (HttpWebResponse)request.GetResponse();

This will have the same HTTP headers as the original request.

dkarzon
  • 7,868
  • 9
  • 48
  • 61
  • 4
    Unable to cast object of type 'System.Web.HttpHeaderCollection' to type 'System.Net.WebHeaderCollection'. – El Che Mar 31 '09 at 06:34