【问题标题】:Check if URL exists in Ruby检查 Ruby 中是否存在 URL
【发布时间】:2011-05-06 07:15:33
【问题描述】:

如何使用 Ruby 检查 URL 是否存在?

例如,对于 URL

https://google.com

结果应该是 truthy,但对于 URLs

https://no.such.domain

https://*.com/no/such/path

结果应该是假的

【问题讨论】:

  • 你应该阅读这篇文章:Validating URL/URI in Ruby on Rails
  • 问题足以匹配我的谷歌搜索,答案很有价值
  • 我同意。这个问题很有用。
  • 我认为这是一个很好的问题,有有用的答案。它被关闭的原因(“必须表现出最低限度的理解”)在 SO 上不再有效。我已经编辑了问题以添加一些示例。有了这个,我认为现在可以重新提出这个问题了。
  • 如果您认为这个问题很好,请投票reopen。需要另外 4 人重新打开此问题。我想发布一个考虑重定向的答案。

标签: ruby


【解决方案1】:

使用Net::HTTP 库。

require "net/http"
url = URI.parse("http://www.google.com/")
req = Net::HTTP.new(url.host, url.port)
res = req.request_head(url.path)

此时res 是一个包含请求结果的Net::HTTPResponse 对象。然后您可以检查响应代码:

do_something_with_it(url) if res.code == "200"

注意:要检查基于https 的url,use_ssl 属性应该是true

require "net/http"
url = URI.parse("https://www.google.com/")
req = Net::HTTP.new(url.host, url.port)
req.use_ssl = true
res = req.request_head(url.path)

【讨论】:

  • 在生产中,对于每个 URL,这都会返回 200 个代码。我已经解析了 http://www.http:/ 这个 URL 并给了我 200 OK ......但是这是错误的......有什么问题这里?任何的想法?注意:这在本地环境中运行良好。
  • 还要检查查询部分,例如YouTube 网址,在使用 req.request_head(address) 之前使用 address = [url.path, url.query].compact.split('').flatten.join('?') 或在 Rails 中使用 [url.path.presence || '/', url.query.presence].compact.join('?')
【解决方案2】:

很抱歉,回复晚了,但我认为这值得更好的回答。

看这个问题有三种方式:

  1. 严格检查网址是否存在
  2. 检查您请求的 URL 是否正确
  3. 检查是否可以正确请求,服务器是否可以正确应答

1。严格检查URL是否存在

虽然200 表示服务器响应该 URL(因此,该 URL 存在),但回答其他状态代码并不意味着该 URL 不存在。例如,回答302 - redirected 表示该URL 存在并且正在重定向到另一个。浏览时,302 对最终用户的行为多次与200 相同。如果 URL 存在,则可以返回的其他状态代码是 500 - internal server error。毕竟,如果 URL 不存在,应用程序服务器是如何处理您的请求的,而不是简单地返回 404 - not found

所以实际上URL不存在只有两种情况:服务器不存在或者服务器存在但找不到给定的URL路径不存在。 因此,检查 URL 是否存在的唯一方法是检查服务器是否响应并且返回码不是 404。 下面的代码就是这样做的。

require "net/http"
def url_exist?(url_string)
  url = URI.parse(url_string)
  req = Net::HTTP.new(url.host, url.port)
  req.use_ssl = (url.scheme == 'https')
  path = url.path if url.path.present?
  res = req.request_head(path || '/')
  res.code != "404" # false if returns 404 - not found
rescue Errno::ENOENT
  false # false if can't find the server
end

2。检查您请求的 URL 是否正确

但是,大多数时候我们并不关心是否存在 URL,而是我们是否可以访问它。幸运的是查看HTTP status codes 家族,即4xx 家族,它说明客户端错误(因此,您身边的错误,这意味着您没有正确请求页面,没有权限或其他任何内容)。这是检查您是否可以访问此页面的一个很好的错误。来自维基:

