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

发布于 2024-10-05 07:29:54 字数 155 浏览 1 评论 0原文

我有一个 HttpHandler ,它将对传入的请求进行一些检查,并在某些情况下执行某些功能。需要检查的条件之一是请求是否是字节范围请求。这是怎么做到的?

I have a HttpHandler which will do some checks on the incoming requests and in certain cases perform some function. One of the conditions that needs to be checked is whether the request is a byte-range request. How is this done?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

不知在何时 2024-10-12 07:29:54

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

Range: bytes=-

其中 是整数。例如,如果有人想要从文件中间获取 64K:

Range: bytes=32768-98304

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

You need to look for a Range header in the Request object that's part of the HttpContext passed to your ProcessRequest method. There is no Range property in the HttpRequest class, so you'll have to look in the Headers. If there is a Range it will be of the form:

Range: bytes=<start>-<end>

Where <start> and <end> are integers. For example, if somebody wanted 64K from the middle of a file:

Range: bytes=32768-98304

You'll have to parse the text into numbers and handle accordingly.

吃颗糖壮壮胆 2024-10-12 07:29:54

请注意,Range 标头语法还允许使用“0-500、100-1500”(多个范围)和“-500”(最后 500 个字节)等内容。请参阅 RFC 2616 了解详细信息,这些详细信息太长,无法在此处引用。

Note that the Range header syntax also allows for things like "0-500, 100-1500" (multiple ranges) and "-500" (last 500 bytes). See RFC 2616 for the gory details, which are too long to quote here.

玩世 2024-10-12 07:29:54

基于@brent-keller 评论中链接到上面的博客文章,该评论又引用了 CodePlex 条目 ——我进行了以下修改。它已通过 FDM 进行测试(可在此处获取)。 (尚)不支持多范围请求。不需要 Web.config 条目。

CodePlex 的原始方法包含一个错误 - Accept-Ranges 标头的值应仅为 bytes,而不是要返回的字节范围。它属于 Content-Range 标头。下载仍然有效,但如果您犯了这个错误,您将无法获得字节服务

为了简洁和可读性,此修改版本进行了重构。它还有一个优点,即返回的文件不一定与实际 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

Based on the blog post linked to above in the comment by @brent-keller—which in turn references a CodePlex entry—I came up with the edits below. It's tested with FDM (available here). MultiRange requests are not supported (yet). No Web.config entries are required.

The original method at CodePlex contains an error—the Accept-Ranges header should have a value of simply bytes, not the byte range to be returned. That belongs to the Content-Range header. The download still works, but you won't get byte serving if you make that mistake.

This modified version is refactored for brevity and readability. It also has the advantage that the file returned isn't necessarily tied to the actual URL—in fact the handler may be called directly from the browser and with query string arguments if need be. This enables dynamic file/data creation and response.

Hopefully someone can do some good with it.

HTTP Handler

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

Range Request

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

Range Response

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文