locked
Creating an HTTP Proxy With Handler RRS feed

  • Question

  • User-1073743741 posted

    I've got an HTTP handler (just a *.ashx file - not declared in web.config) that serves up images at the requested size. As far as I can tell, this part is working flawlessly. Now, in my application, I have a protected directory that I want to be able to protect some images in as well. So, in order to keep the image handler generic and able to be used from anywhere in the site, I thought I'd make a handler file within this protected directory to act as a proxy to get to the images. This works, but is having problems with serving up all the requests. There are many images on a single page and some of them won't be served up by the proxy image handler. Logging errors on the proxy handler shows the following error for each image not served:

    "A first chance exception of type 'System.Net.WebException' occurred in System.dll
    The remote server returned an error: (403) Forbidden."

    I have a feeling that it has something to do with not closing or disposing of things properly in the handler, but I've gone through a couple of different versions now with no difference. Here's the .NET 2.0 code for my proxy image handler (directly in an ASHX file):

      

    1    <%@ WebHandler Language="C#" Class="ClientGetStreamProxy" %>
    2
    3 using System;
    4 using System.Web;
    5 using System.Net;
    6 using System.IO;
    7
    8 public class ClientGetStreamProxy : IHttpHandler {
    9
    10 public bool IsReusable {
    11 get {
    12 return false;
    13 }
    14 }
    15
    16 public void ProcessRequest(HttpContext context) {
    17 try {
    18 HttpContext c = HttpContext.Current;
    19 string imgPath = Utilities.Cryptography.DecryptString(c.Request.QueryString["p"]);
    20 string imgWidth = c.Request.QueryString["w"];
    21
    22 string actualUrl = "~/GetStream.ashx?path=" + imgPath + "&w=" + imgWidth;
    23
    24 WebRequest req = WebRequest.Create(Tower29.Utilities.Utility.GetFullUrl(actualUrl, false, false));
    25 WebResponse res = req.GetResponse();
    26 Stream resStream = res.GetResponseStream();
    27
    28 MemoryStream ms = new MemoryStream();
    29 byte[] buffer = new byte[2048];
    30
    31 int bytesRead = 0;
    32
    33 do {
    34 bytesRead = resStream.Read(buffer, 0, buffer.Length);
    35 ms.Write(buffer, 0, bytesRead);
    36 }
    37 while (bytesRead != 0);
    38
    39 resStream.Close();
    40 res.Close();
    41
    42 byte[] data = ms.ToArray();
    43 ms.Close();
    44
    45 c.Response.Expires = -1;
    46 c.Response.AppendHeader("Pragma", "no-cache");
    47 c.Response.ContentType = res.Headers["content-type"];
    48 c.Response.OutputStream.Write(data, 0, data.Length);
    49
    50 c.Response.OutputStream.Close();
    51 }
    52 catch (Exception e) {
    53 System.Diagnostics.Debug.WriteLine(e.Message);
    54 }
    55 }
    56
    57 }
     
    Thursday, March 5, 2009 10:44 AM