4xx 类状态代码适用于客户端似乎出错的情况。除了响应 HEAD 请求时,服务器应该包含一个实体,其中包含对错误情况的解释,以及它是临时情况还是永久情况。这些状态码适用于任何请求方法。用户代理应该向用户显示任何包含的实体。

所以下面的代码确保网址存在并且可以访问

require "net/http"
def url_exist?(url_string)
  url = URI.parse(url_string)
  req = Net::HTTP.new(url.host, url.port)
  req.use_ssl = (url.scheme == 'https')
  path = url.path if url.path.present?
  res = req.request_head(path || '/')
  if res.kind_of?(Net::HTTPRedirection)
    url_exist?(res['location']) # Go after any redirect and make sure you can access the redirected URL 
  else
    res.code[0] != "4" #false if http code starts with 4 - error on your side.
  end
rescue Errno::ENOENT
  false #false if can't find the server
end

3。检查是否可以正确请求,服务器是否可以正确回答

就像4xx 系列检查您是否可以访问该 URL,5xx 系列检查服务器在回答您的请求时是否有任何问题。大多数时候这个系列的错误是服务器本身的问题,希望他们正在努力解决它。如果您现在需要能够访问该页面并获得正确答案,则应确保该答案不是来自4xx5xx 家庭,如果您被重定向,则重定向页面回答正确。与(2)非常相似,您可以简单地使用以下代码:

require "net/http"
def url_exist?(url_string)
  url = URI.parse(url_string)
  req = Net::HTTP.new(url.host, url.port)
  req.use_ssl = (url.scheme == 'https')
  path = url.path if url.path.present?
  res = req.request_head(path || '/')
  if res.kind_of?(Net::HTTPRedirection)
    url_exist?(res['location']) # Go after any redirect and make sure you can access the redirected URL 
  else
    ! %W(4 5).include?(res.code[0]) # Not from 4xx or 5xx families
  end
rescue Errno::ENOENT
  false #false if can't find the server
end

【讨论】:

  • 如果您使用 https-urls 执行此操作,您可能会收到 Net::HTTPBadResponse: wrong status line 错误。这是因为您必须告诉 Net:HTTP 使用 ssl。要使其也适用于 https,请在调用 request_head 之前添加一行 req.use_ssl = (url.scheme == 'https')
  • @YoLudke 感谢您的贡献
  • 另一件事:如果你请求(或重定向到)'example.com'(没有尾随'/'),那么你会得到一个ArgumentError: HTTP request path is empty。这可以通过将res = req.request_head(url.path) 行更改为path = url.path if url.path.present?req.request_head(path || '/') 来解决
  • 我不得不添加更多救援来管理其他案例:rescue Errno::ENOENT false #false if can't find the server rescue URI::InvalidURIError false #false if URI is invalid rescue SocketError false #false if Failed to open TCP connection rescue Errno::ECONNREFUSED false #false if Failed to open TCP connection rescue Net::OpenTimeout false #false if execution expired rescue OpenSSL::SSL::SSLError false
  • @Ta 表明只有恶意用户可以破解 URI.parse 才会不安全,据我所知,它没有已知的漏洞。
【解决方案3】:

Net::HTTP 可以工作,但如果您可以在 stdlib 之外工作,Faraday 会更好。

Faraday.head(the_url).status == 200

(200 是成功代码,假设这就是您所说的“存在”。)

【讨论】:

【解决方案4】:

Simone 的回答对我很有帮助。

这是一个根据 URL 有效性返回 true/false 并处理重定向的版本:

require 'net/http'
require 'set'

def working_url?(url, max_redirects=6)
  response = nil
  seen = Set.new
  loop do
    url = URI.parse(url)
    break if seen.include? url.to_s
    break if seen.size > max_redirects
    seen.add(url.to_s)
    response = Net::HTTP.new(url.host, url.port).request_head(url.path)
    if response.kind_of?(Net::HTTPRedirection)
      url = response['location']
    else
      break
    end
  end
  response.kind_of?(Net::HTTPSuccess) && url.to_s
end

【讨论】:

  • 如果服务器不支持 HEAD 请求怎么办?