模組 URI

URI 是提供類別來處理統一資源識別碼 (RFC2396) 的模組。

功能

基本範例

require 'uri'

uri = URI("http://foo.com/posts?id=30&limit=5#time=1305298413")
#=> #<URI::HTTP http://foo.com/posts?id=30&limit=5#time=1305298413>

uri.scheme    #=> "http"
uri.host      #=> "foo.com"
uri.path      #=> "/posts"
uri.query     #=> "id=30&limit=5"
uri.fragment  #=> "time=1305298413"

uri.to_s      #=> "http://foo.com/posts?id=30&limit=5#time=1305298413"

新增自訂 URI

module URI
  class RSYNC < Generic
    DEFAULT_PORT = 873
  end
  register_scheme 'RSYNC', RSYNC
end
#=> URI::RSYNC

URI.scheme_list
#=> {"FILE"=>URI::File, "FTP"=>URI::FTP, "HTTP"=>URI::HTTP,
#    "HTTPS"=>URI::HTTPS, "LDAP"=>URI::LDAP, "LDAPS"=>URI::LDAPS,
#    "MAILTO"=>URI::MailTo, "RSYNC"=>URI::RSYNC}

uri = URI("rsync://rsync.foo.com")
#=> #<URI::RSYNC rsync://rsync.foo.com>

RFC 參考

檢視 RFC 規格的好地方是 www.ietf.org/rfc.html

以下是所有相關 RFC 的清單

Class

版權資訊

作者

Akira Yamada <[email protected]>

文件

Akira Yamada <[email protected]> Dmitry V. Sabanin <[email protected]> Vincent Batts <[email protected]>

授權

版權 © 2001 akira yamada <[email protected]> 您可以在與 Ruby 相同的條款下重新分發和/或修改它。

常數

DEFAULT_PARSER

URI::Parser.new

INITIAL_SCHEMES
Parser
REGEXP
RFC3986_PARSER
TBLENCURICOMP_

公開類別方法

decode_uri_component(str, enc=Encoding::UTF_8) 按一下以切換來源

類似於 URI.decode_www_form_component,但會保留 '+'

# File lib/uri/common.rb, line 379
def self.decode_uri_component(str, enc=Encoding::UTF_8)
  _decode_uri_component(/%\h\h/, str, enc)
end
decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false) 按一下以切換來源

傳回從給定的字串 str (必須為 ASCII 字串) 衍生的名稱/值對。

此方法可用於解碼 res['Content-Type']'application/x-www-form-urlencoded'Net::HTTPResponse 物件 res 的主體。

傳回的資料是一個 2 元素子陣列的陣列;每個子陣列都是一個名稱/值對 (兩者都是字串)。每個傳回的字串編碼為 enc,並且已透過 String#scrub 移除無效字元。

一個簡單的範例

URI.decode_www_form('foo=0&bar=1&baz')
# => [["foo", "0"], ["bar", "1"], ["baz", ""]]

傳回的字串具有一些轉換,類似於 URI.decode_www_form_component 中執行的轉換

URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40')
# => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]]

給定的字串可能包含連續的分隔符號

URI.decode_www_form('foo=0&&bar=1&&baz=2')
# => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]]

可以指定不同的分隔符號

URI.decode_www_form('foo=0--bar=1--baz', separator: '--')
# => [["foo", "0"], ["bar", "1"], ["baz", ""]]
# File lib/uri/common.rb, line 554
def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false)
  raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only?
  ary = []
  return ary if str.empty?
  enc = Encoding.find(enc)
  str.b.each_line(separator) do |string|
    string.chomp!(separator)
    key, sep, val = string.partition('=')
    if isindex
      if sep.empty?
        val = key
        key = +''
      end
      isindex = false
    end

    if use__charset_ and key == '_charset_' and e = get_encoding(val)
      enc = e
      use__charset_ = false
    end

    key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
    if val
      val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
    else
      val = +''
    end

    ary << [key, val]
  end
  ary.each do |k, v|
    k.force_encoding(enc)
    k.scrub!
    v.force_encoding(enc)
    v.scrub!
  end
  ary
