我想知道如何最好地验证Rails中的URL。我正在考虑使用正则表达式,但我不确定这是否是最佳实践。
而且,如果我使用正则表达式,有人可以向我推荐一个吗?我还是Regex的新手。
答案 0 :(得分:132)
验证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.nil?
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
答案 1 :(得分:94)
我在模特中使用了一个衬垫:
validates :url, format: URI::regexp(%w[http https])
我认为它足够好并且易于使用。此外,它在理论上应该等同于Simone的方法,因为它在内部使用相同的正则表达式。
答案 2 :(得分:52)
遵循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
在您的模型中。
答案 3 :(得分:25)
还有validate_url gem(这只是Addressable::URI.parse
解决方案的一个很好的包装器。)
添加
gem 'validate_url'
到您的Gemfile
,然后在模型中
validates :click_through_url, url: true
答案 4 :(得分: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
和...
# 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
答案 5 :(得分: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
编辑:更改正则表达式以匹配参数网址。
答案 6 :(得分:10)
你也可以尝试valid_url gem,它允许没有方案的URL,检查域名区域和ip-hostnames。
将其添加到您的Gemfile:
gem 'valid_url'
然后在模特中:
class WebSite < ActiveRecord::Base
validates :url, :url => true
end
答案 7 :(得分:9)
对我有用的解决方案是:
validates_format_of :url, :with => /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?\Z/i
我确实尝试使用您附加的一些示例,但我支持这样的网址:
注意使用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'
答案 8 :(得分:5)
我最近遇到了同样的问题(我需要在Rails应用中验证网址)但我不得不应对unicode网址的额外要求(例如http://кц.рф
)...
我研究了几种解决方案,并发现了以下内容:
URI.parse
。有关详细信息,请查看Simone Carletti的答案。这可行,但不适用于unicode网址。URI.parse
的方法,但使用addressable
gem而不是URI
stdlib。这种方法详见:http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/ 答案 9 :(得分:4)
以下是validator posted by David James的更新版本。它一直是published by Benjamin Fleischer。与此同时,我推出了一个更新的分支,可以找到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。
答案 10 :(得分:3)
我对lafeber solution above略有不同。
它不允许主机名中的连续点(例如在www.many...dots.com
中):
%r"\A(https?://)?[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]{2,6}(/.*)?\Z"i
URI.parse
似乎要求使用方案前缀,这在某些情况下不是您想要的(例如,如果您希望允许您的用户快速拼写twitter.com/username
等形式的网址
答案 11 :(得分:2)
我一直在使用'activevalidators'的宝石,它的效果非常好(不仅仅是用于网址验证)
你可以找到它here
所有文档都有记录,但基本上一旦gem添加,你就想在初始化程序中添加以下几行:/config/environments/initializers/active_validators_activation.rb
# Activate all the validators
ActiveValidators.activate(:all)
(注意:您可以替换:all by:url或:如果您只是想验证特定类型的值,则无论如何)
然后回到你的模型中这样的事情
class Url < ActiveRecord::Base
validates :url, :presence => true, :url => true
end
现在重启服务器,应该是
答案 12 :(得分:1)
https://github.com/perfectline/validates_url是一个很好的,简单的宝石,可以为你做几乎所有的事情
答案 13 :(得分:1)
如果您想要简单的验证和自定义错误消息:
validates :some_field_expecting_url_value,
format: {
with: URI.regexp(%w[http https]),
message: 'is not a valid URL'
}
答案 14 :(得分:1)
最近我遇到了同样的问题,我找到了解决有效网址的问题。
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存在。
答案 15 :(得分:1)
您可以使用以下内容验证多个网址:
validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true
答案 16 :(得分:0)
作为一个模块
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
。仅包括选项。
答案 17 :(得分:0)
使用正则表达式无法简单地处理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
我正在使用path
验证模型的record.path
属性。我也使用record.errors[:path]
将错误推送到相应的属性名称。
您可以使用任何属性名称替换它。
然后,我只是在我的模型中调用自定义验证器。
class Url < ApplicationRecord
# validations
validates_presence_of :path
validates_with UrlValidator
end
答案 18 :(得分:0)
你可以使用正则表达式,因为我这个很好用:
(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])
答案 19 :(得分:0)
我喜欢在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
答案 20 :(得分:-1)
使用URI正则表达式URI::DEFAULT_PARSER.make_regexp