Rails:验证链接(URL)的好方法是啥?

Posted

技术标签:

【中文标题】Rails:验证链接(URL)的好方法是啥?【英文标题】:Rails: What's a good way to validate links (URLs)?Rails:验证链接(URL)的好方法是什么? 【发布时间】:2011-11-02 07:57:52 【问题描述】:

我想知道如何最好地在 Rails 中验证 URL。我正在考虑使用正则表达式,但不确定这是否是最佳做法。

而且,如果我要使用正则表达式,有人可以向我推荐一个吗?我还是 Regex 的新手。

【问题讨论】:

相关:***.com/questions/1805761/… 【参考方案1】:

验证 URL 是一项棘手的工作。这也是一个非常广泛的要求。

你到底想做什么?您要验证 URL 的格式、存在还是什么?有几种可能性,具体取决于您想要做什么。

正则表达式可以验证 URL 的格式。但即使是复杂的正则表达式也无法确保您处理的是有效的 URL。

例如,如果你取一个简单的正则表达式,它可能会拒绝以下主机

http://invalid##host.com

但它会允许

http://invalid-host.foo

这是一个有效的主机,但如果您考虑现有的 TLD,则不是一个有效的域。实际上,如果您想验证主机名而不是域,则该解决方案会起作用,因为以下是有效的主机名

http://host.foo

还有下面的

http://localhost

现在,让我给你一些解决方案。

如果你想验证一个域,那么你需要忘记正则表达式。目前可用的最佳解决方案是由 Mozilla 维护的公共后缀列表。我创建了一个 Ruby 库来根据公共后缀列表解析和验证域,它被称为 PublicSuffix。

如果您想验证 URI/URL 的格式,那么您可能需要使用正则表达式。不要搜索,而是使用内置的 Ruby URI.parse 方法。

require 'uri'

def valid_url?(uri)
  uri = URI.parse(uri) && uri.host
rescue URI::InvalidURIError
  false
end

您甚至可以决定使其更具限制性。例如,如果您希望 URL 是 HTTP/HTTPS URL,那么您可以使验证更加准确。

require 'uri'

def valid_url?(url)
  uri = URI.parse(url)
  uri.is_a?(URI::HTTP) && !uri.host.nil?
rescue URI::InvalidURIError
  false
end

当然,您可以对这种方法进行大量改进,包括检查路径或方案。

最后但同样重要的是,您还可以将此代码打包到验证器中:

class HttpUrlValidator < ActiveModel::EachValidator

  def self.compliant?(value)
    uri = URI.parse(value)
    uri.is_a?(URI::HTTP) && !uri.host.nil?
  rescue URI::InvalidURIError
    false
  end

  def validate_each(record, attribute, value)
    unless value.present? && self.class.compliant?(value)
      record.errors.add(attribute, "is not a valid HTTP URL")
    end
  end

end

# in the model
validates :example_attribute, http_url: true

【讨论】:

请注意,对于 https uris,该类将为 URI::HTTPS(例如:URI.parse("https://yo.com").class =&gt; URI::HTTPS URI::HTTPS继承自URI:HTTP,这就是我使用kind_of?的原因。 迄今为止安全验证 URL 的最完整解决方案。 URI.parse('http://invalid-host.foo') 返回 true,因为该 URI 是有效的 URL。另请注意,.foo 现在是有效的 TLD。 iana.org/domains/root/db/foo.html www.google 是一个有效的域,尤其是现在 .GOOGLE 是一个有效的 TLD:github.com/whois/ianawhois/blob/master/GOOGLE。如果您希望验证器显式验证特定 TLD,则必须添加您认为合适的任何业务逻辑。【参考方案2】:

我在我的模型中使用一个衬里:

validates :url, format: URI::regexp(%w[http https])

我认为足够好且易于使用。此外,它在理论上应该等同于 Simone 的方法,因为它在内部使用了完全相同的正则表达式。

【讨论】:

不幸的是,'http://' 匹配上述模式。见:URI::regexp(%w(http https)) =~ 'http://' http:fake 这样的网址也是有效的。【参考方案3】:

按照 Simone 的想法,您可以轻松创建自己的验证器。

class UrlValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?
    begin
      uri = URI.parse(value)
      resp = uri.kind_of?(URI::HTTP)
    rescue URI::InvalidURIError
      resp = false
    end
    unless resp == true
      record.errors[attribute] << (options[:message] || "is not an url")
    end
  end
end

然后使用

validates :url, :presence => true, :url => true

在你的模型中。

【讨论】:

我应该把这门课放在哪里?在初始化程序中? 我引用@gbc 的话:“如果您将自定义验证器放在 app/validators 中,它们将自动加载,而无需更改您的 config/application.rb 文件。” (***.com/a/6610270/839847)。请注意,以下 Stefan Pettersson 的回答表明他也在“app/validators”中保存了一个类似的文件。 这仅检查 url 是否以 http:// 或 https:// 开头,这不是正确的 URL 验证 结束如果你能负担得起 URL 是可选的: class OptionalUrlValidator 这不是一个好的验证:URI("http:").kind_of?(URI::HTTP) #=&gt; true【参考方案4】:

还有validate_url gem(它只是Addressable::URI.parse 解决方案的一个很好的包装)。

只需添加

gem 'validate_url'

到你的Gemfile,然后你可以在模型中

validates :click_through_url, url: true

【讨论】:

@ЕвгенийМасленков 这可能也不错,因为它根据规范是有效的,但您可能需要检查 github.com/sporkmonger/addressable/issues 。同样在一般情况下,我们发现没有人遵循标准,而是使用简单的格式验证。【参考方案5】:

这个问题已经回答了,但到底是什么,我提出了我正在使用的解决方案。

正则表达式适用于我遇到的所有网址。 如果没有提到协议(假设 http://),setter 方法要小心。

最后,我们尝试获取页面。也许我应该接受重定向,而不仅仅是 HTTP 200 OK。

# app/models/my_model.rb
validates :website, :allow_blank => true, :uri =>  :format => /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]1[a-z0-9]+)*\.[a-z]2,5(([0-9]1,5)?\/.*)?$)/ix 