end
decode_www_form_component(str, enc=Encoding::UTF_8) 按一下以切換來源

傳回從給定的 URL 編碼字串 str 解碼的字串。

給定的字串首先編碼為 Encoding::ASCII-8BIT (使用 String#b),然後解碼 (如下所述),最後強制編碼為給定的編碼 enc

傳回的字串

  • 保留

    • 字元 '*''.''-''_'

    • 範圍 'a'..'z''A'..'Z''0'..'9' 中的字元。

    範例

    URI.decode_www_form_component('*.-_azAZ09')
    # => "*.-_azAZ09"
    
  • 轉換

    • 字元 '+' 至字元 ' '

    • 每個「百分比符號」至一個 ASCII 字元。

    範例

    URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A')
    # => "Here are some punctuation characters: ,;?:"
    

相關:URI.decode_uri_component(保留 '+')。

# File lib/uri/common.rb, line 368
def self.decode_www_form_component(str, enc=Encoding::UTF_8)
  _decode_uri_component(/\+|%\h\h/, str, enc)
end
encode_uri_component(str, enc=nil) 按一下以切換來源

類似於 URI.encode_www_form_component,但將 ' '(空白)編碼為 '%20'(而非 '+')。

# File lib/uri/common.rb, line 374
def self.encode_uri_component(str, enc=nil)
  _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc)
end
encode_www_form(enum, enc=nil) 按一下以切換來源

傳回一個 URL 編碼字串,衍生自指定的 Enumerable enum

結果適合用於 HTTP 要求的表單資料,其 Content-Type'application/x-www-form-urlencoded'

傳回的字串包含 enum 的元素,每個元素轉換為一個或多個 URL 編碼字串,並全部以字元 '&' 連接。

簡單範例

URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]])
# => "foo=0&bar=1&baz=2"
URI.encode_www_form({foo: 0, bar: 1, baz: 2})
# => "foo=0&bar=1&baz=2"

傳回的字串使用 URI.encode_www_form_component 方法形成,該方法會轉換特定字元

URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@')
# => "f%23o=%2F&b-r=%24&b+z=%40"

enum 類似陣列時,每個元素 ele 會轉換為一個欄位

  • 如果 ele 是包含兩個或更多元素的陣列,欄位會從其前兩個元素形成(任何其他元素都會被忽略)

    name = URI.encode_www_form_component(ele[0], enc)
    value = URI.encode_www_form_component(ele[1], enc)
    "#{name}=#{value}"
    

    範例

    URI.encode_www_form([%w[foo bar], %w[baz bat bah]])
    # => "foo=bar&baz=bat"
    URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']])
    # => "foo=0&bar=baz"
    
  • 如果 ele 是包含一個元素的陣列,欄位會從 ele[0] 形成

    URI.encode_www_form_component(ele[0])
    

    範例

    URI.encode_www_form([['foo'], [:bar], [0]])
    # => "foo&bar&0"
    
  • 否則,欄位會從 ele 形成

    URI.encode_www_form_component(ele)
    

    範例

    URI.encode_www_form(['foo', :bar, 0])
    # => "foo&bar&0"
    

類似陣列的 enum 的元素可能是混合

URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat])
# => "foo=0&bar=1&baz&bat"

enum 類似雜湊時,每個 key/value 配對會轉換為一個或多個欄位

  • 如果 value類似陣列value 中的每個元素 ele 會與 key 配對以形成一個欄位

    name = URI.encode_www_form_component(key, enc)
    value = URI.encode_www_form_component(ele, enc)
    "#{name}=#{value}"
    

    範例

    URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]})
    # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2"
    
  • 否則,keyvalue 會配對以形成一個欄位

    name = URI.encode_www_form_component(key, enc)
    value = URI.encode_www_form_component(value, enc)
    "#{name}=#{value}"
    

    範例

    URI.encode_www_form({foo: 0, bar: 1, baz: 2})
    # => "foo=0&bar=1&baz=2"
    

類似雜湊的 enum 的元素可能是混合

