模块 URI

URI 是一个模块,提供处理统一资源标识符的类 (RFC2396)。

特性

  • 处理 URI 的统一方式。

  • 引入自定义 URI 方案的灵活性。

  • 具有备用 URI::Parser(或只是不同的模式和正则表达式)的灵活性。

基本示例

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 的列表

类树

版权信息

作者

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
INITIAL_SCHEMES
RFC2396_PARSER
RFC3986_PARSER
TBLENCURICOMP_

公共类方法

const_missing(const) 点击切换源
调用超类方法
# File uri/common.rb, line 43
def self.const_missing(const)
  if const == :REGEXP
    warn "URI::REGEXP is obsolete. Use URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE
    URI::RFC2396_REGEXP
  elsif value = RFC2396_PARSER.regexp[const]
    warn "URI::#{const} is obsolete. Use RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE
    value
  elsif value = RFC2396_Parser.const_get(const)
    warn "URI::#{const} is obsolete. Use RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE
    value
  else
    super
  end
end
decode_uri_component(str, enc=Encoding::UTF_8) 点击切换源

类似于 URI.decode_www_form_component,但会保留 '+'

# File uri/common.rb, line 402
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 字符串。

该方法可用于解码 Net::HTTPResponse 对象 res 的主体,其中 res['Content-Type']'application/x-www-form-urlencoded'

返回的数据是一个 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 uri/common.rb, line 577
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 uri/common.rb, line 391
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 uri/common.rb, line 397
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) 点击切换源

返回从给定的可枚举 enum 派生的 URL 编码的字符串。

该结果适合用作 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 uri/common.rb, line 524
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) 点击切换源

返回从给定字符串 str 派生的 URL 编码的字符串。

返回的字符串

  • 保留

    • 字符 '*''.''-''_'

    • 范围 '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 uri/common.rb, line 358
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 uri/common.rb, line 146
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 uri/common.rb, line 234
def self.join(*str)
  DEFAULT_PARSER.join(*str)
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 uri/common.rb, line 207
def self.parse(uri)
  DEFAULT_PARSER.parse(uri)
end
parser=(parser = RFC3986_PARSER) 点击切换源
# File uri/common.rb, line 25
def self.parser=(parser = RFC3986_PARSER)
  remove_const(:Parser) if defined?(::URI::Parser)
  const_set("Parser", parser.class)

  remove_const(:REGEXP) if defined?(::URI::REGEXP)
  remove_const(:PATTERN) if defined?(::URI::PATTERN)
  if Parser == RFC2396_Parser
    const_set("REGEXP", URI::RFC2396_REGEXP)
    const_set("PATTERN", URI::RFC2396_REGEXP::PATTERN)
  end

  Parser.new.regexp.each_pair do |sym, str|
    remove_const(sym) if const_defined?(sym, false)
    const_set(sym, str)
  end
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 uri/common.rb, line 102
def self.register_scheme(scheme, klass)
  Schemes.const_set(scheme.to_s.upcase, klass)
end
scheme_list() 点击切换源

返回已定义方案的哈希值

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 uri/common.rb, line 120
def self.scheme_list
  Schemes.constants.map { |name|
    [name.to_s.upcase, Schemes.const_get(name)]
  }.to_h
end
split(uri) 点击切换源

返回一个 9 元素数组,表示从字符串 uri 形成的 URI 的各个部分;每个数组元素都是一个字符串或 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 uri/common.rb, line 193
def self.split(uri)
  DEFAULT_PARSER.split(uri)
end

私有类方法

_decode_uri_component(regexp, str, enc) 点击切换源
# File uri/common.rb, line 420
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 uri/common.rb, line 406
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