URI

URI

URI는 Uniform Resource Identifier( 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"

새로운 스킴(scheme)을 직접 등록할 수도 있어요. register_scheme로 클래스를 등록하면, 그 스킴을 가진 문자열을 파싱할 때 해당 클래스가 사용돼요.

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 사양을 직접 보시려면 www.ietf.org/rfc.html 을 참고하면 좋아요. 이 모듈과 관련된 RFC 목록이 정리되어 있으니까요.

출처: Ruby 3.3 API

본문

URI 모듈이 제공하는 주요 메서드를 하나씩 살펴볼게요.

decode_uri_component(str, enc = UTF_8)

URI.decode_www_form_component과 비슷하지만, '+'는 보존해요.

decode_www_form(str, sep = '&', use__charset_, isindex = false, enc = UTF_8) → array

주어진 문자열 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", ""]]

decode_www_form_component(str, enc = UTF_8) → string

URL 인코딩된 문자열 str을 디코딩한 문자열을 돌려줘요. 먼저 String#bEncoding::ASCII-8BIT로 인코딩한 뒤, 아래처럼 디코딩하고, 마지막으로 주어진 인코딩 enc로 강제 변환해요.

반환 문자열은:

  • '*', '.', '-', '_' 문자와 'a'..'z', 'A'..'Z', '0'..'9' 범위의 문자는 보존해요.
    URI.decode_www_form_component('*.-_azAZ09')
    # => "*.-_azAZ09"
    
  • '+'' '(공백)으로, 각 "percent notation"은 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( '+' 보존).

encode_uri_component(str, enc = UTF_8)

URI.encode_www_form_component과 비슷하지만, ' '(공백)을 '+' 대신 '%20'으로 인코딩해요.

encode_www_form(enum, enc = UTF_8) → string

주어진 Enumerable 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가 두 개 이상 요소의 배열이면 앞의 두 요소로 필드를 만들고(추가 요소는 무시):
    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([['foo'], [:bar], [0]])
    # => "foo&bar&0"
    
  • 그 외에는 ele 자체로 필드를 만들어요:
    URI.encode_www_form(['foo', :bar, 0])
    # => "foo&bar&0"
    

배열류 요소는 섞여 있어도 돼요:

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

enum이 해시처럼 생긴 경우, 키/값 쌍을 하나 이상의 필드로 변환하는데:

  • 값이 배열로 변환 가능하면, 값의 각 요소 ele를 키와 짝지어 필드를 만들고:
    URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]})
    # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2"
    
  • 그 외에는 키와 값을 짝지어 필드를 만들어요:
    URI.encode_www_form({foo: 0, bar: 1, baz: 2})
    # => "foo=0&bar=1&baz=2"
    

해시류도 요소가 섞일 수 있어요:

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

encode_www_form_component(str, enc = UTF_8) → string

주어진 문자열 str에서 URL 인코딩된 문자열을 만들어요.

반환 문자열은:

  • '*', '.', '-', '_' 문자와 'a'..'z', 'A'..'Z', '0'..'9' 범위 문자는 보존해요.
    URI.encode_www_form_component('*.-_azAZ09')
    # => "*.-_azAZ09"
    
  • ' '(공백)은 '+'로, 그 외 문자는 "percent notation"으로 변환해요. 문자 c의 percent notation은 '%%%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'으로 인코딩).

for(scheme, *arguments, default: nil) → uri

주어진 scheme, arguments, default로 새 객체를 만들어요:

  • 새 객체는 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://***@www.example.com: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>

join(*str) → uri

주어진 URI 문자열 str들을 RFC 2396 에 따라 합쳐요. 각 문자열은 합치기 전에 먼저 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>

open(name, *rest) → file

URI를 포함한 여러 자원을 여는 것을 허용해요.

  • 첫 인자가 open 메서드에 응답하면, 나머지 인자들과 함께 그 open을 호출해요.
  • 첫 인자가 (protocol)://로 시작하는 문자열이면 URI.parse로 파싱하고, 파싱된 객체가 open에 응답하면 나머지 인자들로 그 open을 호출해요.
  • 그 외에는 Kernel#open을 호출해요.

OpenURI::OpenRead#openURI::HTTP#open, URI::HTTPS#open, URI::FTP#open, Kernel#open을 제공해요. http://, https://, ftp://로 시작하는 URI와 문자열을 받을 수 있고, 이 경우 열린 파일 객체는 OpenURI::Meta로 확장돼요.

parse(uri) → uri

주어진 문자열 uri로 새 URI 객체를 만들어요:

URI.parse('https://***@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# => #<URI::HTTPS https://***@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
URI.parse('http://***@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# => #<URI::HTTP http://***@www.example.com:123/forum/questions/?tag=networking&order=newest#top>

uri에 유효하지 않은 URI 문자가 있을 수 있다면 먼저 ::escape를 사용하는 걸 권장해요.

register_scheme(scheme, klass) → klass

주어진 scheme의 URI를 파싱할 때 인스턴스화할 클래스로 klass를 등록해요:

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

schemeString#upcase를 적용한 뒤 그 결과가 유효한 상수 이름이어야 한다는 점을 기억하세요.

scheme_list → hash

정의된 스킴들의 해시를 돌려줘요:

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.

split(uri) → array

문자열 uri로 만든 URI의 구성 요소를 나타내는 9개짜리 배열을 돌려줘요. 각 요소는 문자열이거나 nil이에요:

names = %w[scheme userinfo host port registry path opaque query fragment]
values = URI.split('https://***@www.example.com: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"]]