All replies

  • User-1073743741 posted

     Since that error actually seems related to the image handler, here's that code:

      

    1    <%@ WebHandler Language="VB" Class="GetStream" %>
    2
    3 Imports CompanyDLL.Common
    4 Imports CompanyDLL.Common.Interfaces
    5 Imports CompanyDLL.Common.Managers
    6 Imports CompanyDLL.Utilities.Cryptography
    7 Imports CompanyDLL.Utilities.Utility
    8
    9 Imports System.IO
    10 Imports System.Reflection
    11 Imports System.Reflection.MethodInfo
    12
    13 Public NotInheritable Class GetStream : Implements IHttpHandler
    14
    15 #Region "1. Properties "
    16
    17 Private Enum RequestType As Integer
    18 Unknown = -1
    19 [Assembly] = 1
    20 FileSystem = 2
    21 End Enum
    22
    23 Private
    _c As HttpContext
    24
    25 Private _requestType As RequestType = RequestType.Unknown
    26 Private _inputStream As MemoryStream
    27
    28 Private _isImage As Boolean = False
    29 Private Const
    _minimumSize As Int32 = 125
    30 Private _requestedWidth As Int32 = _minimumSize * 4
    31 Private _requestedHeight As Int32 = _minimumSize * 4
    32
    33 Private _contentType As String = "application/x-download"
    34 Private _filename As String
    35
    36 #End Region
    37
    38 #Region "1. Request "

    39
    40 Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
    41 Get
    42 Return False
    43 End Get
    44 End Property
    45
    46 Public Sub
    ProcessRequest(ByVal c As HttpContext) Implements IHttpHandler.ProcessRequest
    47
    48 _c = c
    49
    50 GetRequestType()
    51 GetDataByRequest()
    52 If _isImage Then
    53 GetRequestedSize()
    54 SendStream(GetImage())
    55 Else
    56 SendStream(_inputStream)
    57 End If
    58
    59 End Sub
    60
    61 Private Sub
    GetRequestType()
    62
    63 If _requestType = RequestType.Unknown Then
    64 If Not
    _c.Request.QueryString("path") Is Nothing Then
    65 _requestType = RequestType.FileSystem
    66 ElseIf Not _c.Request.QueryString("asm") Is Nothing Then
    67 _requestType = RequestType.Assembly
    68 Else
    69 Throw New
    Exception("Unknown request type")
    70 End If
    71 Else
    72 Throw New
    Exception("RequestType already specified: " & _requestType.ToString)
    73 End If
    74
    75 End Sub
    76
    77 Private Sub
    GetRequestedSize()
    78
    79 Dim paramSize As String = "0"
    80 Dim paramWidth As String = "0"
    81 Dim paramHeight As String = "0"
    82
    83 If Not _c.Request.QueryString("size") Is Nothing Then
    84 paramSize = _c.Request.QueryString("size")
    85 ElseIf Not _c.Request.QueryString("sz") Is Nothing Then
    86 paramSize = _c.Request.QueryString("sz")
    87 ElseIf Not _c.Request.QueryString("s") Is Nothing Then
    88 paramSize = _c.Request.QueryString("s")
    89 End If
    90
    91 If
    (paramSize > 0) Then
    92 paramWidth = paramSize
    93 paramHeight = paramSize
    94
    95 Else
    96 If
    (Not _c.Request.QueryString("w") Is Nothing) Then
    97 paramWidth = _c.Request.QueryString("w")
    98 ElseIf (Not _c.Request.QueryString("width") Is Nothing) Then
    99 paramWidth = _c.Request.QueryString("width")
    100 End If
    101
    102 If
    (Not _c.Request.QueryString("h") Is Nothing) Then
    103 paramHeight = _c.Request.QueryString("h")
    104 ElseIf (Not _c.Request.QueryString("height") Is Nothing) Then
    105 paramHeight = _c.Request.QueryString("height")
    106 End If
    107
    108 End If

    109
    110 ParseSizeRequest(paramWidth, paramHeight)
    111
    112 End Sub
    113
    114 #End Region
    115
    116 #Region "1. LoadFrom "

    117
    118 Private Sub GetDataByRequest()
    119
    120 Select Case _requestType
    121 Case RequestType.Assembly
    122 LoadFromAssembly(_c.Request.QueryString("asm"))
    123 Case RequestType.FileSystem
    124 LoadFromFileSystem(_c.Request.QueryString("path"))
    125 Case Else
    126 Throw New
    ArgumentException("Cannot get by " & _requestType.ToString)
    127 End Select
    128
    129 End Sub
    130
    131 Private Sub
    LoadFromAssembly(ByVal theAssembly As String)
    132
    133 Dim assemblyQuery As String
    134 Try

    135 assemblyQuery = DecryptString(_c.Request.QueryString("asm")) 'this had BETTER be encrypted...
    136 Catch ex As Exception
    137 Throw ex
    138 End Try
    139
    140 Dim
    TargetAssembly As [Assembly] = [Assembly].LoadFrom(_c.Server.MapPath(GetFullUrl(assemblyQuery.Split("&"c)(0))))
    141 Dim si As StreamInterface = DirectCast(TargetAssembly.CreateInstance(assemblyQuery.Split("&"c)(1), True), StreamInterface)
    142 Dim buffer(Convert.ToInt32(si.OutputStream.Length) - 1) As Byte
    143 si.OutputStream.Read(buffer, 0, buffer.GetLength(0))
    144 si.OutputStream.Close()
    145 _inputStream = New MemoryStream(buffer)
    146
    147 End Sub
    148
    149 Private Sub
    LoadFromFileSystem(ByVal thePath As String)
    150
    151 Dim requestedPath As String
    152 Try

    153 requestedPath = DecryptString(thePath)
    154 Catch ex As Exception
    155 Select Case GenerateErrorCode(ex)
    156 Case 36598 'Cryptography.DecryptString->Invalid character in a Base-64 string
    157 'This error is likely caused by attempting to decrypt a non-encrypted string
    158 ' Non-encrypted strings passed to GetStream generally signify a plain text file path request

    159 requestedPath = thePath
    160 Case Else
    161 'Any other error needs to be thrown
    162 Throw ex
    163 End Select
    164 End Try
    165
    166 Dim
    requestedFpi As New FilePathManager(Path.GetDirectoryName(requestedPath).Replace("\"c, "/"c))
    167 requestedFpi.Filename = Path.GetFileName(requestedPath).Replace("\"c, "/"c)
    168
    169 Select Case requestedFpi.Extension.ToUpper
    170 Case "JPG", "JPEG", "GIF", "PNG", "PDF"
    171 'Do nothing
    172 Case Else
    173 _c.Response.Redirect("~/Error.aspx?s=404")
    174 Exit Sub
    175 End Select

    176
    177 _filename = requestedFpi.Filename
    178 Dim requestedFi As New FileInfo(requestedFpi.SystemFilePath)
    179 If requestedFi.Exists Then
    180 Dim
    requestedFs As FileStream = Nothing
    181 Try

    182 requestedFs = requestedFi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
    183 Dim buffer(Convert.ToInt32(requestedFs.Length - 1)) As Byte
    184 requestedFs.Read(buffer, 0, buffer.GetLength(0))
    185 _inputStream = New MemoryStream(buffer)
    186 Catch ex As Exception
    187 Throw ex
    188 Finally
    189 If
    requestedFs.CanRead Or requestedFs.CanWrite Then
    190 requestedFs.Close()
    191 End If
    192 End Try
    193 Else
    194 Throw New
    FileNotFoundException("Requested file not found: " & _c.Request.QueryString("path"))
    195 End If
    196
    197 Select Case
    requestedFpi.Extension.ToUpper
    198 Case "JPG", "JPEG", "GIF", "PNG"
    199 _contentType = "image/jpeg"
    200 _isImage = True
    201 Case
    "PDF"
    202 _contentType = "application/pdf"
    203 End Select
    204
    205 End Sub
    206
    207 #End Region
    208
    209 #Region "1. Image Processing "

    210
    211 Private Sub ParseSizeRequest(ByVal paramWidth As String, ByVal paramHeight As String)
    212
    213 Select Case IsNumeric(paramWidth)
    214 Case True
    215 _requestedWidth = Convert.ToInt32(paramWidth)
    216 _requestedHeight = Convert.ToInt32(paramHeight)
    217 Case False
    218 Select Case
    paramWidth.ToUpper
    219 Case "T", "TH", "THUMB", "THUMBNAIL", "TI", "TINY"
    220 _requestedWidth = Convert.ToInt32(_minimumSize / 2)
    221 _requestedHeight = Convert.ToInt32(_minimumSize / 2)
    222 Case "S", "SM", "SML", "SMALL"
    223 _requestedWidth = _minimumSize
    224 _requestedHeight = _minimumSize
    225 Case "M", "MD", "MED", "MEDIUM"
    226 _requestedWidth = _minimumSize * 2
    227 _requestedHeight = _minimumSize * 2
    228 Case "L", "LG", "LRG", "LARGE"
    229 _requestedWidth = _minimumSize * 3
    230 _requestedHeight = _minimumSize * 2
    231 Case "H", "HU", "HUGE"
    232 _requestedWidth = _minimumSize * 4
    233 _requestedHeight = _minimumSize * 4
    234 End Select
    235 End Select
    236
    237 End Sub
    238
    239 Private Function
    GetImage() As MemoryStream
    240
    241 Dim it As New ImageTransformManager(_inputStream)
    242 Return it.GetAsMemoryStream(_requestedWidth, _requestedHeight)
    243
    244 End Function
    245
    246 #End Region
    247
    248 #Region "1. Response "

    249
    250 Private Overloads Sub SendStream(ByVal OutputStream As System.IO.MemoryStream)
    251
    252 Dim ForceSaveAs As Boolean = True
    253 Dim
    OutputBuffer As Byte() = OutputStream.ToArray
    254
    255 If _contentType <> "application/x-download" Then
    256 ForceSaveAs = False
    257 End If
    258
    259 If
    _requestType = RequestType.FileSystem Then
    260 Dim
    sb As New System.Text.StringBuilder
    261
    262 If ForceSaveAs = True Then
    263 sb.Append("attachment; ")
    264 End If
    265
    266 sb.Append("filename=""")
    267 sb.Append(_filename)
    268 sb.Append(""";")
    269
    270 _c.Response.Expires = -1
    271 _c.Response.AppendHeader("Pragma", "no-cache")
    272 _c.Response.AppendHeader("Content-Disposition", sb.ToString)
    273 _c.Response.ContentType = _contentType
    274 End If
    275
    276 _c.Response.OutputStream.Write(OutputBuffer, 0, OutputBuffer.Length)
    277 OutputStream.Close()
    278 _inputStream.Close()
    279
    280 _c.Response.End()
    281
    282 End Sub
    283
    284 #End Region
    285
    286 End Class

    287
     
    Thursday, March 5, 2009 10:56 AM
  • User-1073743741 posted

     *bump*

    Monday, March 9, 2009 3:30 PM
  • User624841477 posted

     Hi,

    I have two questions..

    1. let me know what url you are passing in querystring  i.e. 

    string actualUrl = "~/GetStream.ashx?path=" + imgPath + "&w=" + imgWidth;  
    an example would be better to understand.

     2.Which images are not serverd by the proxy image handler i.e all the images from protected Directory.

     

    Regards,

    sunil

    Friday, March 13, 2009 6:37 AM
  • User-1073743741 posted

    Thanks for the reply.

    Well, the most important bit of information is probably that it doesn't display certain images at random. I'm using it to display a page of thumbnails and if I hit refresh repeatedly it will be different images that don't get served up each time. I'm beginning to suspect that it's too many requests going to my local server (you know, the "too many users are connected" error). I've been using my local IIS server (on XP), but I checked it out on the Cassini server and it doesn't seem to be occurring there. At this point I think it's the amount of requests going to the server causing the "too many users" error. I'll have to weigh this possible performance issue against the way I have it coded now. It might be better to move all of the functionality into a common library so I could place copies of an image HTTP handler (with minimal code) wherever I like.

    Here's a querystring example anyway:

    http://localhost/SiteName/ProtectedDirectory/GetStream.ashx?p=~/Images/Image1.jpg&w=100
     
    Friday, March 13, 2009 10:20 AM