【问题标题】:How to get HTTP headers before downloading with Ruby's OpenUri如何在使用 Ruby 的 OpenUri 下载之前获取 HTTP 标头
【发布时间】:2013-07-01 13:18:03
【问题描述】:

我目前正在使用 OpenURI 在 Ruby 中下载文件。不幸的是,如果不下载完整文件,似乎不可能获取 HTTP 标头:

open(base_url,
  :content_length_proc => lambda {|t|
    if t && 0 < t
      pbar = ProgressBar.create(:total => t)
  end
  },
  :progress_proc => lambda {|s|
    pbar.progress = s if pbar
  }) {|io|
    puts io.size
    puts io.meta['content-disposition']
  }

运行上面的代码表明它首先下载了完整的文件,然后才打印我需要的标题。

有没有办法在下载完整文件之前获取标题,所以如果标题不是我期望的那样,我可以取消下载?

【问题讨论】:

  • @Kira 不,使用链接的答案将首先下载完整文件,这正是我想要的。
  • 打开不会将整个响应加载到内存中。事实上,它会这样做,但仅适用于小于或等于 10240 字节的响应。较大的响应将流式传输Tempfile。您可以使用这些知识来实际访问临时文件并使用它来做精益工作。除非您愿意,否则内存中什么也没有发生。在这里查看我的答案:stackoverflow.com/questions/2263540/… 但是如果您只想访问标头,则不应使用open,因为它总是会读取响应。下面的答案很好。

标签: ruby http-headers open-uri


【解决方案1】:

与其使用 Net::HTTP,它可以像使用沙铲在海滩上挖一个水池一样,您可以使用一些用于 Ruby 的 HTTP 客户端并清理代码。

这是一个使用HTTParty的示例:

require 'httparty'

resp = HTTParty.head('http://example.org')
resp.headers
# => {"accept-ranges"=>["bytes"], "cache-control"=>["max-age=604800"], "content-type"=>["text/html"], "date"=>["Thu, 02 Mar 2017 18:52:42 GMT"], "etag"=>["\"359670651\""], "expires"=>["Thu, 09 Mar 2017 18:52:42 GMT"], "last-modified"=>["Fri, 09 Aug 2013 23:54:35 GMT"], "server"=>["ECS (oxr/83AB)"], "x-cache"=>["HIT"], "content-length"=>["1270"], "connection"=>["close"]}

此时很容易检查文档的大小:

resp.headers['content-length'] # => "1270"

不幸的是,您正在与之交谈的 HTTPd 可能不知道内容有多大;为了快速响应,服务器不一定要计算动态生成输出的大小,这几乎与实际发送它所花费的时间一样长,并且几乎与 CPU 密集型一样,因此依赖“内容长度”值可能是错误的。

Net::HTTP 的问题是它不会自动处理重定向,所以你必须添加额外的代码。当然,文档中提供了该代码,但是随着您需要做更多事情,代码会不断增长,直到您最终编写另一个 http 客户端 (YAHC)。所以,避免这种情况并使用现有的轮子。

【讨论】:

  • 如果我正确理解代码,这实际上是一个 HEAD 请求,在这种特定情况下,这不是我想要的。尽管一般来说这可能是解决这个问题的好方法,但在这种情况下我不得不使用 GET 请求。
  • GET 将始终尝试检索整个文件。有可能进入处理过程并中止连接,但这不是一个好的网络公民。考虑会发生什么:您发出 GET 并且服务器加载文件以开始发送它。你中止了,你刚刚在服务器和中间网络以及你的主机上造成了额外的负载。这就是 HEAD 被发明的原因,以避免这样做。
  • 正如我所说,我知道这一点,但在特定情况下 HEAD 不起作用,因此 GET 是唯一的选择。而且我想避免下载完整文件只是为了把它扔掉,所以我认为能够尽早中止,而不是在下载整个文件之后,将是一件好事。
【解决方案2】:

似乎我想要的内容无法使用 OpenURI 进行归档,至少如我所说,如果不先加载整个文件,则不能。

我能够使用 Net::HTTP 的 request_get 做我想做的事

这里是一个例子:

http.request_get('/largefile.jpg') {|response|
  if (response['content-length'] < max_length)
    response.read_body do |str|   # read body now
      # save to file
    end
  end
}

请注意,这仅在使用块时有效,如下所示:

response = http.request_get('/largefile.jpg')

正文已经被读取。

【讨论】:

  • 正确,OpenURI 预先读取内容然后返回一个文件句柄,无论你是否使用块形式。
【解决方案3】:

这个问题可以使用Net::HTTP,例如:

require 'net/http'

http = Net::HTTP.start('stackoverflow.com')

resp = http.head('/')
resp.each { |k, v| puts "#{k}: #{v}" }
http.finish

另一个例子,这次拿到了精彩的书的标题,Object Orient Programming With ANSI-C

require 'net/http'

http = Net::HTTP.start('www.planetpdf.com')

resp = http.head('/codecuts/pdfs/ooc.pdf')
resp.each { |k, v| puts "#{k}: #{v}" }
http.finish

【讨论】:

  • 使用start的块形式更简洁。请参阅the documentation 中的示例。
  • 使用块形式有很多很好的理由,包括在块结束时自动关闭逻辑(如果不是连接)。做他们想做的任何事是程序员的特权,但应该有充分的理由。缩进太深或块形式不合适听起来需要重构。
  • 谢谢,但这并不是我真正想要归档的(见我的回答)。无论如何,这对找到我想要的东西很有帮助,谢谢。
  • @ePirat 实际上,如果您不想下载文件并且只想收集有关文件的信息,那么 HEAD 请求确实是您想要的。从 RFC2616 秒。 9.4 此方法(HEAD)可用于获取有关请求所暗示的实体的元信息,而无需传输实体主体本身。此方法常用于测试超文本链接的有效性、可访问性和最近的修改。请访问w3.org/Protocols/rfc2616/rfc2616-sec9.html
  • @kira 很好的解释。我总是难以理解 Ruby 的 http lib.. 你能帮我解决这个问题吗?
猜你喜欢
  • 2013-07-27
  • 2011-11-20
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-25
  • 2023-03-24
相关资源
最近更新 更多