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

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

如果我要使用正则表达式,有人能给我推荐一个吗? 我对正则表达式还是个新手。

102433 次浏览

确认 URL 是一项棘手的工作,也是一个非常宽泛的要求。

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

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

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

http://invalid##host.com

但它会允许

http://invalid-host.foo

这是一个有效的主机,但如果考虑到现有的 TLD,则不是一个有效的域。实际上,如果您想要验证主机名,而不是域,那么这个解决方案就可以工作,因为下面这个是一个有效的主机名

http://host.foo

还有下面这个

http://localhost

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

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

如果您想验证 URI/URL 的格式,那么可能需要使用正则表达式。使用内置的 RubyURI.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

我在我的模型中使用一行:

validates :url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https])

我认为是足够好的和简单的使用。此外,它在理论上应该等同于 Simone 的方法,因为它在内部使用非常相同的 regexp。

按照 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

在你的模型里。

这个问题已经得到了回答,但管他呢,我提出了我正在使用的解决方案。

Regexp 对于我遇到的所有 url 都能正常工作。 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

您可以使用以下方法验证多个 url:

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

我只想说:

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 的额外需求(例如 http://кц.рф) ..。

我研究了一些解决方案,得出了以下结论:

下面是 验证程序由大卫 · 詹姆斯发布的更新版本。是 由 Benjamin Fleischer 出版。与此同时,我推出了一个更新的分叉,可以找到 给你

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

下面是一个涵盖了这些例子的 发行 addressable宝石

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

只要加上

gem 'validate_url'

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

validates :click_through_url, url: true

我在 上述拉斐尔溶液上使用了一个微小的变化。 它不允许主机名中的连续点(例如在 www.many...dots.com中) :

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

URI.parse似乎强制使用方案前缀,在某些情况下,这可能不是您想要的(例如,如果您希望允许您的用户快速拼写表单中的 URL,如 twitter.com/username)

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

您也可以尝试 Valid _ url gem,它允许没有方案的 URL,检查域区域和 ip 主机名。

将它添加到你的 Gemfile:

gem 'valid_url'

然后是模型:

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

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

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

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

作为一个模块

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在任何模型,你想验证网址的。只包括选项。

对我有效的解决办法是:

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'

我一直在使用 Activevalidators’gem,它的工作非常好(不仅仅是网址验证)

你可以找到它 给你

这些都有文档说明,但是基本上,一旦添加了 gem,您就需要在初始化器中添加以下几行:/config/環境/初始化器/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

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

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

你可以使用正则表达式,对我来说这个很好用:

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

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

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

我喜欢对 URI 模块进行 monkeypatch,以添加有效的? 方法

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

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

相反,使用 URI::DEFAULT_PARSER.make_regexp(%w[http https])

保持简单:

validates :url, format: %r{http(s)://.+}