aboutsummaryrefslogtreecommitdiffstats

Plum: An HTTP/2 Library for Ruby

A pure Ruby HTTP/2 server and client implementation.

WARNING: Plum is currently under heavy development. You will encounter bugs when using it.

Circle CI Build Status Code Climate Test Coverage

Requirements

  • Ruby 2.3
  • OpenSSL 1.0.2 or newer
  • http_parser.rb gem - if you need HTTP/2 without TLS or HTTP/1.1 support
  • rack gem - if you use plum as a Rack server

Installation

You can install via rubygems:

gem install plum

then require it:

require "plum"

Usage

  • Documentation: http://www.rubydoc.info/gems/plum
  • Some examples are in examples/

As a Rack-compatible server

Most existing Rack-based applications should work without modification.

# config.ru
App = -> env {
  [
    200,
    { "Content-Type" => "text/plain" },
    ["request: #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"]
  ]
}

run App

You can run it:

% plum -e production -p 8080 --https --cert server.crt --key server.key config.ru

NOTE: If --cert and --key are omitted, a temporary dummy certificate will be generated.

My website https://rhe.jp is using plum as a Rack server.

As a HTTP/2 (HTTP/1.x) client library

If the server doesn't support HTTP/2, it falls back to HTTP/1.1 seamlessly.

   +-----------------+
   |:https option    | false
   |(default: true)  |-------> Try Upgrade from HTTP/1.1
   +-----------------+
            | true
            v
   +-----------------+
   | ALPN            | failed
   | negotiation     |-------> HTTP/1.x
   +-----------------+
            | "h2"
            v
          HTTP/2
Sequential request
client = Plum::Client.start("http2.rhe.jp", user_agent: "nyaan")
res1 = client.get("/", headers: { "accept" => "*/*" }).join
puts res1.body # => "..."
res2 = client.post("/post", "data").join
puts res2.body # => "..."

client.get("/clockstream").on_headers { |res|
  puts "status: #{res.status}, headers: #{res.headers}"
}.on_chunk { |chunk|
  puts chunk
}.on_finish {
  puts "finish!"
}.join

client.close
Parallel request
res1 = res2 = nil
Plum::Client.start("rhe.jp", 443, http2_settings: { max_frame_size: 32768 }) { |client|
  res1 = client.get("/")
  res2 = client.post("/post", "data")
  # res1.status == nil ; because it's async request
} # wait for response(s) and close

p res1.status # => "200"
Download a large file
# the value of hostname option will be used in SNI and :authority header
Plum::Client.start("http2.rhe.jp", 443, hostname: "assets.rhe.jp") { |client|
  client.get("/large") do |res| # called when received response headers
    p res.status # => "200"
    File.open("/tmp/large.file", "wb") { |file|
      res.on_chunk do |chunk| # called when each chunk of response body arrived
        file << chunk
      end
    }
  end
}

TODO

  • Better API design
  • Better server push support
  • Stream priority support

Of course ideas and pull requests are welcome.

Hacking

Clone this Git repository and run bundle install to install development dependencies. You can run test with rake test. The tests are written with Minitest.

License

MIT License