Skip to content

Instantly share code, notes, and snippets.

@nicklegr
Created August 16, 2017 08:10
Show Gist options
  • Select an option

  • Save nicklegr/1ed4ec64f57aaf0899d6c91c89f8734c to your computer and use it in GitHub Desktop.

Select an option

Save nicklegr/1ed4ec64f57aaf0899d6c91c89f8734c to your computer and use it in GitHub Desktop.
Simple http/2 wrapper
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