def website= url_str
  unless url_str.blank?
    unless url_str.split(':')[0] == 'http' || url_str.split(':')[0] == 'https'
        url_str = "http://" + url_str
    end
  end  
  write_attribute :website, url_str
end

还有……

# app/validators/uri_vaidator.rb
require 'net/http'

# Thanks Ilya! http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/
# Original credits: http://blog.inquirylabs.com/2006/04/13/simple-uri-validation/
# HTTP Codes: http://www.ruby-doc.org/stdlib/libdoc/net/http/rdoc/classes/Net/HTTPResponse.html

class UriValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    raise(ArgumentError, "A regular expression must be supplied as the :format option of the options hash") unless options[:format].nil? or options[:format].is_a?(Regexp)
    configuration =  :message => I18n.t('errors.events.invalid_url'), :format => URI::regexp(%w(http https)) 
    configuration.update(options)

    if value =~ configuration[:format]
      begin # check header response
        case Net::HTTP.get_response(URI.parse(value))
          when Net::HTTPSuccess then true
          else object.errors.add(attribute, configuration[:message]) and false
        end
      rescue # Recover on DNS failures..
        object.errors.add(attribute, configuration[:message]) and false
      end
    else
      object.errors.add(attribute, configuration[:message]) and false
    end
  end
end

【讨论】:

真的很整洁!感谢您的意见,通常有很多方法可以解决问题;当人们分享他们的时,这很棒。 只是想指出,根据rails security guide,您应该在该正则表达式中使用 \A 和 \z 而不是 $^ 我喜欢。快速建议通过将正则表达式移动到验证器中来稍微干燥代码,因为我想你希望它在模型之间保持一致。奖励:它将允许您将第一行放在 validate_each 下。 如果 url 需要很长时间并且超时怎么办?显示超时错误消息或无法打开页面的最佳选择是什么? 这将永远无法通过安全审核,您正在让您的服务器戳任意 url【参考方案6】:

对我有用的解决方案是:

validates_format_of :url, :with => /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]2,6)([\/\w\.-]*)*\/?\Z/i

我确实尝试使用您附加的一些示例,但我支持这样的 url:

注意 A 和 Z 的使用,因为如果您使用 ^ 和 $,您将看到来自 Rails 验证器的警告安全性。

 Valid ones:
 'www.crowdint.com'
 'crowdint.com'
 'http://crowdint.com'
 'http://www.crowdint.com'

 Invalid ones:
  'http://www.crowdint. com'
  'http://fake'
  'http:fake'

【讨论】:

"https://portal.example.com/portal/#"试试这个。在 Ruby 2.1.6 中,评估挂起。 你是对的,在某些情况下,这个正则表达式似乎需要永远解析:( 显然,没有涵盖所有场景的正则表达式,这就是为什么我最终只使用一个简单的验证: validates :url, format: with: URI.regexp , if: Proc .new |a| a.url.present? 【参考方案7】:

您也可以尝试valid_url gem,它允许没有方案的 URL,检查域区域和 ip-hostnames。

将其添加到您的 Gemfile:

gem 'valid_url'

然后在模型中:

class WebSite < ActiveRecord::Base
  validates :url, :url => true
end

【讨论】:

这太棒了,尤其是没有scheme的URL,这令人惊讶地与URI类有关。 我对这个 gem 能够挖掘基于 IP 的 URL 并检测虚假 URL 的能力感到惊讶。谢谢!【参考方案8】:

只要我的 2 美分:

before_validation :format_website
validate :website_validator

private

def format_website
  self.website = "http://#self.website" unless self.website[/^https?/]
end

def website_validator
  errors[:website] << I18n.t("activerecord.errors.messages.invalid") unless website_valid?
end

def website_valid?
  !!website.match(/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]2,6)([\/\w \.-=\?]*)*\/?$/)
