Created
August 16, 2017 08:10
-
-
Save nicklegr/1ed4ec64f57aaf0899d6c91c89f8734c to your computer and use it in GitHub Desktop.
Simple http/2 wrapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'optparse' | |
| require 'socket' | |
| require 'openssl' | |
| require 'http/2' | |
| require 'uri' | |
| DRAFT = 'h2'.freeze | |
| class Logger | |
| def initialize(id) | |
| @id = id | |
| end | |
| def info(msg) | |
| # puts "[Stream #{@id}]: #{msg}" | |
| end | |
| end | |
| class Http2 | |
| def self.get(uri, headers) | |
| call("GET", uri, headers, nil) | |
| end | |
| def self.post(uri, headers, body) | |
| call("POST", uri, headers, body) | |
| end | |
| def self.call(method, uri, headers, body) | |
| uri = URI.parse(uri) | |
| tcp = TCPSocket.new(uri.host, uri.port) | |
| sock = nil | |
| if uri.scheme == 'https' | |
| ctx = OpenSSL::SSL::SSLContext.new | |
| ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE | |
| # For ALPN support, Ruby >= 2.3 and OpenSSL >= 1.0.2 are required | |
| ctx.npn_protocols = [DRAFT] | |
| ctx.npn_select_cb = lambda do |protocols| | |
| # puts "NPN protocols supported by server: #{protocols}" | |
| DRAFT if protocols.include? DRAFT | |
| end | |
| sock = OpenSSL::SSL::SSLSocket.new(tcp, ctx) | |
| sock.sync_close = true | |
| sock.hostname = uri.hostname | |
| sock.connect | |
| if sock.npn_protocol != DRAFT | |
| puts "Failed to negotiate #{DRAFT} via NPN" | |
| exit | |
| end | |
| else | |
| sock = tcp | |
| end | |
| conn = HTTP2::Client.new | |
| stream = conn.new_stream | |
| log = Logger.new(stream.id) | |
| response = { | |
| :headers => {}, | |
| :body => "", | |
| } | |
| conn.on(:frame) do |bytes| | |
| # puts "Sending bytes: #{bytes.unpack("H*").first}" | |
| sock.print bytes | |
| sock.flush | |
| end | |
| stream.on(:close) do | |
| log.info 'stream closed' | |
| sock.close # whileを抜けさせる | |
| end | |
| stream.on(:headers) do |h| | |
| log.info "response headers: #{h}" | |
| response[:headers].merge!(Hash[h]) | |
| end | |
| stream.on(:data) do |d| | |
| log.info "response data chunk: <<#{d}>>" | |
| response[:body] += d | |
| end | |
| head = { | |
| ':scheme' => uri.scheme, | |
| ':method' => method, | |
| ':authority' => uri.host, | |
| ':path' => uri.path, | |
| } | |
| head.merge!(headers) | |
| if head[':method'] == 'GET' | |
| stream.headers(head, end_stream: true) | |
| else | |
| stream.headers(head, end_stream: false) | |
| stream.data(body) | |
| end | |
| while !sock.closed? && !sock.eof? | |
| data = sock.read_nonblock(1024) | |
| # puts "Received bytes: #{data.unpack("H*").first}" | |
| begin | |
| conn << data | |
| rescue => e | |
| puts "#{e.class} exception: #{e.message} - closing socket." | |
| e.backtrace.each { |l| puts "\t" + l } | |
| sock.close | |
| end | |
| end | |
| response | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment