Rails: What's a good way to validate links (URLs)?
我想知道如何最好地验证Rails中的URL。 我当时在考虑使用正则表达式,但是不确定这是否是最佳实践。
而且,如果我要使用正则表达式,有人可以向我推荐一个吗? 我还是Regex的新手。
验证URL是一项棘手的工作。这也是一个非常广泛的要求。
您到底想做什么?您要验证URL的格式,存在性还是什么?根据您要做什么,有几种可能性。
正则表达式可以验证URL的格式。但是,即使是复杂的正则表达式也无法确保您正在处理有效的URL。
例如,如果采用简单的正则表达式,则它可能会拒绝以下主机
1 | http://invalid##host.com |
但它将允许
1 | http://invalid-host.foo |
如果您考虑现有的TLD,则它是有效的主机,但不是有效的域。确实,如果您要验证主机名而不是域,则该解决方案将起作用,因为以下主机名是有效的主机名
1 | http://host.foo |
以及以下一个
1 | http://localhost |
现在,让我给您一些解决方案。
如果要验证域,则需要忽略正则表达式。目前最好的解决方案是公共后缀列表,该列表由Mozilla维护。我创建了一个Ruby库来根据Public Suffix List解析和验证域,该库称为PublicSuffix。
如果要验证URI / URL的格式,则可能要使用正则表达式。而不是搜索一个,而是使用内置的Ruby
1 2 3 4 5 6 7 | require 'uri' def valid_url?(uri) uri = URI.parse(uri) && uri.host rescue URI::InvalidURIError false end |
您甚至可以决定使其更具限制性。例如,如果您希望URL为HTTP / HTTPS URL,则可以使验证更加准确。
1 2 3 4 5 6 7 8 | require 'uri' def valid_url?(url) uri = URI.parse(url) uri.is_a?(URI::HTTP) && !uri.host.nil? rescue URI::InvalidURIError false end |
当然,您可以对此方法进行大量改进,包括检查路径或方案。
最后但并非最不重要的一点是,您还可以将此代码打包到验证器中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 |
我在模型中使用了一根衬垫:
我认为它足够好并且易于使用。此外,它在理论上应该等效于Simone的方法,因为它在内部使用了完全相同的正则表达式。
遵循Simone的想法,您可以轻松创建自己的验证器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 |
然后使用
1 | validates :url, :presence => true, :url => true |
在您的模型中。
也有validate_url gem(它只是
只需添加
1 | gem 'validate_url' |
到
1 | validates :click_through_url, url: true |
这个问题已经回答了,但是到底我提出了我正在使用的解决方案。
regexp与我遇到的所有网址都可以正常工作。
设置方法是要注意是否未提及协议(假设http://)。
最后,我们尝试获取页面。也许我应该接受重定向,而不仅仅是HTTP 200 OK。
1 2 3 4 5 6 7 8 9 10 11 | # 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 |
和...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # 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 |
您也可以尝试valid_url gem,它允许不带方案的URL,检查域区域和ip-hostnames。
将其添加到您的Gemfile中:
然后在模型中:
1 2 3 | class WebSite < ActiveRecord::Base validates :url, :url => true end |
对我有用的解决方案是:
1 | validates_format_of :url, :with => /\\A(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w\\.-]*)*\\/?\\Z/i |
我确实尝试使用您所附的一些示例,但是我支持url如下:
注意使用A和Z,因为如果使用^和$,则会从Rails验证程序看到此警告安全性。
1 2 3 4 5 6 7 8 9 10 | Valid ones: 'www.crowdint.com' 'crowdint.com' 'http://crowdint.com' 'http://www.crowdint.com' Invalid ones: 'http://www.crowdint. com' 'http://fake' 'http:fake' |
只是我的2美分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
编辑:更改正则表达式以匹配参数url。
我最近遇到了同样的问题(我需要在Rails应用中验证URL),但是我不得不应付Unicode URL的其他要求(例如
我研究了几种解决方案,发现了以下问题:
-
首先也是最建议使用的是
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/
这是David James发布的验证器的更新版本。它已由本杰明·弗莱舍(Benjamin Fleischer)出版。同时,我推送了一个更新的fork,可以在这里找到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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 |
...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 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被解析为有效地址。
1 2 3 4 | http://google http://.com http://ftp://ftp.google.com http://ssh://google.com |
这是涵盖示例的
我在上面的lafeber解决方案上使用了一些细微的变化。
它不允许主机名中连续的点(例如
1 | %r"\\A(https?://)?[a-z\\d\\-]+(\\.[a-z\\d\\-]+)*\\.[a-z]{2,6}(/.*)?\\Z"i |
我一直在使用" activevalidators" gem,而且效果很好(不仅用于URL验证)
你可以在这里找到它
所有内容都已记录在案,但基本上,一旦添加了gem,就需要在初始化程序中添加以下几行:/config/environments/initializers/active_validators_activation.rb
1 2 | # Activate all the validators ActiveValidators.activate(:all) |
(注意:如果只想验证特定类型的值,则可以用:url或:what替换:all)
然后回到模型中
1 2 3 | class Url < ActiveRecord::Base validates :url, :presence => true, :url => true end |
现在重新启动服务器,应该是这样
如果您想要简单的验证和自定义错误消息:
1 2 3 4 5 | validates :some_field_expecting_url_value, format: { with: URI.regexp(%w[http https]), message: 'is not a valid URL' } |
我喜欢在URI模块上添加有效的猴子补丁吗?方法
内部
1 2 3 4 5 6 7 8 | module URI def self.valid?(url) uri = URI.parse(url) uri.is_a?(URI::HTTP) && !uri.host.nil? rescue URI::InvalidURIError false end end |
最近,我遇到了同样的问题,并且找到了有效网址的解决方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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存在。
您可以使用以下方法验证多个网址:
1 | validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true |
https://github.com/perfectline/validates_url是一个不错的简单宝石,它将为您做几乎所有的事情
作为一个模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
然后只需在要验证url的任何模型中输入
URL验证不能简单地通过使用正则表达式来处理,因为网站的数量在不断增长,新的域名命名方案也在不断涌现。
就我而言,我只是编写了一个自定义验证器来检查是否成功响应。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
我正在使用
您可以简单地将其替换为任何属性名称。
然后,我只需在模型中调用自定义验证器即可。
1 2 3 4 5 6 7 | class Url < ApplicationRecord # validations validates_presence_of :path validates_with UrlValidator end |
您可以为此使用正则表达式,对我来说,这很有效:
1 | (^|[\\s.:;?\\-\\]<\\(])(ftp|https?:\\/\\/[-\\w;\\/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,]+[\\w\\/#](\\(\\))?)(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)]) |