DesignAssembler

備忘録に近い

httpクライアントの実装(2)

続きです。

hyottokoaloha.hatenablog.com

コードをいじりました。

レスポンスをただputsするのではなくてResponseクラスのインスタンスを返すようにしました。

#response.rb
class Response
  attr_accessor :request, :headers, :response_except_body, :status, :body
  def initialize(response)
    @response = response
    @body = @response.slice!(/(\n\r\n).+/m)
    @response_except_body = @response.slice(/.+/m)
    #なぜsliceしないと動かないのだろうか・・・・
    @request = @response.slice!(/.+/)
    @status = @request.slice!(/\d{3}.+/)

    #ヘッダーの切り出し
    @headers = []
    while @response != ""
      header = @response.slice!(/.+/)
      @headers.push(header) unless header.nil?
      @response.sub!(/(\r\n|\r|\n)/, '')
    end

    #切り出したヘッダーからインスタンス変数、
    #アクセサメソッドを作成
    @headers.each do |header|
      header_name_raw = header.slice!(/^.+: /)
      unless header_name_raw.nil?
        header_name = header_name_raw.downcase
                      .gsub("-", "_")
                      .gsub(":", "")
                      .gsub(" ", "")
        self.instance_variable_set("@#{header_name}",header)
        add_accessor header_name
      end
    end
  end

  def all_response
    self.response_except_body
  end

  def disp_body
    self.body
  end

  private

    def add_accessor instance_variable_name
      self.class.send(:attr_accessor, instance_variable_name)
    end

end
#http_client.rb
require 'socket'
require './response'

class HttpClient
  attr_accessor :uri, :port, :path

  def initialize(options = {})
    @uri = options[:uri]
    @path = options[:path]
    @port = options[:port]
  end

  [:get, :post, :put, :delete, :head, :options].each do |method|
    define_method(method) do |content = nil, type = "text/plain"|
      @socket = TCPSocket.open(uri, port)
      @socket << "#{method.to_s.swapcase} #{self.path} HTTP/1.1\r\n"
      @socket << "Host: #{self.uri}\r\n"
      @socket << "Content-Type: #{type}; charset=utf-8\r\n"

      unless content.nil?
        @socket << "Content-Length: #{content.length}\r\n"
        @socket << "\r\n#{content}"
      end

      @socket << "Connection: close\r\n"
      @socket << "\r\n"
      response = Response.new(@socket.read)
      return response
      @socket.close
    end
  end
end

request = HttpClient.new(uri:"example.com", path:"/", port:80)
puts "===================="
puts request.get.all_response
puts "===================="
puts HttpClient.new(uri:"example.com", path:"/", port:80).post("こんにちは").all_response
puts "===================="
puts HttpClient.new(uri:"www.google.com", path:"/", port:80).post("こんにちは").all_response
puts "===================="
puts HttpClient.new(uri:"example.com", path:"/", port:80).options.all_response
puts "===================="

実行すると、

% ruby http_client.rb
====================
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html
Date: Sat, 30 Apr 2016 03:37:44 GMT
Etag: "359670651"
Expires: Sat, 07 May 2016 03:37:44 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (rhv/818F)
Vary: Accept-Encoding
X-Cache: HIT
x-ec-custom-error: 1
Content-Length: 1270
Connection: close
====================
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: max-age=604800
Content-Type: text/html
Date: Sat, 30 Apr 2016 03:37:44 GMT
Etag: "359670651"
Expires: Sat, 07 May 2016 03:37:44 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: EOS (lax004/2812)
Content-Length: 1270
====================
HTTP/1.1 405 Method Not Allowed
Allow: GET, HEAD
Date: Sat, 30 Apr 2016 03:37:44 GMT
Content-Type: text/html; charset=UTF-8
Server: gws
Content-Length: 1589
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
====================
HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, POST
Cache-Control: max-age=604800
Date: Sat, 30 Apr 2016 03:37:45 GMT
Expires: Sat, 07 May 2016 03:37:45 GMT
Server: EOS (lax004/28A4)
x-ec-custom-error: 1
Content-Length: 0
Connection: close

====================

簡単にResponseクラスの説明をすると、正規表現でガリガリ削ってレスポンスのヘッダーを動的にインスタンス変数に入れて動的にアクセサメソッド付与しています。これだけです。

参考