URI.encode_www_form({foo: [0, 1], bar: 2})
# => "foo=0&foo=1&bar=2"
# File lib/uri/common.rb, line 501
def self.encode_www_form(enum, enc=nil)
  enum.map do |k,v|
    if v.nil?
      encode_www_form_component(k, enc)
    elsif v.respond_to?(:to_ary)
      v.to_ary.map do |w|
        str = encode_www_form_component(k, enc)
        unless w.nil?
          str << '='
          str << encode_www_form_component(w, enc)
        end
      end.join('&')
    else
      str = encode_www_form_component(k, enc)
      str << '='
      str << encode_www_form_component(v, enc)
    end
  end.join('&')
end
encode_www_form_component(str, enc=nil) 按一下以切換來源

傳回一個 URL 編碼字串,衍生自指定的字串 str

傳回的字串

  • 保留

    • 字元 '*''.''-''_'

    • 範圍 'a'..'z''A'..'Z''0'..'9' 中的字元。

    範例

    URI.encode_www_form_component('*.-_azAZ09')
    # => "*.-_azAZ09"
    
  • 轉換

    • 字元 ' ' 至字元 '+'

    • 任何其他字元至「百分比符號」;字元 c 的百分比符號為 '%%%X' % c.ord

    範例

    URI.encode_www_form_component('Here are some punctuation characters: ,;?:')
    # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A"
    

編碼

  • 如果 str 具有編碼 Encoding::ASCII_8BIT,則會忽略參數 enc

  • 否則,會先將 str 轉換為 Encoding::UTF_8(使用適當的字元替換),再轉換為編碼 enc

在任一種情況下,傳回的字串都強制使用編碼 Encoding::US_ASCII。

相關:URI.encode_uri_component(將 ' ' 編碼為 '%20')。

# File lib/uri/common.rb, line 335
def self.encode_www_form_component(str, enc=nil)
  _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc)
end
for(scheme, *arguments, default: Generic) 按一下以切換來源

傳回一個由指定的 schemeargumentsdefault 建構的新物件

  • 新物件是 URI.scheme_list[scheme.upcase] 的執行個體。

  • 使用 schemearguments 呼叫類別初始化函式來初始化物件。請參閱 URI::Generic.new

範例

values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top']
URI.for('https', *values)
# => #<URI::HTTPS https://[email protected]:123/forum/questions/?tag=networking&order=newest#top>
URI.for('foo', *values, default: URI::HTTP)
# => #<URI::HTTP foo://[email protected]:123/forum/questions/?tag=networking&order=newest#top>
# File lib/uri/common.rb, line 123
def self.for(scheme, *arguments, default: Generic)
  const_name = scheme.to_s.upcase

  uri_class = INITIAL_SCHEMES[const_name]
  uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false)
    Schemes.const_get(const_name, false)
  end
  uri_class ||= default

  return uri_class.new(scheme, *arguments)
end
join(*str) 按一下以切換來源

根據 RFC 2396 合併指定的 URI 字串 str

str 中的每個字串在合併之前會轉換為 RFC3986 URI

範例

URI.join("http://example.com/","main.rbx")
# => #<URI::HTTP http://example.com/main.rbx>

URI.join('http://example.com', 'foo')
# => #<URI::HTTP http://example.com/foo>

URI.join('http://example.com', '/foo', '/bar')
# => #<URI::HTTP http://example.com/bar>

URI.join('http://example.com', '/foo', 'bar')
# => #<URI::HTTP http://example.com/bar>

URI.join('http://example.com', '/foo/', 'bar')
# => #<URI::HTTP http://example.com/foo/bar>
# File lib/uri/common.rb, line 211
def self.join(*str)
  RFC3986_PARSER.join(*str)
end
open(name, *rest, &block) 按一下以切換來源

允許開啟各種資源,包括 URI。

如果第一個參數回應「open」方法,則會使用其餘參數對其呼叫「open」。

如果第一個參數是從 (protocol):// 開始的字串,則會由 URI.parse 解析。如果解析的物件回應「open」方法,則會使用其餘參數對其呼叫「open」。

否則,會呼叫 Kernel#open

OpenURI::OpenRead#open 提供 URI::HTTP#openURI::HTTPS#openURI::FTP#openKernel#open

