it-swarm.com.ru

Rails: Какой хороший способ проверки ссылок (URL)?

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

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

118
jay

Проверка 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
132
Simone Carletti

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

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

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

96
Matteo Collina

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

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

в твоей модели.

52
jlfenaux

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

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

gem 'validate_url'

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

validates :click_through_url, url: true
25
dolzenko

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

Регулярное выражение отлично работает со всеми 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
14
Stefan Pettersson

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

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

gem 'valid_url'

И тогда в модели:

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

Просто мои 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
lafeber

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

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'
10
heriberto perez

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

Я исследовал пару решений и наткнулся на следующее:

  • Первая и наиболее рекомендуемая вещь - это использование URI.parse. Проверьте ответ Симоне Карлетти для деталей. Это работает нормально, но не для URL Unicode.
  • Второй метод, который я увидел, был Илья Григорик: 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/
5
severin

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

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

4
JJD

Я использую небольшое изменение решение Лафебера выше . Он запрещает последовательные точки в имени хоста (например, в 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)

3
Franco

Я использовал гем 'activevalidators' , и он работает довольно хорошо (не только для проверки URL)

вы можете найти это здесь

Это все задокументировано, но в основном после добавления гема вы захотите добавить следующие несколько строк в инициализаторе: /config/environments/initializers/active_validators_activation.rb

# Activate all the validators
ActiveValidators.activate(:all)

(Примечание: вы можете заменить: все на: url или: что угодно, если вы просто хотите проверить определенные типы значений)

А потом снова в вашей модели что-то вроде этого

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

Теперь Перезагрузите сервер и так и должно быть

2
Arnaud Bouchot

Вы можете проверить несколько URL, используя что-то вроде:

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

Недавно у меня возникла такая же проблема, и я нашел способ найти действительные 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

Первой части метода validate_url достаточно для проверки формата URL. Вторая часть будет гарантировать, что URL существует, отправив запрос.

1
Dilnavaz

https://github.com/perfectline/validates_url это красивый и простой гем, который сделает для вас почти все

1
stuartchaney

Если вам нужна простая проверка и пользовательское сообщение об ошибке:

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

Вы можете использовать регулярные выражения для этого, для меня это хорошо работает:

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

Проверка 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
0
Noman Ur Rehman

Мне понравилось monkeypatch модуль 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
0
Blair Anderson

И как модуль

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 в любой модели, для которой вы хотите проверить URL. Просто в том числе для вариантов.

0
MCB