Sending HTTP/2 POST request in Ruby

2020-07-03 07:14发布

问题:

I am trying to use the new Apple Push Notification API, which is based on HTTP/2.

I found the http-2 Ruby gem, but the documentation isn’t clear about how to make requests as a client.

How to make a HTTP/2 request in Ruby/Rails?

回答1:

DISCLAIMER: I'm the author of the two gems listed here below.

If you want to issue HTTP/2 calls, you may consider NetHttp2, a HTTP/2 client for Ruby.

Usage example for sync calls:

require 'net-http2'

# create a client
client = NetHttp2::Client.new("http://106.186.112.116")

# send request
response = client.call(:get, '/')

# read the response
response.ok?      # => true
response.status   # => '200'
response.headers  # => {":status"=>"200"}
response.body     # => "A body"

# close the connection
client.close    

On top of writing the HTTP/2 calls yourself, if you want an Apple Push Notification gem that uses the new HTTP/2 specifics and can be embedded in a Rails environment then you may also consider Apnotic.

Usage is very simple:

require 'apnotic'

# create a persistent connection
connection = Apnotic::Connection.new(cert_path: "apns_certificate.pem", cert_pass: "pass")

# create a notification for a specific device token 
token = "6c267f26b173cd9595ae2f6702b1ab560371a60e7c8a9e27419bd0fa4a42e58f"

notification       = Apnotic::Notification.new(token)
notification.alert = "Notification from Apnotic!"

# send (this is a blocking call)
response = connection.push(notification)

# read the response
response.ok?      # => true
response.status   # => '200'
response.headers  # => {":status"=>"200", "apns-id"=>"6f2cd350-bfad-4af0-a8bc-0d501e9e1799"}
response.body     # => ""

# close the connection
connection.close


回答2:

There is an example of creating HTTP/2 client in this file. It could be adapted to APN requests like this:

require 'socket'
require 'http/2'

payload = '{"foo":"bar"}'
device_token = '00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0' # example
sock = TCPSocket.new('api.development.push.apple.com', 443)

conn = HTTP2::Client.new
conn.on(:frame) do |bytes|
  puts "Sending bytes: #{bytes.unpack("H*").first}"
  sock.print bytes
  sock.flush
end

stream = conn.new_stream

stream.on(:close) do
  sock.close
end

head = {
  ':scheme' => 'https',
  ':method' => 'POST',
  ':path' => "/3/device/#{device_token}",
  'apns-id' => '123e4567-e89b-12d3-a456-42665544000', # or you could omit this header
  'content-length' => payload.bytesize.to_s # should be less than or equal to 4096 bytes
}

puts 'Sending HTTP 2.0 request'
stream.headers(head, end_stream: false)
stream.data(payload)

while !sock.closed? && !sock.eof?
  data = sock.read_nonblock(1024)
  puts "Received bytes: #{data.unpack("H*").first}"

  begin
    conn << data
  rescue => e
    puts "Exception: #{e}, #{e.message} - closing socket."
    sock.close
  end
end


回答3:

I’ve been working on a client implementation for this: https://github.com/alloy/lowdown.

Thus far I have been extensively testing it, but it won’t be deployed into production until next week. I’d love for it to get more testing, feedback, etc.



回答4:

Here is how you do it using async-http:

#!/usr/bin/env ruby

require 'async'
require 'async/logger'
require 'async/http/client'
require 'async/http/url_endpoint'

Async.run do |task|
    endpoint = Async::HTTP::URLEndpoint.parse("https://www.google.com")

    client = Async::HTTP::Client.new(endpoint)

    headers = {
        'accept' => 'text/html',
    }

    request = Async::HTTP::Request.new("www.google.com", "GET", "/search?q=cats", headers)

    response = client.call(request)
    body = response.read
end

This will negotiate HTTP/1, HTTP/2 and SSL as required.