我們可以接受 URI 和從 http://、https:// 和 ftp:// 開始的字串。在這些情況下,開啟的檔案物件會由 OpenURI::Meta 延伸。

呼叫超類別方法
# File lib/open-uri.rb, line 23
def self.open(name, *rest, &block)
  if name.respond_to?(:open)
    name.open(*rest, &block)
  elsif name.respond_to?(:to_str) &&
        %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
        (uri = URI.parse(name)).respond_to?(:open)
    uri.open(*rest, &block)
  else
    super
  end
end
parse(uri) 按一下以切換來源

傳回由給定字串 uri 建構的新 URI 物件

URI.parse('https://[email protected]:123/forum/questions/?tag=networking&order=newest#top')
# => #<URI::HTTPS https://[email protected]:123/forum/questions/?tag=networking&order=newest#top>
URI.parse('http://[email protected]:123/forum/questions/?tag=networking&order=newest#top')
# => #<URI::HTTP http://[email protected]:123/forum/questions/?tag=networking&order=newest#top>

建議先 ::escape 字串 uri,如果它可能包含無效的 URI 字元。

# File lib/uri/common.rb, line 184
def self.parse(uri)
  RFC3986_PARSER.parse(uri)
end
register_scheme(scheme, klass) 按一下以切換來源

註冊給定的 klass 作為在解析具有給定 scheme 的 URI 時要實例化的類別

URI.register_scheme('MS_SEARCH', URI::Generic) # => URI::Generic
URI.scheme_list['MS_SEARCH']                   # => URI::Generic

請注意,在 scheme 上呼叫 String#upcase 之後,它必須是有效的常數名稱。

# File lib/uri/common.rb, line 79
def self.register_scheme(scheme, klass)
  Schemes.const_set(scheme.to_s.upcase, klass)
end
scheme_list() 按一下以切換來源

傳回已定義的 scheme 的雜湊

URI.scheme_list
# =>
{"MAILTO"=>URI::MailTo,
 "LDAPS"=>URI::LDAPS,
 "WS"=>URI::WS,
 "HTTP"=>URI::HTTP,
 "HTTPS"=>URI::HTTPS,
 "LDAP"=>URI::LDAP,
 "FILE"=>URI::File,
 "FTP"=>URI::FTP}

相關:URI.register_scheme

# File lib/uri/common.rb, line 97
def self.scheme_list
  Schemes.constants.map { |name|
    [name.to_s.upcase, Schemes.const_get(name)]
  }.to_h
end
split(uri) 按一下以切換來源

傳回表示由字串 uri 形成的 URI 部分的 9 元素陣列;每個陣列元素都是字串或 nil

names = %w[scheme userinfo host port registry path opaque query fragment]
values = URI.split('https://[email protected]:123/forum/questions/?tag=networking&order=newest#top')
names.zip(values)
# =>
[["scheme", "https"],
 ["userinfo", "john.doe"],
 ["host", "www.example.com"],
 ["port", "123"],
 ["registry", nil],
 ["path", "/forum/questions/"],
 ["opaque", nil],
 ["query", "tag=networking&order=newest"],
 ["fragment", "top"]]
# File lib/uri/common.rb, line 170
def self.split(uri)
  RFC3986_PARSER.split(uri)
end

私人類別方法

_decode_uri_component(regexp, str, enc) 按一下以切換來源
# File lib/uri/common.rb, line 397
def self._decode_uri_component(regexp, str, enc)
  raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str)
  str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc)
end
_encode_uri_component(regexp, table, str, enc) 按一下以切換來源
# File lib/uri/common.rb, line 383
def self._encode_uri_component(regexp, table, str, enc)
  str = str.to_s.dup
  if str.encoding != Encoding::ASCII_8BIT
    if enc && enc != Encoding::ASCII_8BIT
      str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace)
      str.encode!(enc, fallback: ->(x){"&##{x.ord};"})
    end
    str.force_encoding(Encoding::ASCII_8BIT)
  end
  str.gsub!(regexp, table)
  str.force_encoding(Encoding::US_ASCII)
end