Rails: jaki jest dobry sposób na walidację linków (adresów URL)?

Zastanawiałem się, jak najlepiej zweryfikować adresy URL w Rails. Myślałem o użyciu wyrażenia regularnego, ale nie jestem pewien, czy jest to najlepsza praktyka.

A gdybym miał użyć regex, czy ktoś mógłby mi go zasugerować? Wciąż jestem nowy w Regex.

Author: user489998, 2011-08-23

15 answers

Walidacja adresu URL jest trudnym zadaniem. To również bardzo szeroka Prośba.

Co dokładnie chcesz robić? Czy chcesz zweryfikować format adresu URL, istnienie lub co? Istnieje kilka możliwości, w zależności od tego, co chcesz zrobić.

Wyrażenie regularne może zweryfikować format adresu URL. Ale nawet złożone wyrażenie regularne nie może zapewnić, że masz do czynienia z prawidłowym adresem URL.

Na przykład, jeśli weźmiesz proste wyrażenie regularne, prawdopodobnie odrzuci ono następujący host

http://invalid##host.com

Ale pozwoli

http://invalid-host.foo

Jest to prawidłowy host, ale nie prawidłowa domena, jeśli wziąć pod uwagę istniejące TLD. Rzeczywiście, rozwiązanie będzie działać, jeśli chcesz zweryfikować nazwę hosta, a nie domenę, ponieważ następująca jest poprawna nazwa hosta

http://host.foo

Jak również następujące

http://localhost
Pozwól, że przedstawię Ci kilka rozwiązań.

Jeśli chcesz zweryfikować domenę, musisz zapomnieć o wyrażeniach regularnych. Najlepsze dostępne rozwiązanie obecnie jest to Public sufiks List, Lista utrzymywana przez Mozillę. Stworzyłem bibliotekę Ruby do analizy i walidacji domen względem publicznej listy sufiksów i nazywa się PublicSuffix.

Jeśli chcesz zweryfikować format URI / URL, możesz użyć wyrażeń regularnych. Zamiast szukać, użyj wbudowanej metody Ruby URI.parse.

require 'uri'

def valid_url?(uri)
  uri = URI.parse(uri) && uri.host
rescue URI::InvalidURIError
  false
end

Możesz nawet zdecydować się na bardziej restrykcyjne. Na przykład, jeśli chcesz, aby adres URL był adresem URL HTTP/HTTPS, następnie możesz zwiększyć dokładność walidacji.

require 'uri'

def valid_url?(url)
  uri = URI.parse(url)
  uri.is_a?(URI::HTTP) && !uri.host.nil?
rescue URI::InvalidURIError
  false
end

Oczywiście istnieje mnóstwo ulepszeń, które możesz zastosować do tej metody, w tym sprawdzanie ścieżki lub schematu.

Last but not least, you can also packaging this code into a validator:

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
 156
Author: Simone Carletti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-28 19:59:48

Używam jednej wkładki wewnątrz moich modeli:

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

Myślę, że jest wystarczająco dobry i prosty w użyciu. Ponadto powinna być teoretycznie równoważna metodzie Simone ' a, ponieważ używa wewnętrznie tego samego wyrażenia regularnego.
 107
Author: Matteo Collina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-05-25 15:05:44

Podążając za pomysłem Simone, możesz łatwo stworzyć własny walidator.

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

A następnie użyj

validates :url, :presence => true, :url => true
W twoim modelu.
 55
Author: jlfenaux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-25 11:43:12

Istnieje również validate_url gem (który jest po prostu ładnym opakowaniem dla Addressable::URI.parse rozwiązanie).

Po prostu dodaj

gem 'validate_url'

Do twojego Gemfile, a następnie w modelach możesz

validates :click_through_url, url: true
 30
Author: dolzenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-29 13:46:59

To pytanie jest już odpowiedział, Ale co mi tam, proponuję rozwiązanie, którego używam.

