在.NET HttpHandler中检测字节范围请求

时间:2010-12-01 23:01:30

标签: .net http httphandler

我有一个HttpHandler,它会对传入的请求进行一些检查,并在某些情况下执行某些功能。需要检查的条件之一是请求是否为byte-range request。这是怎么做到的?

3 个答案:

答案 0 :(得分:4)

您需要在Range对象中查找Request标头,该标头是传递给HttpContext方法的ProcessRequest的一部分。 Range课程中没有HttpRequest属性,因此您必须查看Headers。如果有Range,则其格式为:

Range: bytes=<start>-<end>

<start><end>是整数。例如,如果有人想从文件中间找到64K:

Range: bytes=32768-98304

您必须将文本解析为数字并进行相应处理。

答案 1 :(得分:3)

请注意,Range标头语法还允许使用“0-500,100-1500”(多个范围)和“-500”(最后500个字节)之类的内容。有关血腥的详细信息,请参阅RFC 2616,这些内容太长,无法引用。

答案 2 :(得分:0)

基于@ brent-keller评论中上面链接的博客帖子 - 后者又引用CodePlex entry - 我想出了下面的编辑。它使用FDM进行测试(可用here)。 (尚未)支持MultiRange请求。不需要Web.config条目。

CodePlex的原始方法包含错误 - Accept-Ranges标头的值应为bytes,而不是要返回的字节范围。这属于Content-Range标题。下载仍然有效,但如果你犯了错误,你就不会得到byte serving

为了简洁和可读性,对此修改版本进行了重构。它还具有以下优点:返回的文件不一定与实际URL相关联 - 事实上,如果需要,可以直接从浏览器调用处理程序并使用查询字符串参数。这样可以动态创建文件/数据和响应。

希望有人能用它做点好事。

HTTP处理程序

Public Class Upload
  Implements IHttpHandler

  Public Sub ProcessRequest(Context As HttpContext) Implements IHttpHandler.ProcessRequest
    Dim oFile As FileInfo

    oFile = New FileInfo(Context.Server.MapPath("~/0HCJ0LE.zip"))

    Me.UploadRange(Context, oFile)
  End Sub



  Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
    Get
      Return False
    End Get
  End Property



  Private Sub UploadRange(Context As HttpContext, File As FileInfo)
    Dim oResponse As Response
    Dim oRequest As Request

    Dim _
      nOffset,
      nLength As Long

    Using oReader As New StreamReader(File.FullName)
      Context.Response.AddHeader("Accept-Ranges", "bytes")

      oResponse = New Response(oReader)
      oRequest = New Request(oResponse, Context)

      If oRequest.HasRange Then
        If oRequest.IsMultiRange Then
          ' At the moment we only support single ranges.'
          '         * Multiple range support requires some more work'
          '         * to comply with the specifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2'
          '         *'
          '         * Multirange content must be sent with multipart/byteranges mediatype,'
          '         * (mediatype = mimetype)'
          '         * as well as a boundary header to indicate the various chunks of data.'
          ' '        
          ' (?) Shoud this be issued here, or should the first'
          ' range be used? Or should the header be ignored and'
          ' we output the whole content?'
          Me.ThrowBadRange(Context, oResponse)
        Else
          If oRequest.IsBadRange Then
            Me.ThrowBadRange(Context, oResponse)
          Else
            Context.Response.StatusCode = 206

            oResponse.Start = oRequest.Start
            oResponse.End = oRequest.End

            nOffset = oReader.BaseStream.Seek(oResponse.Start, SeekOrigin.Begin)
            nLength = oResponse.End - oResponse.Start + 1
          End If
        End If
      Else
        nOffset = 0
        nLength = oResponse.Size
      End If
    End Using

    Context.Response.ContentType = MediaTypeNames.Application.Zip
    Context.Response.AddHeader("Content-Disposition", $"attachment; filename={File.Name}")
    Context.Response.AddHeader("Content-Length", nLength)
    Context.Response.AddHeader(oResponse.HeaderName, oResponse.HeaderValue)
    Context.Response.WriteFile(File.FullName, nOffset, nLength)
    Context.Response.End()
  End Sub



  Private Sub ThrowBadRange(Context As HttpContext, Response As Response)
    Context.Response.AddHeader(Response.HeaderName, Response.HeaderValue)
    Throw New HttpException(416, "Requested range not satisfiable")
  End Sub
End Class

范围请求

Friend NotInheritable Class Request
  Public Sub New(Response As Response, Context As HttpContext)
    Me.Response = Response
    Me.Context = Context
  End Sub



  Public ReadOnly Property Start As Long
    Get
      If Me.Range(0) = String.Empty Then
        Start = Me.Response.Size - Me.Range(1)
      Else
        Start = Me.Range(0)
      End If
    End Get
  End Property



  Public ReadOnly Property [End] As Long
    Get
      If Me.Range(0) = String.Empty Then
        [End] = Me.Response.End
      Else
        If Long.TryParse(Me.Range(1), 0) Then
          [End] = Me.Range(1)
        Else
          [End] = Me.Response.Size
        End If
      End If

      [End] = Math.Min(Me.Response.End, [End])
    End Get
  End Property



  Public ReadOnly Property HasRange As Boolean
    Get
      Return String.IsNullOrEmpty(Me.Context.Request.ServerVariables(HTTP_RANGE)) = False
    End Get
  End Property



  Public ReadOnly Property IsMultiRange As Boolean
    Get
      Return Me.Context.Request.ServerVariables(HTTP_RANGE).Contains(",")
    End Get
  End Property



  Public ReadOnly Property IsBadRange As Boolean
    Get
      Return Me.Start > Me.End OrElse Me.Start > Me.Response.Size - 1 OrElse Me.End >= Me.Response.Size
    End Get
  End Property



  Private ReadOnly Property Range As List(Of String)
    Get
      Return Me.Context.Request.ServerVariables(HTTP_RANGE).Split("=")(1).Split("-").ToList
    End Get
  End Property



  Private ReadOnly Response As Response
  Private ReadOnly Context As HttpContext

  Private Const HTTP_RANGE As String = "HTTP_RANGE"
End Class

范围响应

Friend NotInheritable Class Response
  Public Sub New(Reader As StreamReader)
    _Size = Reader.BaseStream.Length
    Me.End = Me.Size - 1
  End Sub



  Public Property Start As Long
  Public Property [End] As Long
  Public ReadOnly Property Size As Long



  Public ReadOnly Property HeaderName As String
    Get
      Return "Content-Range"
    End Get
  End Property



  Public ReadOnly Property HeaderValue() As String
    Get
      Return $"bytes {Me.Start}-{Me.End}/{Me.Size}"
    End Get
  End Property
End Class