end

编辑:更改正则表达式以匹配参数网址。

【讨论】:

感谢您的意见,总是很高兴看到不同的解决方案 顺便说一句,您的正则表达式将拒绝带有查询字符串的有效网址,例如http://test.com/fdsfsdf?a=b 我们将此代码投入生产,并不断在 .match 正则表达式行的无限循环中超时。不知道为什么,只是注意一些极端情况,并希望听到其他人对为什么会发生这种情况的想法。【参考方案9】:

我最近遇到了同样的问题(我需要在 Rails 应用程序中验证 url),但我不得不应对 unicode url 的额外要求(例如http://кц.рф)...

我研究了几个解决方案并遇到了以下问题:

第一个也是最推荐的方法是使用URI.parse。查看 Simone Carletti 的答案以获取详细信息。这工作正常,但不适用于 unicode 网址。 我看到的第二种方法是 Ilya Grigorik 的方法:http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/ 基本上,他尝试向 url 发出请求;如果有效,则有效... 我发现的第三种方法(也是我更喜欢的方法)是一种类似于URI.parse 的方法,但使用addressable gem 而不是URI stdlib。这种方法在这里详细介绍:http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/

【讨论】:

是的,但是从 Addressable 的角度来看,Addressable::URI.parse('http:///').scheme # =&gt; "http"Addressable::URI.parse('Съешь [же] ещё этих мягких французских булок да выпей чаю') 完全没问题:(【参考方案10】:

这是validator posted by David James 的更新版本。一直是published by Benjamin Fleischer。同时,我推送了一个更新的fork,可以找到here。

require 'addressable/uri'

# Source: http://gist.github.com/bf4/5320847
# Accepts options[:message] and options[:allowed_protocols]
# spec/validators/uri_validator_spec.rb
class UriValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    uri = parse_uri(value)
    if !uri
      record.errors[attribute] << generic_failure_message
    elsif !allowed_protocols.include?(uri.scheme)
      record.errors[attribute] << "must begin with #allowed_protocols_humanized"
    end
  end

private

  def generic_failure_message
    options[:message] || "is an invalid URL"
  end

  def allowed_protocols_humanized
    allowed_protocols.to_sentence(:two_words_connector => ' or ')
  end

  def allowed_protocols
    @allowed_protocols ||= [(options[:allowed_protocols] || ['http', 'https'])].flatten
  end

  def parse_uri(value)
    uri = Addressable::URI.parse(value)
    uri.scheme && uri.host && uri
  rescue URI::InvalidURIError, Addressable::URI::InvalidURIError, TypeError
  end

end

...

require 'spec_helper'

# Source: http://gist.github.com/bf4/5320847
# spec/validators/uri_validator_spec.rb
describe UriValidator do
  subject do
    Class.new do
      include ActiveModel::Validations
      attr_accessor :url
      validates :url, uri: true
    end.new
  end

  it "should be valid for a valid http url" do
    subject.url = 'http://www.google.com'
    subject.valid?
    subject.errors.full_messages.should == []
  end

  ['http://google', 'http://.com', 'http://ftp://ftp.google.com', 'http://ssh://google.com'].each do |invalid_url|
    it "#invalid_url.inspect is a invalid http url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.full_messages.should == []
    end
  end

  ['http:/www.google.com','<>hi'].each do |invalid_url|
    it "#invalid_url.inspect is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("is an invalid URL")
    end
  end

  ['www.google.com','google.com'].each do |invalid_url|
    it "#invalid_url.inspect is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("is an invalid URL")
    end
  end

  ['ftp://ftp.google.com','ssh://google.com'].each do |invalid_url|
    it "#invalid_url.inspect is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("must begin with http or https")
    end
  end
end

请注意,仍然有一些奇怪的 HTTP URI 被解析为有效地址。

http://google  
http://.com  
http://ftp://ftp.google.com  
http://ssh://google.com

这是一个issue for the addressable gem,其中涵盖了示例。

【讨论】:

在above linked issue 中,存储库的所有者非常详细地解释了为什么“奇怪的 HTTP URI”是有效的,以及对于他的图书馆的工作来说,失败的有效 URI 比允许无效的 URI 更具破坏性URI。【参考方案11】:

我对@9​​87654321@ 使用了细微的变化。 它不允许主机名中出现连续的点(例如www.many...dots.com):

%r"\A(https?://)?[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]2,6(/.*)?\Z"i

URI.parse 似乎要求使用方案前缀,这在某些情况下可能不是您想要的(例如,如果您想允许您的用户以 twitter.com/username 等形式快速拼写 URL)

【讨论】:

【参考方案12】:

我一直在使用 'activevalidators' gem,它的效果很好(不仅仅是用于 url 验证)

你可以找到它here

这一切都已记录在案,但基本上一旦添加了 gem,您就需要在初始化程序中添加以下几行:/config/environments/initializers/active_validators_activation.rb

# Activate all the validators
ActiveValidators.activate(:all)

(注意:如果您只想验证特定类型的值,您可以将 :all 替换为 :url 或 :whatever)

然后回到你的模型中像这样

class Url < ActiveRecord::Base
   validates :url, :presence => true, :url => true
end

现在重启服务器应该就是这样了

【讨论】:

【参考方案13】:

如果您想要简单的验证和自定义错误消息:

  validates :some_field_expecting_url_value,
            format: 
              with: URI.regexp(%w[http https]),
              message: 'is not a valid URL'
            

【讨论】:

【参考方案14】:

我喜欢猴子补丁 URI 模块以添加有效的?方法

config/initializers/uri.rb

module URI
  def self.valid?(url)
    uri = URI.parse(url)
    uri.is_a?(URI::HTTP) && !uri.host.nil?
  rescue URI::InvalidURIError
    false
  end
end

【讨论】:

【参考方案15】:

您可以使用以下方式验证多个网址:

validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true

【讨论】:

如果没有方案,您将如何处理 URL(例如 www.bar.com/foo)?【参考方案16】:

https://github.com/perfectline/validates_url 是一个漂亮而简单的 gem,几乎可以为你做任何事情

【讨论】:

【参考方案17】:

最近我遇到了同样的问题,我找到了有效网址的解决方法。

validates_format_of :url, :with => URI::regexp(%w(http https))
validate :validate_url
def validate_url

  unless self.url.blank?

    begin

      source = URI.parse(self.url)

      resp = Net::HTTP.get_response(source)

    rescue URI::InvalidURIError

      errors.add(:url,'is Invalid')

    rescue SocketError 

      errors.add(:url,'is Invalid')

    end



  end

validate_url 方法的第一部分足以验证 url 格式。第二部分将通过发送请求来确保 url 存在。

【讨论】:

如果 url 指向的资源非常大(例如,多个 GB)怎么办? @JonSchneider 可以使用 http 头请求(如 here)而不是 get。【参考方案18】:

作为一个模块

module UrlValidator
  extend ActiveSupport::Concern
  included do
    validates :url, presence: true, uniqueness: true
    validate :url_format
  end

  def url_format
    begin
      errors.add(:url, "Invalid url") unless URI(self.url).is_a?(URI::HTTP)
    rescue URI::InvalidURIError
      errors.add(:url, "Invalid url")
    end
  end
end

然后只需 include UrlValidator 在您想要验证 url 的任何模型中。只包括选项。

【讨论】:

【参考方案19】:

随着网站数量的不断增长和新的域命名方案不断出现,无法简单地使用正则表达式来处理 URL 验证。

就我而言,我只是编写了一个自定义验证器来检查响应是否成功。

class UrlValidator < ActiveModel::Validator
  def validate(record)
    begin
      url = URI.parse(record.path)
      response = Net::HTTP.get(url)
      true if response.is_a?(Net::HTTPSuccess)   
    rescue StandardError => error
      record.errors[:path] << 'Web address is invalid'
      false
    end  
  end
end

我正在使用record.path 验证我的模型的path 属性。我还使用record.errors[:path] 将错误推送到相应的属性名称。

您可以简单地将其替换为任何属性名称。

然后,我只需在我的模型中调用自定义验证器。

class Url < ApplicationRecord

  # validations
  validates_presence_of :path
  validates_with UrlValidator

end

【讨论】:

如果 url 指向的资源非常大(例如,多个 GB)怎么办? 为此,我建议使用异步检查并使用 HEAD 请求而不是 GET。【参考方案20】:

您可以为此使用正则表达式,对我来说这个效果很好:

(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])

【讨论】:

【参考方案21】:

URI::regexp(%w[http https]) 已过时,不应使用。

改为使用URI::DEFAULT_PARSER.make_regexp(%w[http https])

【讨论】:

以上是关于Rails:验证链接(URL)的好方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

开发简单的序列号生成器/验证器的好方法是啥?

在 Rails 中验证多封电子邮件和处理错误的最佳方法是啥?

在 Rails 中创建主观视图的好方法是啥?

解释 Core Data 验证消息并在 iPhone 上显示它们的好模式是啥?

Rails 3 验证和非 REST URL

Rails:通过表单创建新记录时验证后的 URL 失败