diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2015-11-13 09:03:44 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2015-11-13 09:03:44 +0900 |
commit | 963925aed0bca245f390dbdffc6c5308b88bc683 (patch) | |
tree | cbd76d2db899aff26f202cb2314aee5893ea3baf /lib/plum/client/legacy_client_session.rb | |
parent | 0e9f859c18d78a3c34d493e9d673e06ab10c311a (diff) | |
parent | 014ff6d424f5ad863a099428d865bb74c857b36e (diff) | |
download | plum-963925aed0bca245f390dbdffc6c5308b88bc683.tar.gz |
Merge branch 'master' of github.com:rhenium/plum
Diffstat (limited to 'lib/plum/client/legacy_client_session.rb')
-rw-r--r-- | lib/plum/client/legacy_client_session.rb | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/lib/plum/client/legacy_client_session.rb b/lib/plum/client/legacy_client_session.rb new file mode 100644 index 0000000..bc531ac --- /dev/null +++ b/lib/plum/client/legacy_client_session.rb @@ -0,0 +1,118 @@ +# -*- frozen-string-literal: true -*- +module Plum + # HTTP/1.x client session. + class LegacyClientSession + # Creates a new HTTP/1.1 client session + def initialize(socket, config) + require "http/parser" + @socket = socket + @config = config + + @parser = setup_parser + @requests = [] + @response = nil + @headers_callback = nil + end + + def succ + @parser << @socket.readpartial(16384) + rescue => e # including HTTP::Parser::Error + fail(e) + end + + def empty? + !@response + end + + def close + @closed = true + @response._fail if @response + end + + def request(headers, body, options, &headers_cb) + headers["host"] = headers[":authority"] || headers["host"] || @config[:hostname] + if body + if headers["content-length"] || headers["transfer-encoding"] + chunked = false + else + chunked = true + headers["transfer-encoding"] = "chunked" + end + end + + response = Response.new(**options) + @requests << [response, headers, body, chunked, headers_cb] + consume_queue + response + end + + private + def fail(exception) + close + raise exception + end + + def consume_queue + return if @response || @requests.empty? + + response, headers, body, chunked, cb = @requests.shift + @response = response + @headers_callback = cb + + @socket << construct_request(headers) + + if body + if chunked + read_object(body) { |chunk| + @socket << chunk.bytesize.to_s(16) << "\r\n" << chunk << "\r\n" + } + else + read_object(body) { |chunk| @socket << chunk } + end + end + end + + def construct_request(headers) + out = String.new + out << "%s %s HTTP/1.1\r\n" % [headers[":method"], headers[":path"]] + headers.each { |key, value| + next if key.start_with?(":") # HTTP/2 psuedo headers + out << "%s: %s\r\n" % [key, value] + } + out << "\r\n" + end + + def read_object(body) + if body.is_a?(String) + yield body + else # IO + until body.eof? + yield body.readpartial(1024) + end + end + end + + def setup_parser + parser = HTTP::Parser.new + parser.on_headers_complete = proc { + resp_headers = parser.headers.map { |key, value| [key.downcase, value] }.to_h + @response._headers({ ":status" => parser.status_code.to_s }.merge(resp_headers)) + @headers_callback.call(@response) if @headers_callback + } + + parser.on_body = proc { |chunk| + @response._chunk(chunk) + } + + parser.on_message_complete = proc { |env| + @response._finish + @response = nil + @headers_callback = nil + close unless parser.keep_alive? + consume_queue + } + + parser + end + end +end |