Rails: Какой хороший способ проверки ссылок (URL)? - PullRequest
118 голосов
/ 24 августа 2011

Мне было интересно, как мне лучше всего проверить URL в Rails. Я думал об использовании регулярного выражения, но не уверен, что это лучшая практика.

И, если бы я использовал регулярное выражение, мог бы кто-нибудь мне его предложить? Я все еще новичок в Regex.

Ответы [ 20 ]

132 голосов
/ 24 августа 2011

Проверка URL - сложная задача. Это также очень широкий запрос.

Что именно вы хотите сделать? Вы хотите проверить формат URL, существование или что? Есть несколько возможностей, в зависимости от того, что вы хотите сделать.

Регулярное выражение может проверять формат URL. Но даже сложное регулярное выражение не может гарантировать, что вы имеете дело с действительным URL.

Например, если вы берете простое регулярное выражение, оно, вероятно, отклонит следующий хост

http://invalid##host.com

но это позволит

http://invalid-host.foo

это действительный хост, но не действительный домен, если вы рассматриваете существующие TLD. Действительно, решение будет работать, если вы хотите проверить имя хоста, а не домен, потому что следующее является допустимым именем хоста

http://host.foo

а также следующий

http://localhost

Теперь позвольте мне дать вам несколько решений.

Если вы хотите проверить домен, то вам нужно забыть о регулярных выражениях. Наилучшее решение, доступное на данный момент, - Public Suffix List, список, поддерживаемый 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 был URL-адресом HTTP / HTTPS, вы можете сделать проверку более точной.

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
97 голосов
/ 24 августа 2011

Я использую один вкладыш внутри своих моделей:

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

Я думаю, что это достаточно хорошо и просто в использовании. Кроме того, он должен быть теоретически эквивалентен методу Симона, так как он использует тот же самый регулярное выражение внутри.

52 голосов
/ 28 января 2012

Следуя идее Симоны, вы можете легко создать свой собственный валидатор.

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

в вашей модели.

25 голосов
/ 29 октября 2013

Существует также validate_url gem (это просто хорошая оболочка для решения Addressable::URI.parse).

Просто добавьте

gem 'validate_url'

на Gemfile, а затем в моделях вы можете

validates :click_through_url, url: true
14 голосов
/ 05 ноября 2012

На этот вопрос уже дан ответ, но, черт возьми, я предлагаю решение, которое я использую.

Регулярное выражение отлично работает со всеми встреченными URL. Метод setter должен позаботиться о том, чтобы протокол не упоминался (предположим, http://).

И, наконец, мы пытаемся получить страницу. Может быть, я должен принимать перенаправления, а не только HTTP 200 ОК.

# 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
11 голосов
/ 19 ноября 2014

Вы также можете попробовать valid_url gem, который позволяет URL-адреса без схемы, проверяет доменную зону и имена хостов ip.

Добавьте его в свой Gemfile:

gem 'valid_url'

А потом в модели:

class WebSite < ActiveRecord::Base
  validates :url, :url => true
end
10 голосов
/ 13 мая 2013

Только мои 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

РЕДАКТИРОВАТЬ: изменено регулярное выражение для соответствия URL параметров.

10 голосов
/ 26 июня 2015

Решение, которое работало для меня, было:

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'
5 голосов
/ 05 июля 2013

В последнее время я столкнулся с той же проблемой (мне нужно было проверить URL-адреса в приложении Rails), но мне пришлось справиться с дополнительным требованием URL-адресов Unicode (например, http://кц.рф) ...

Я исследовал пару решений и обнаружил следующее:

  • Первая и наиболее рекомендуемая вещь - это использование URI.parse. Проверьте ответ Симоне Карлетти для деталей. Это работает нормально, но не для URL-адресов Юникода.
  • Второй метод, который я увидел, был у Ильи Григорика: http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/ По сути, он пытается сделать запрос к URL; если это работает, это действительно ...
  • Третий метод, который я нашел (и тот, который я предпочитаю), - это подход, аналогичный URI.parse, но использующий гем addressable вместо URI stdlib. Этот подход подробно описан здесь: http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/
4 голосов
/ 17 октября 2013

Вот обновленная версия валидатора , опубликованного Дэвидом Джеймсом . Это было опубликовано Бенджамином Флейшером . Тем временем я нажал обновленный форк, который можно найти здесь .

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 gem , которая охватывает примеры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...