Wyrażenie regularne działa dobrze ze wszystkimi adresami URL, które spotkałem. Metoda setter ma dbać o to, czy żaden protokół nie jest wymieniony (Załóżmy http://).

I na koniec próbujemy pobrać stronę. Może powinienem zaakceptować przekierowania i nie tylko 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

I...

# 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
 13
Author: Stefan Pettersson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-05 14:18:58

Rozwiązanie, które zadziałało dla mnie było:

validates_format_of :url, :with => /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?\Z/i

Próbowałem użyć jakiegoś przykładu, który załączyłeś, ale wspieram url w ten sposób:

Zwróć uwagę na użycie a i z, ponieważ jeśli użyjesz ^ i $ zobaczysz to Ostrzeżenie z walidatorów 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'
 13
Author: heriberto perez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-26 12:42:21

Możesz również spróbować valid_url gem, który pozwala adresom URL bez schematu, sprawdza strefę domeny i nazwy ip-hostów.

Dodaj go do swojego Gemfile:

gem 'valid_url'

A następnie w modelu:

class WebSite < ActiveRecord::Base
  validates :url, :url => true
end
 12
Author: Roman Ralovets,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-18 23:07:13

Tylko moje 2 grosze:

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

EDIT: zmieniono wyrażenia regularne, aby dopasować adresy URL parametrów.

 10
Author: lafeber,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-03 08:36:22

Ostatnio napotkałem ten sam problem (musiałem zweryfikować adresy URL w aplikacji Rails), ale musiałem poradzić sobie z dodatkowym wymogiem Unicode (np. http://кц.рф)...

Zbadałem kilka rozwiązań i natknąłem się na następujące:

 5
Author: severin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-05 07:56:15

Oto zaktualizowana wersja walidatora opublikowanego przez Davida Jamesa . Został on opublikowany przez Benjamina Fleischera . W międzyczasie wcisnąłem zaktualizowany fork, który można znaleźć tutaj.

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

Proszę zauważyć, że nadal istnieją dziwne Uri HTTP, które są przetwarzane jako poprawne adresy.

http://google  
http://.com  
http://ftp://ftp.google.com  
http://ssh://google.com

Oto problem dla addressable gem , który obejmuje przykłady.

 4
Author: JJD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:03

Używam lekkiego wariantu na lafeber rozwiązanie powyżej. Wyłącza kolejne kropki w nazwie hosta (np. w www.many...dots.com):

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

URI.parse wydaje się, że nakazuje prefiks scheme, który w niektórych przypadkach nie jest tym, czego możesz chcieć (np. jeśli chcesz, aby użytkownicy mogli szybko przeliterować adresy URL w formularzach, takich jak twitter.com/username)

 3
Author: Franco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:44

Używałem kleju 'activevalidators' i działa całkiem dobrze (nie tylko do walidacji adresów URL)

Możesz go znaleźć tutaj

To wszystko jest udokumentowane, ale zasadniczo po dodaniu gem będziesz chciał dodać kilka następujących linii w inicjalizatorze powiedzieć: / config/environments/initializers / active_validators_activation.rb

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

(Uwaga: możesz zastąpić :all przez: url lub: whatever, jeśli chcesz tylko zweryfikować określone typy wartości)

Oraz potem w twoim modelu coś takiego

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

Teraz Uruchom ponownie Serwer i to powinno być to

 2
Author: Arnaud Bouchot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-07 15:04:24

Jeśli chcesz prostej walidacji i niestandardowego komunikatu o błędzie:

  validates :some_field_expecting_url_value,
            format: {
              with: URI.regexp(%w[http https]),
              message: 'is not a valid URL'
            }
 2
Author: Caleb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-17 23:13:44

Możesz zweryfikować wiele adresów URL używając czegoś takiego jak:

validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true
 1
Author: Damien Roche,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-28 16:17:46

Https://github.com/perfectline/validates_url to miły i prosty klejnot, który zrobi dla ciebie prawie wszystko

 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-27 22:55:04