📄 open-uri.rb
字号:
#= open-uri.rb##open-uri.rb is easy-to-use wrapper for net/http, net/https and net/ftp.##== Example##It is possible to open http/https/ftp URL as usual a file:## open("http://www.ruby-lang.org/") {|f|# f.each_line {|line| p line}# }##The opened file has several methods for meta information as follows since#it is extended by OpenURI::Meta.## open("http://www.ruby-lang.org/en") {|f|# f.each_line {|line| p line}# p f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/># p f.content_type # "text/html"# p f.charset # "iso-8859-1"# p f.content_encoding # []# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002# }##Additional header fields can be specified by an optional hash argument.## open("http://www.ruby-lang.org/en/",# "User-Agent" => "Ruby/#{RUBY_VERSION}",# "From" => "foo@bar.invalid",# "Referer" => "http://www.ruby-lang.org/") {|f|# ...# }##The environment variables such as http_proxy, https_proxy and ftp_proxy#are in effect by default. :proxy => nil disables proxy.## open("http://www.ruby-lang.org/en/raa.html",# :proxy => nil) {|f|# ...# }##URI objects can be opened in similar way.## uri = URI.parse("http://www.ruby-lang.org/en/")# uri.open {|f|# ...# }##URI objects can be read directly.#The returned string is also extended by OpenURI::Meta.## str = uri.read# p str.base_uri##Author:: Tanaka Akira <akr@m17n.org>#--# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.# All rights reserved.# See LICENSE.txt for permissions.#++require 'uri'require 'stringio'require 'time'module Kernel private alias open_uri_original_open open # :nodoc: # makes possible to open various resources including URIs. # If the first argument respond to `open' method, # the method is called with the rest arguments. # # If the first argument is a string which begins with xxx://, # it is parsed by URI.parse. If the parsed object respond to `open' method, # the method is called with the rest arguments. # # Otherwise original open is called. # # Since open-uri.rb provides URI::HTTP#open, URI::HTTPS#open and # URI::FTP#open, # Kernel[#.]open can accepts such URIs and strings which begins with # http://, https:// and ftp://. # In these case, the opened file object is extended by OpenURI::Meta. def open(name, *rest, &block) # :doc: 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 open_uri_original_open(name, *rest, &block) end end module_function :openendmodule OpenURI Options = { :proxy => true, :proxy_http_basic_authentication => true, :progress_proc => true, :content_length_proc => true, :http_basic_authentication => true, :read_timeout => true, } def OpenURI.check_options(options) # :nodoc: options.each {|k, v| next unless Symbol === k unless Options.include? k raise ArgumentError, "unrecognized option: #{k}" end } end def OpenURI.scan_open_optional_arguments(*rest) # :nodoc: if !rest.empty? && (String === rest.first || Integer === rest.first) mode = rest.shift if !rest.empty? && Integer === rest.first perm = rest.shift end end return mode, perm, rest end def OpenURI.open_uri(name, *rest) # :nodoc: uri = URI::Generic === name ? name : URI.parse(name) mode, perm, rest = OpenURI.scan_open_optional_arguments(*rest) options = rest.shift if !rest.empty? && Hash === rest.first raise ArgumentError.new("extra arguments") if !rest.empty? options ||= {} OpenURI.check_options(options) unless mode == nil || mode == 'r' || mode == 'rb' || mode == File::RDONLY raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)") end io = open_loop(uri, options) if block_given? begin yield io ensure io.close end else io end end def OpenURI.open_loop(uri, options) # :nodoc: proxy_opts = [] proxy_opts << :proxy_http_basic_authentication if options.include? :proxy_http_basic_authentication proxy_opts << :proxy if options.include? :proxy proxy_opts.compact! if 1 < proxy_opts.length raise ArgumentError, "multiple proxy options specified" end case proxy_opts.first when :proxy_http_basic_authentication opt_proxy, proxy_user, proxy_pass = options.fetch(:proxy_http_basic_authentication) proxy_user = proxy_user.to_str proxy_pass = proxy_pass.to_str if opt_proxy == true raise ArgumentError.new("Invalid authenticated proxy option: #{options[:proxy_http_basic_authentication].inspect}") end when :proxy opt_proxy = options.fetch(:proxy) proxy_user = nil proxy_pass = nil when nil opt_proxy = true proxy_user = nil proxy_pass = nil end case opt_proxy when true find_proxy = lambda {|u| pxy = u.find_proxy; pxy ? [pxy, nil, nil] : nil} when nil, false find_proxy = lambda {|u| nil} when String opt_proxy = URI.parse(opt_proxy) find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]} when URI::Generic find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]} else raise ArgumentError.new("Invalid proxy option: #{opt_proxy}") end uri_set = {} buf = nil while true redirect = catch(:open_uri_redirect) { buf = Buffer.new uri.buffer_open(buf, find_proxy.call(uri), options) nil } if redirect if redirect.relative? # Although it violates RFC2616, Location: field may have relative # URI. It is converted to absolute URI using uri as a base URI. redirect = uri + redirect end unless OpenURI.redirectable?(uri, redirect) raise "redirection forbidden: #{uri} -> #{redirect}" end if options.include? :http_basic_authentication # send authentication only for the URI directly specified. options = options.dup options.delete :http_basic_authentication end uri = redirect raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s uri_set[uri.to_s] = true else break end end io = buf.io io.base_uri = uri io end def OpenURI.redirectable?(uri1, uri2) # :nodoc: # This test is intended to forbid a redirection from http://... to # file:///etc/passwd. # However this is ad hoc. It should be extensible/configurable. uri1.scheme.downcase == uri2.scheme.downcase || (/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:http|ftp)\z/i =~ uri2.scheme) end def OpenURI.open_http(buf, target, proxy, options) # :nodoc: if proxy proxy_uri, proxy_user, proxy_pass = proxy raise "Non-HTTP proxy URI: #{proxy_uri}" if proxy_uri.class != URI::HTTP end if target.userinfo && "1.9.0" <= RUBY_VERSION # don't raise for 1.8 because compatibility. raise ArgumentError, "userinfo not supported. [RFC3986]" end header = {} options.each {|k, v| header[k] = v if String === k } require 'net/http' klass = Net::HTTP if URI::HTTP === target # HTTP or HTTPS if proxy if proxy_user && proxy_pass klass = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_user, proxy_pass) else klass = Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port) end end target_host = target.host target_port = target.port request_uri = target.request_uri else # FTP over HTTP proxy target_host = proxy_uri.host target_port = proxy_uri.port request_uri = target.to_s if proxy_user && proxy_pass header["Proxy-Authorization"] = 'Basic ' + ["#{proxy_user}:#{proxy_pass}"].pack('m').delete("\r\n") end end http = klass.new(target_host, target_port) if target.class == URI::HTTPS require 'net/https' http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER store = OpenSSL::X509::Store.new store.set_default_paths http.cert_store = store end if options.include? :read_timeout http.read_timeout = options[:read_timeout] end resp = nil http.start { if target.class == URI::HTTPS # xxx: information hiding violation sock = http.instance_variable_get(:@socket) if sock.respond_to?(:io) sock = sock.io # 1.9 else sock = sock.instance_variable_get(:@socket) # 1.8 end sock.post_connection_check(target_host) end req = Net::HTTP::Get.new(request_uri, header) if options.include? :http_basic_authentication user, pass = options[:http_basic_authentication] req.basic_auth user, pass end http.request(req) {|response| resp = response if options[:content_length_proc] && Net::HTTPSuccess === resp if resp.key?('Content-Length') options[:content_length_proc].call(resp['Content-Length'].to_i) else options[:content_length_proc].call(nil) end end resp.read_body {|str| buf << str if options[:progress_proc] && Net::HTTPSuccess === resp options[:progress_proc].call(buf.size) end } } } io = buf.io io.rewind io.status = [resp.code, resp.message] resp.each {|name,value| buf.io.meta_add_field name, value } case resp when Net::HTTPSuccess when Net::HTTPMovedPermanently, # 301 Net::HTTPFound, # 302 Net::HTTPSeeOther, # 303 Net::HTTPTemporaryRedirect # 307 throw :open_uri_redirect, URI.parse(resp['location']) else raise OpenURI::HTTPError.new(io.status.join(' '), io) end end class HTTPError < StandardError def initialize(message, io) super(message) @io = io end attr_reader :io end class Buffer # :nodoc: def initialize @io = StringIO.new @size = 0 end attr_reader :size StringMax = 10240 def <<(str) @io << str @size += str.length if StringIO === @io && StringMax < @size require 'tempfile' io = Tempfile.new('open-uri') io.binmode Meta.init io, @io if @io.respond_to? :meta io << @io.string @io = io end end def io Meta.init @io unless @io.respond_to? :meta @io end end # Mixin for holding meta-information. module Meta def Meta.init(obj, src=nil) # :nodoc: obj.extend Meta obj.instance_eval { @base_uri = nil @meta = {} }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -