Rails : 링크 (URL)의 유효성을 검사하는 좋은 방법은 무엇입니까?


125

Rails에서 URL을 가장 잘 검증하는 방법이 궁금합니다. 정규 표현식을 사용하려고 생각했지만 이것이 최선의 방법인지 확실하지 않습니다.

그리고 내가 정규식을 사용한다면 누군가 나에게 하나를 제안 할 수 있습니까? 나는 여전히 Regex를 처음 사용합니다.


답변:


151

URL 유효성 검사는 까다로운 작업입니다. 또한 매우 광범위한 요청입니다.

정확히 무엇을 하시겠습니까? URL 형식, 존재 여부 또는 무엇을 확인 하시겠습니까? 수행하려는 작업에 따라 몇 가지 가능성이 있습니다.

정규식은 URL 형식의 유효성을 검사 할 수 있습니다. 그러나 복잡한 정규식조차도 유효한 URL을 처리하고 있는지 확인할 수 없습니다.

예를 들어 간단한 정규 표현식을 사용하면 다음 호스트를 거부 할 것입니다.

http://invalid##host.com

그러나 그것은 허용 할 것입니다

http://invalid-host.foo

유효한 호스트이지만 기존 TLD를 고려할 경우 유효한 도메인이 아닙니다. 실제로 다음 항목이 유효한 호스트 이름이므로 도메인이 아닌 호스트 이름을 확인하려는 경우 솔루션이 작동합니다.

http://host.foo

다음 중 하나

http://localhost

이제 몇 가지 해결책을 드리겠습니다.

