Ruby + Mosaic = Rusaic ブラウザを作ってみた(gzip対応)
結構前に作りはじめて、そこそこ安定してきたのでこっそり公開。対話的に使えるわけではなく、プログラム中から気軽にhttp/httpsアクセスしたいというのが目的。
今から見るとちょっとRubyの機能を使いこなしていない書き方や、意図の不明なコメントもちらほらあるけど(「とりあえず」ってなんだ!?)
一番単純な使い方
require "rusaic.rb" browser = Rusaic.new browser.request("http://www.google.com") puts browser.body
出力
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>302 Moved</TITLE></HEAD><BODY> <H1>302 Moved</H1> The document has moved <A HREF="http://www.google.co.jp/">here</A>. </BODY></HTML>
メソッドの説明は下の方にあります。
ソースコード
rusaic.rb
$KCODE = "utf-8" require 'net/http' require 'net/https' require 'date' require 'stringio' require 'zlib' Rusaic_basic_req_head={ "User-Agent" => %q|Mozilla/5.0 (X11; U; Linux i686; ja; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3|, "Accept" => %q|text/html,application/xrds+xml,application/json,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8|, "Accept-Language" => %q|ja,en-us;q=0.7,en;q=0.3|, "Accept-Encoding" => %q|gzip,deflate|, "Accept-Charset" => %q|Shift_JIS,utf-8;q=0.7,*;q=0.7|, "Keep-Alive" => "115", "Connection" => "keep-alive" } class Rusaic def initialize(verify_depth=5, ex_cookie="") @depth = verify_depth @cookie = Array.new #cookieは配列の配列 ex_cookie.split(/;/).each{|e| @cookie << [e,""] } @res @req_head = Hash.new("") @res_body="" @protocol @domain @path @encoding="" @charset = "" @href = Array.new @src = Array.new @link = Array.new @req_head = Rusaic_basic_req_head end attr_reader :charset, :domain, :path, :href, :src, :link, :cookie, :res, :encoding attr_accessor :req_head #ここから、プライベート private def parse_cookie(str) return nil if str == nil # str.gsub!(/;,/, ';') str.gsub!(/(Mon|Tue|Wed|Thu|Fri|Sat|Sun),/){|match| match[0,3] + 'c' } #日付表示の中の","をcに置換 arr = str.split(/,/) #,で分割 arr.each{|e| e.gsub!(/(Mon|Tue|Wed|Thu|Fri|Sat|Sun)c/){|match| match[0,3] + ',' }#cにした,を元に戻す a = e.split(/;/) @cookie << a } return arr end def set_cookie c = Array.new @cookie.each{|e| c << e[0].strip } @req_head['Cookie'] = c.join(";") end def parse_url(url) col = url.split(/:\/\//) @protocol = col[0] @domain = col[1][0..col[1].index("/")-1] @path = col[1][col[1].index("/")..col[1].length] return @protocol, @domain, @path end def parse_charset(str) return nil if str==nil||str==""||!(str=~/;/) return str.split(/;/)[1].split(/=/)[1].downcase end def parse_body#とりあえず @src.clear @href.clear @link.clear @charset = parse_charset(@res['Content-Type']) @encoding = @res['content-encoding'] @res.body.each_line{|l| @src << l if l =~ /src/ @href << l if l =~ /<a.+href/ @link << l if l =~ /<link/ } @charset end def hash2str(hash) str = "" hash.each{|k,v| str += k+"="+v.to_s+"&" } return str end def get_ssl(domain, path) https = Net::HTTP.new(domain, 443) https.use_ssl = true https.verify_mode = OpenSSL::SSL::VERIFY_NONE https.verify_depth = @depth https.start{|w| @res = w.get(path, @req_head) } return @res end def post_ssl(domain, path, content) https = Net::HTTP.new(domain, 443) https.use_ssl = true https.verify_mode = OpenSSL::SSL::VERIFY_NONE https.verify_depth = @depth https.start{|w| @res = w.post(path, content, @req_head) } return @res end def get_normal(domain, path) Net::HTTP.start(domain, 80){|http| @res = http.get(path, @req_head) } return @res end def post_normal(domain, path, content) Net::HTTP.start(domain, 80){|http| @res = http.post(path, content, @req_head) } return @res end def get if @protocol=="http" @res = get_normal(@domain, @path) elsif @protocol=="https" @res = get_ssl(@domain, @path) else end @res end def post(content) if @protocol=="http" @res = post_normal(@domain, @path, content) elsif @protocol=="https" @res = post_ssl(@domain, @path, content) else end @res end #以下、パブリック public def add_cookie(a) @cookie << a end def request(url="", content=nil) return if url=="" set_cookie parse_url(url) puts url if $DEBUG begin if content==nil @res = get else content = hash2str(content) if content.class == Hash @req_head['Content-Length'] = content.length.to_s @res = post(content) @req_head.delete("Content-Length") end rescue StandardError => ex str = ex.backtrace.to_s raise StandardError, "Rusaic Error : "+ex.message+str rescue Timeout::Error => ex raise StandardError, "Rusaic Timeout : "+ex.message end parse_cookie(@res['Set-Cookie']) parse_body if @encoding == "gzip" StringIO.open(@res.body, 'rb'){|sio| @res_body = Zlib::GzipReader.wrap(sio).read } else @res_body = @res.body end puts "GOT:"+url if $DEBUG @res end def body @res_body end def dump puts "@res:" puts @res @res.each{|k,v| puts k+":"+v } puts "@req_head" @req_head.each{|k,v| puts k+":"+v } end end
クラスの説明
スーパークラス
Object
例外
タイムアウトも含めてStandardErrorとして返します。
クラスメソッド
Rusaic.new([verify_depth=5, [ex_cookie=""]])
- verify_depth・・・net/httpsのverify_depthに渡す値
- ex_cookie・・・手動で設定したいcookie・・・"I_do_Javascript=yes" とか
メソッド
- request(url [,content])
- body
- レスポンスボディを返します。
- dump
- 現在ブラウザ内部で保持している情報を一気に表示します。
- リーダー
- アクセサ
- req_head・・ブラウザのリクエストヘッダ