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

jay*_*jay 122 ruby regex validation url ruby-on-rails

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

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

Sim*_*tti 141

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

你到底想做什么?您想验证URL的格式,存在还是什么?根据您的要求,有几种可能性.

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

例如,如果您使用简单的正则表达式,它可能会拒绝以下主机

http://invalid##host.com
Run Code Online (Sandbox Code Playgroud)

但它会允许

http://invalid-host.foo
Run Code Online (Sandbox Code Playgroud)

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

http://host.foo
Run Code Online (Sandbox Code Playgroud)

以及以下一个

http://localhost
Run Code Online (Sandbox Code Playgroud)

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

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

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

require 'uri'

def valid_url?(uri)
  uri = URI.parse(uri) && !uri.host.nil?
rescue URI::InvalidURIError
  false
end
Run Code Online (Sandbox Code Playgroud)

您甚至可以决定使其更具限制性.例如,如果您希望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
Run Code Online (Sandbox Code Playgroud)

当然,您可以对此方法应用大量改进,包括检查路径或方案.

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

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
Run Code Online (Sandbox Code Playgroud)

  • `URI :: HTTPS`继承自`URI:HTTP`,这就是我使用`kind_of?`的原因. (12认同)
  • `URI.parse('http:http://invalid-host.foo')`返回true,因为该URI是有效的URL.另请注意,`.foo`现在是有效的TLD.http://www.iana.org/domains/root/db/foo.html (4认同)
  • 迄今为止最完整的安全验证 URL 的解决方案。 (2认同)
  • `www.google` 是一个有效的域,特别是现在 `.GOOGLE` 是一个有效的 TLD:https://github.com/whois/ianawhois/blob/master/GOOGLE。如果您希望验证器显式验证特定的 TLD,那么您必须添加您认为合适的任何业务逻辑。 (2认同)

小智 97

我在模特里面使用了一个衬垫:

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

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

  • 不幸的是''http://'`匹配上面的模式.请参阅:`URI :: regexp(%w(http https))=〜'http://'` (15认同)
  • 另外一个像`http:fake`的网址也是有效的. (13认同)

jlf*_*aux 53

遵循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
Run Code Online (Sandbox Code Playgroud)

然后使用

validates :url, :presence => true, :url => true
Run Code Online (Sandbox Code Playgroud)

在你的模型中.

  • 我重构了你更简洁的答案:https://gist.github.com/2986523 (10认同)
  • 这只会检查url是以http://还是https://开头,这不是正确的URL验证 (4认同)
  • 我引用@gbc:"如果你将自定义验证器放在app/validators中,它们将自动加载,而不需要改变你的config/application.rb文件." (http://stackoverflow.com/a/6610270/839847).请注意,下面Stefan Pettersson的答案显示他也在"app/validators"中保存了一个类似的文件. (3认同)

dol*_*nko 26

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

只需添加

gem 'validate_url'
Run Code Online (Sandbox Code Playgroud)

到你的Gemfile,然后你可以在模特

validates :click_through_url, url: true
Run Code Online (Sandbox Code Playgroud)


Ste*_*son 14

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

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

最后,我们尝试获取页面.也许我应该接受重定向,而不仅仅是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
Run Code Online (Sandbox Code Playgroud)

和...

# 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
Run Code Online (Sandbox Code Playgroud)

  • 只是想指出,根据[rails安全指南](http://guides.rubyonrails.org/security.html#regular-expressions),您应该在该regexp中使用\ A和\ z而不是$ ^ (6认同)

小智 12

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

将它添加到您的Gemfile:

gem 'valid_url'

然后在模型中:

class WebSite < ActiveRecord::Base
  validates :url, :url => true
end
Run Code Online (Sandbox Code Playgroud)


laf*_*ber 10

只需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
Run Code Online (Sandbox Code Playgroud)

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

  • 我们将此代码投入生产,并在.match正则表达式行上的无限循环上保持超时.不知道为什么,只是提醒一些角落,并希望听到别人的想法,为什么会发生这种情况. (2认同)

Her*_*aña 10

对我有用的解决方案是:

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

我确实尝试使用你附加的一些例子但我支持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'
Run Code Online (Sandbox Code Playgroud)

  • 显然,没有涵盖所有场景的正则表达式,这就是为什么我最终只使用一个简单的验证:validates :url, format: { with: URI.regexp }, if: Proc.new { |a| a.url.present? } (2认同)

sev*_*rin 5

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

我研究了几个解决方案,并发现了以下内容:

  • 第一个也是最值得建议的是使用URI.parse.有关详细信息,请查看Simone Carletti的答案.这可行,但不适用于unicode网址.
  • 我看到的第二种方法是Ilya Grigorik:http ://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/ 基本上,他试图向网址; 如果它有效,它是有效的......
  • 我找到的第三种方法(以及我更喜欢的方法)是一种类似URI.parse但使用addressablegem而不是URIstdlib的方法.这种方法在这里详述:http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/