如何使用Net::HTTP仅读取指定数量(x)的字节体?

13

看起来Ruby的Net::HTTP方法在读取网页正文时都是一刀切的。有没有办法只读取前100个字节?

我正在尝试从一个内容服务器中读取数据。如果请求的文件不存在,服务器会在响应正文中返回一个短错误消息。我需要读取足够的正文来确定文件是否存在,但这些文件非常大,所以我不想获取整个正文仅仅为了检查文件是否存在。

6个回答

13

这是一个旧帖子,但是我的研究表明,在Ruby中如何通过HTTP仅读取文件的一部分仍然是一个大多数未被解答的问题。这是我通过对Net::HTTP进行一些猴子补丁(monkey-patch)所想出的解决方案:

require 'net/http'

# provide access to the actual socket
class Net::HTTPResponse
  attr_reader :socket
end

uri = URI("http://www.example.com/path/to/file")
begin
  Net::HTTP.start(uri.host, uri.port) do |http|
    request = Net::HTTP::Get.new(uri.request_uri)
    # calling request with a block prevents body from being read
    http.request(request) do |response|
      # do whatever limited reading you want to do with the socket
      x = response.socket.read(100);
      # be sure to call finish before exiting the block
      http.finish
    end
  end
rescue IOError
  # ignore
end

当您提前调用HTTP.finish时,救援(rescue)会捕获抛出的IOError。

顺便说一下,在HTTPResponse对象中的套接字并不是真正的IO对象(它是一个名为BufferedIO的内部类),但很容易通过改写代码来模拟所需的IO方法。例如,我正在使用的另一个库(exifr)需要添加readchar方法:

class Net::BufferedIO
  def readchar
    read(1)[0].ord
  end
end

2
请注意,响应可能会被压缩,因此在上面的示例中会得到100个“二进制”八位组。当期望文本时,请使用request = Net::HTTP::Get.new(uri.request_uri, {'Accept-Encoding' => 'entity'})来禁用压缩。请注意,如果响应是分块的,则首先会得到一个带有块大小的行,以十六进制表示。因此,response.socket.read(100)将产生一行带有十六进制数字64,以及另一行带有100个八位组(或多行带有较小的大小,如果服务器发送较小的块)。 - Arjan

12
您是否应该使用HTTP HEAD 请求(Ruby的 Net::HTTP::Head 方法)来查看资源是否存在,并仅在收到2xx或3xx响应时继续进行? 这假定您的服务器已配置为返回4xx错误代码,如果文档不可用。我认为这是正确的解决方案。
另一种选择是请求HTTP head并查看结果中的content-length标头值:如果您的服务器已正确配置,则应能够轻松区分短消息和长文档之间的长度差异。 另一种选择是在请求中设置content-range标头字段(再次假设服务器根据HTTP规范正确运行)。
我不认为在发送GET请求后在客户端中解决问题是正确的方法:此时,网络已经完成了大部分工作,您不会真正节省任何浪费的资源。
参考:http header definitions

1
尝试过了,服务器发送了一个OK响应和0的内容长度。这是来自Perforce的P4Web服务器。 - bvanderw
3
如果你的供应商在实际上表示资源不存在时发送200 OK,那么你应该向他们提出一个优先级别的错误报告!请注意,本翻译仅代表原文含义,不包含其他解释或内容。 - Ian Dickinson
使用 HEAD 是从客户端出发的正确方式。如果他们的服务器出现问题,他们需要修复它。不幸的是,这并不会让 OP 的任务变得更容易,因为公司和供应商通常不关心使用内容时遇到的问题,当黑客攻击他们的服务器时也是如此。 - the Tin Man

3

我曾经想过这样做,唯一能想到的方法就是对Net::HTTP#read_bodyNet::HTTP#read_body_0方法进行猴子补丁以接受长度参数,然后在前者中将长度参数传递给read_body_0方法,在该方法中只读取指定长度的字节数。


如果你还有那段代码,我很想看看。 - William Pietri
很不幸,我手头没有它,但它非常简单,因为我只需要读取这些字节,而且我不关心后面的字节。所以我在#read_body中添加了另一个参数,其默认值为'nil',并在#read_body_0中添加了参数len=nil,然后我得到了以下代码:如果len; @socket.read len, dest; return; end - Roman

2
为了以块的形式读取HTTP请求正文,您需要像这样使用Net :: HTTPResponse#read_body
http.request_get('/large_resource') do |response|
  response.read_body do |segment|
    print segment
  end
end

2
尝试过这个。request_get仍然想要在处理块之前下载整个文件。 - bvanderw
如果我在两个块中(在两个“end”之前)都添加一个“break”,以停止获取第一个块,那么这对于分块响应(带有“Transfer-Encoding: chunked”)对我有效。在这种情况下,使用具有“read_body”的块使Ruby不会读取完整的响应(甚至不等待它)。但是,再次强调:我的响应首先是分块的,而且这些块很小。我怀疑HTTP允许客户端显式请求分块响应,也不允许它建议最大块大小;如果服务器没有返回(小)块,则应改用“Range”标头。 - Arjan

2

您确定内容服务器只返回了一张简短的错误页面吗?

它是否还将HTTPResponse设置为适当的值,例如404?如果是这样,您可以捕获HTTPClientError派生的异常(最可能是HTTPNotFound),当访问Net::HTTP.value()时会出现该异常。

如果您收到错误,则说明该文件不存在;如果您收到200,则表示该文件已经开始下载,此时可以关闭连接。


-4

你不能这样做。但是你为什么需要这样做呢?如果页面只是显示文件不可用,那么它不会是一个很大的页面(也就是说,按照定义,该文件不存在)。


这是你没能想象到某些东西为什么是必要的/有用的/可取的,因为你个人还没有遇到过它。谁在乎他为什么需要?谁在乎你是否能绕过问题?问题是“如何使用Net::HTTP只读取x个字节的正文?” 你知道吗?如果不知道,那你为什么浪费大家的带宽? - Michael Johnston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接