도메인의 유효성을 검사하려면 정규식을 잊어야합니다. 현재 사용 가능한 최상의 솔루션은 Mozilla에서 관리하는 목록 인 Public Suffix List입니다. Public Suffix List에 대해 도메인을 구문 분석하고 유효성을 검사하기 위해 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
수업은 URI::HTTPShttps uris (예 :URI.parse("https://yo.com").class => URI::HTTPS
tee

12
URI::HTTPS상속 URI:HTTP, 그것이 내가 사용하는 이유 kind_of?입니다.
Simone Carletti 2013 년

1
URL을 안전하게 검증하는 가장 완벽한 솔루션입니다.
Fabrizio Regini

4
URI.parse('http://invalid-host.foo')해당 URI가 유효한 URL이기 때문에 true를 반환합니다. 또한 .foo이제 유효한 TLD입니다. iana.org/domains/root/db/foo.html
시몬 Carletti

1
@jmccartie 전체 게시물을 읽으십시오. 스킴에 관심이 있다면 해당 라인뿐만 아니라 유형 검사도 포함하는 최종 코드를 사용해야합니다. 게시물이 끝나기 전에 읽기를 중단했습니다.
Simone Carletti 2015 년

101

내 모델 내부에 하나의 라이너를 사용합니다.

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

충분히 좋고 사용하기 쉽다고 생각합니다. 또한 내부적으로 동일한 정규 표현식을 사용하므로 이론적으로 Simone의 방법과 동일해야합니다.


17
불행히도 'http://'위의 패턴과 일치합니다. 참조 :URI::regexp(%w(http https)) =~ 'http://'
데이비드 J.

15
또한 같은 URL http:fake이 유효합니다.
nathanvda

54

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

모델에서.


1
이 수업은 어디에 두어야합니까? 이니셜 라이저에서?
deb

3
@gbc에서 인용합니다. "앱 / 유효성 검사기에 사용자 지정 유효성 검사기를 배치하면 config / application.rb 파일을 변경할 필요없이 자동으로로드됩니다." ( stackoverflow.com/a/6610270/839847 ). Stefan Pettersson의 아래 답변은 "app / validators"에서도 유사한 파일을 저장했음을 보여줍니다.
bergie3000 2012

4
이 단지 검사의 경우와 URL을 시작 http : // 또는 https : //, 그것은 적절한 URL 검증이 아니다
maggix

1
URL을 선택적으로 사용할 수있는 경우 종료 : class OptionalUrlValidator <UrlValidator def validate_each (record, attribute, value) return true if value.blank? 반환 슈퍼 엔드 끝
Dirty Henry

1
이것은 좋은 검증이 아닙니다 :URI("http:").kind_of?(URI::HTTP) #=> true
smathy

29

도 있습니다 validate_url 보석 단지 멋진 래퍼 (Addressable::URI.parse 솔루션).

그냥 추가

gem 'validate_url'

에 추가 Gemfile한 다음 모델에서

validates :click_through_url, url: true

@ ЕвгенийМасленков는 사양에 따라 유효하기 때문에 잘 될 수 있지만 github.com/sporkmonger/addressable/issues 를 확인하는 것이 좋습니다 . 또한 일반적인 경우에 아무도 표준을 따르지 않고 대신 간단한 형식 유효성 검사를 사용하고 있음을 발견했습니다.
dolzenko 2014-06-19

13

이 질문은 이미 답변되어 있지만 도대체 내가 사용하는 솔루션을 제안합니다.

정규식은 내가 만난 모든 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

정말 깔끔 해요! 귀하의 의견에 감사 드리며 문제에 대한 많은 접근 방식이 있습니다. 사람들이 자신의 것을 공유 할 때 좋습니다.
제이

6
Rails 보안 가이드 에 따르면 정규 표현식에서 $ ^ 대신 \ A 및 \ z를 사용해야 한다는 점을 지적하고 싶었습니다.
Jared

1
나는 그것을 좋아한다. 정규식을 유효성 검사기로 이동하여 코드를 약간 건조 시키라는 빠른 제안입니다. 모델간에 일관성이 있기를 원한다고 생각합니다. 보너스 : validate_each 아래에 첫 번째 줄을 놓을 수 있습니다.
Paul Pettengill 2013 년

URL이 오래 걸리고 시간이 초과되면 어떻게됩니까? 시간 초과 오류 메시지를 표시하거나 페이지를 열 수없는 경우 가장 좋은 옵션은 무엇입니까?
user588324

이것은 보안 감사를 통과하지 못할 것입니다. 서버가 임의의 URL을 찌르도록 만들고 있습니다
Mauricio

12

valid_url 을 사용해 볼 수도 있습니다.스키마없이 URL을 허용하고 도메인 영역과 ip-hostnames를 확인하는 gem을 .

Gemfile에 추가하십시오.

gem 'valid_url'

그리고 모델에서 :

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

이것은 매우 훌륭합니다. 특히 URI 클래스와 놀랍게도 관련된 스키마가없는 URL입니다.
Paul Pettengill

IP 기반 URL을 검색하고 가짜 URL을 탐지하는이 gem의 능력에 놀랐습니다. 감사!
오즈의

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

편집 : 매개 변수 URL과 일치하도록 정규식을 변경했습니다.


1
의견을 보내 주셔서 감사합니다. 항상 다른 솔루션을 만나서
jay

Btw, 귀하의 정규 표현식은 다음과 같은 쿼리 문자열이있는 유효한 URL을 거부합니다http://test.com/fdsfsdf?a=b
MikDiet

2
우리는이 코드를 프로덕션에 넣고 .match regex 라인의 무한 루프에서 계속 시간 초과를 얻었습니다. 왜 그런지 잘 모르겠지만, 일부 코너 케이스에주의를 기울이고 왜 이런 일이 발생하는지 다른 사람의 생각을 듣고 싶어합니다.
toobulkeh

10

나를 위해 일한 솔루션은 다음과 같습니다.

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

첨부 한 예제 중 일부를 사용하려고 시도했지만 다음과 같이 URL을 지원하고 있습니다.

^ 및 $를 사용하면 Rails 유효성 검사기에서이 경고 보안을 볼 수 있으므로 A와 Z를 사용하는 것에 유의하십시오.

 Valid ones:
 'www.crowdint.com'
 'crowdint.com'
 'http://crowdint.com'
 'http://www.crowdint.com'

 Invalid ones:
  'http://www.crowdint. com'
  'http://fake'
  'http:fake'

1
이것을보십시오 "https://portal.example.com/portal/#". Ruby 2.1.6에서는 평가가 중단됩니다.
Old Pro

당신은 바로 어떤 경우에는이 정규 표현식 :( 해결하기 위해 영원히 걸리는 것 같아있어
heriberto 페레즈

1
분명히 모든 시나리오를 다루는 정규식이 없기 때문에 간단한 유효성 검사 만 사용하게됩니다. validates : url, format : {with : URI.regexp}, if : Proc.new {| a | a.url.present? }
heriberto perez

5

최근에 동일한 문제가 발생했지만 (Rails 앱에서 URL의 유효성을 검사해야했습니다) 유니 코드 URL의 추가 요구 사항 (예 : http://кц.рф ...

몇 가지 솔루션을 조사하고 다음을 발견했습니다.

  • 첫 번째이자 가장 제안 된 것은 URI.parse. 자세한 내용은 Simone Carletti의 답변을 확인하십시오. 이것은 정상적으로 작동하지만 유니 코드 URL에는 적용되지 않습니다.
  • 두 번째 방법은 Ilya Grigorik의 방법입니다. http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/ 기본적으로 그는 url; 작동하면 유효합니다 ...
  • 내가 찾은 세 번째 방법 (그리고 내가 선호하는 방법)은 stdlib 대신 gem을 URI.parse사용하는 것과 비슷한 접근 방식 입니다. 이 접근 방식은 http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/에 자세히 설명되어 있습니다.addressableURI

그래,하지만 Addressable::URI.parse('http:///').scheme # => "http"Addressable::URI.parse('Съешь [же] ещё этих мягких французских булок да выпей чаю')완벽하게 :( 뷰의 어드레스로의 시점에서 확인된다
smileart

4

다음은 David James가 게시 한 유효성 검사기 의 업데이트 된 버전입니다 . 그것은 된 벤자민 플라이셔에 의해 출판 . 한편, 여기 에서 찾을 수있는 업데이트 된 포크를 푸시했습니다 .

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

다음은 예제를 다루는 gem에 대한 문제입니다addressable .


3

위의 lafeber 솔루션 에 약간의 변형을 사용합니다 . 호스트 이름에 연속 된 점 (예 :)을 허용하지 않습니다 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)


2

나는 'activevalidators'젬을 사용 하고 있으며 꽤 잘 작동합니다 (URL 유효성 검사뿐만 아니라)

여기에서 찾을 수 있습니다

모두 문서화되어 있지만 기본적으로 gem이 추가되면 이니셜 라이저에 다음과 같은 몇 줄을 추가 할 수 있습니다. /config/environments/initializers/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

이제 서버를 다시 시작 하고 그것을해야


2

간단한 유효성 검사 및 사용자 지정 오류 메시지를 원하는 경우 :

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

1

다음과 같은 방법으로 여러 URL을 확인할 수 있습니다.

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

1
스키마가없는 URL (예 : www.bar.com/foo)을 어떻게 처리 하시겠습니까?
craig


1

최근에 동일한 문제가 발생하여 유효한 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이 존재하는지 확인합니다.


URL이 매우 큰 (예 : 수 기가 바이트) 리소스를 가리키는 경우 어떻게됩니까?
존 슈나이더

@JonSchneider 하나 는 get 대신 http 헤드 요청 ( 여기 와 같은 )을 사용할 수 있습니다.
wvengen

1

유효한 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

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 UrlValidatorURL의 유효성을 검사하려는 모든 모델에서. 옵션을 포함합니다.


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

URL이 매우 큰 (예 : 수 기가 바이트) 리소스를 가리키는 경우 어떻게됩니까?
Jon Schneider

0

나를 위해 정규식을 사용할 수 있습니다.

(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.