Giter Site home page Giter Site logo

digital-fabric / tipi Goto Github PK

View Code? Open in Web Editor NEW
239.0 9.0 9.0 496 KB

Tipi - the All-in-one Web Server for Ruby Apps

License: MIT License

Ruby 97.05% HTML 2.87% Shell 0.08%
ruby http web-server http2 ssl websocket rack polyphony acme acme-client

tipi's Introduction

Tipi - the All-in-one Web Server for Ruby Apps

Gem Version Tipi Test MIT licensed

What is Tipi?

Tipi is an integrated, feature-complete HTTP/S server for Ruby applications. Tipi is built on top of Polyphony, a robust, high-performance library for building highly-concurrent applications in Ruby. Tipi can be used to serve any Rack application or set of static files directly without having to employ a reverse-proxy such as Nginx.

Features

  • High-performance, highly concurrent web server based on Polyphony
  • Full support for HTTP/1, HTTP/2, WebSocket protocols
  • Built-in SSL termination for secure, encrypted connections
  • Automatic SSL certificates using ACME providers such as Let's Encrypt (WIP)
  • Automatic ALPN protocol selection for serving HTTP/2
  • Request and response body streaming for efficient downloads and uploads
  • Full support for Rack-based apps

Benchmarks

Caveat emptor: the following results were obtained with an ad-hoc, manual process. I am not really familiar with the servers I compared Tipi against, and I ran them in their default configuration (apart from setting the number of workers). Take these results with a bunch of salt.

Tipi Puma Falcon Unicorn
HTTP/1.1 138629 34573 40714 7438
HTTPS/2 56762 n/a 34226 n/a

Methodology

  • All servers ran the same "Hello world" Rack application
  • Each server was run with 4 forked worker processes:
    • Tipi: tipi -w4 -flocalhost:10080:10443 examples/hello.ru
    • Puma: puma -w 4 examples/hello.ru
    • Falcon: falcon -n 4 -b http://localhost:9292/ -c examples/hello.ru
    • Unicorn: unicorn -c u.conf examples/hello.ru with the configuration file containing the directive worker_processes 4
  • The benchmark results were obtained using wrk -d60 -t4 -c64 <url>
  • All servers were run on Ruby 2.7.2p137
  • Machine specs: [email protected] CPU, 8GB of RAM, running Linux kernel version 5.13.7
  • Puma does not support HTTP/2.
  • As far as I could tell Unicorn does not support SSL termination.

Running Tipi

To run Tipi, run the included tipi command. Alternatively you can add tipi as a dependency to your Gemfile, then run bundle exec tipi. By default

Tipi can be used to drive Rack apps or alternatively any app using the Qeweney request-response interface.

Running Rack apps

Use the tipi command to start your app:

$ bundle exec tipi myapp.ru

Running Qeweney apps

$ bundle exec tipi myapp.rb

The app script file should define an app method that returns a proc/lambda taking a single Qeweney::Request argument. Here's an example:

# frozen_string_literal: true

def app
  ->(req) { req.respond('Hello, world!', 'Content-Type' => 'text/plain') }
end

Setting server listening options

By default, Tipi serves plain HTTP on port 1234, but you can easily change that by providing command line options as follows:

HTTP

To listen for plain HTTP, use the -l/--listen option and specify a port number:

$ bundle exec tipi -l9292 myapp.ru

HTTPS

To listen for HTTPS connections, use the -s/--secure option and specify a host name and a port:

$ bundle exec tipi -sexample.com:9292 myapp.ru

Full service listening

The Tipi full service listens for both HTTP and HTTPS and supports automatic certificate provisioning. To use the full service, use the -f/--full option, and specify the domain name, the HTTP port, and the HTTPS port, e.g.:

$ bundle exec tipi -fmysite.org:10080:10443 myapp.ru

#If serving multiple domains, you can use * as place holder
$ bundle exec tipi -f*:10080:10443 myapp.ru

If localhost is specified as the domain, Tipi will automatically generate a localhost certificate.

Concurrency settings

By default, the tipi command starts a single controller and uses Polyphony to run each connection on its own fiber. This means that you will have a single process running on a single thread (on a single CPU core). In order to parallelize your app and employ multiple CPU cores, you can tell Tipi to fork multiple worker processes to run your app. The number of workers is controlled using the -w/--workers option:

# fork 4 worker processes
$ bundle exec tipi -w4 myapp.ru

You can also set Tipi to spawn multiple threads in each worker when in compatibility mode (see below.)

Compatibility mode

Note: compatibility mode is still being developed, and currently only supports HTTP/1 connections.

In some apps, using Polyphony is not possible, due to incompatibilities between it and other third-party dependencies. In order to be able to run these apps, Tipi provides a compatibility mode that does not use Polyphony for concurrency, but instead uses a thread-per-connection concurrency model. You can also fork multiple workers, each running multiple threads, if so desired. Note that the concurrency level is the maximum number workers multiplied by the number of threads per worker:

concurrency = worker_count * threads_per_worker

To run Tipi in compatibility mode, use the -c/--compatibility option, e.g.:

# 4 workers * 8 threads = 32 max concurrency
$ bundle exec tipi -c -w4 -t8 myapp.ru

Worker process supervision

Tipi employs a supervisor-controller-worker process supervision model, which minimizes the memory consumption of forked workers, and which facilitates graceful reloading after updating the application code.

This supervision model is made of three levels:

  • Supervisor - Starts and stops the controller process
  • Controller - loads the application code and forks workers
  • Worker - listens for connections, handles incoming requests

(If the worker count is 1, the Controller and Worker roles are merged into a single process.)

This model allows Tipi to fork workers after loading the app code, and use a much simpler way to perform graceful restarts:

  • The supervisor starts a new controller process (which may fork one or more worker processes).
  • Sleep for a certain amount of time (currently 1 second.)
  • Stop the old controller process.
  • Each worker process is gracefully stopped and allowed to finish all pending requests, then shutdown all open connections.

Performing a graceful restart

A graceful restart performed by sending SIGUSR2 to the supervisor process.

Documentation

Documentation for Tipi's API is coming soon...

tipi's People

Contributors

dependabot[bot] avatar dm1try avatar elmassimo avatar noteflakes avatar swagdevops avatar timhatch avatar tonyfg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tipi's Issues

Ctrl+C on app throws ArgumentError

I'm on Ruby 3.2.0, Rack 3, Roda 3.70, Tipi 0.55, Fedora based Linux

^CTerminate controller 73020
/home/megatux/.local/share/rtx/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/tipi-0.55/lib/tipi/controller/web_polyphony.rb:54:in `block in run_worker': wrong number of arguments (given 1, expected 0) (ArgumentError)
	from /home/megatux/.local/share/rtx/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/tipi-0.55/lib/tipi/controller/web_polyphony.rb:54
/home/megatux/.local/share/rtx/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/tipi-0.55/lib/tipi/controller/web_polyphony.rb:57:in `block in run_worker': wrong number of arguments (given 1, expected 0) (ArgumentError)
	from /home/megatux/.local/share/rtx/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/tipi-0.55/lib/tipi/controller/web_polyphony.rb:55
/home/megatux/.local/share/rtx/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/tipi-0.55/lib/tipi/controller/web_polyphony.rb:57:in `block in run_worker': wrong number of arguments (given 1, expected 0) (ArgumentError)
	from /home/megatux/.local/share/rtx/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/tipi-0.55/lib/tipi/controller/web_polyphony.rb:55

The app is just a dummy hello-world Roda app that works fine:

require "roda"

class App < Roda
  route do |r|
    r.root do
      "hola!"
    end
  end
end

run App

Tipi.serve callback invoked twice

I took examples/http_server.rb and added a simple puts:

spin do
  Tipi.serve('0.0.0.0', 4411, opts) do |req|
    req.respond("Hello world!\n")
    puts "200 OK"
  rescue Exception => e
    p e
  end
  p 'done...'
end.await

For a single request, it outputs:

Listening on port 4411...
200 OK
200 OK

Within a small unit test, it behaves as expected:

  def test_that_callback_runs_once_per_request
    count = 0

    @server, connection = spin_server do |req|
      count += 1
      req.respond('Hello, world!', {})
    end

    # using HTTP 1.0, server should close connection after responding
    connection << "GET / HTTP/1.0\r\n\r\n"

    connection.readpartial(8192)
    assert_equal(1, count)
  end

I'm on Mac OS Big Sur and Ruby 3.0. Could it be related to how the server is bound to the interface? I also noticed that when the server exits, the port is not freed.

Server crashes when using gzip compression

Tipi's Rack adapter crashes when a request with Accept-Encoding: gzip is handled:

/usr/local/bundle/gems/tipi-0.55/lib/tipi/rack_adapter.rb:28:in `respond': undefined method `first' for #<Rack::Deflater::GzipStream:0x00007f6c78931930 @body=#<Rack::BodyProxy:0x00007f6c78932a10 @body=["Not Found\n"], @block=#<Proc:0x00007f6c78932920 /usr/local/bundle/gems/rack-3.0.8/lib/rack/common_logger.rb:45>, @closed=false>, @mtime=nil, @sync=true> (NoMethodError)

            body.first
                ^^^^^^
	from /usr/local/bundle/gems/tipi-0.55/lib/tipi/rack_adapter.rb:9:in `block in run'
	from /usr/local/bundle/gems/tipi-0.55/lib/tipi/http1_adapter.rb:52:in `handle_request'
	from /usr/local/bundle/gems/tipi-0.55/lib/tipi/http1_adapter.rb:28:in `each'
	from /usr/local/bundle/gems/tipi-0.55/lib/tipi.rb:50:in `client_loop'
	from /usr/local/bundle/gems/tipi-0.55/lib/tipi.rb:41:in `block (2 levels) in accept_loop'
/srv # bundle exec ruby tipi.rb
Starting metrics server on 0.0.0.0:9394
/usr/local/bundle/gems/tipi-0.55/lib/tipi/rack_adapter.rb:28:in `respond': undefined method `first' for #<Rack::Deflater::GzipStream:0x00007fc887ff7768 @body=#<Rack::BodyProxy:0x00007fc887ff7cb8 @body=["Not Found\n"], @block=#<Proc:0x00007fc887ff7c68 /usr/local/bundle/gems/rack-3.0.8/lib/rack/common_logger.rb:45>, @closed=false>, @mtime=nil, @sync=true> (NoMethodError)

The code I was using at the moment:

require "tipi"
require "yabeda"
require "yabeda/prometheus"

Tipi.serve(
  "0.0.0.0",
  9394,
  { reuse_addr: true, dont_linger: true },
  &Tipi::RackAdapter.run(Yabeda::Prometheus::Exporter.rack_app)
)

The request that triggers the crash:

curl -sH 'Accept-Encoding: gzip' localhost:9394

The problem seems to be that Tipi::RackAdapter receives the Rack::BodyProxy object wrapped in a Rack::Deflater::GzipStream when compression is requested.

Forked example doesn't work?

When running the forked http example I only ever get responses from a single pid, so can't seem to utilize more than a single core on my machine.

System: MacOS Big Sur on a 2020 intel Macbook Pro
Ruby: 2.7.2
Tipi version: 0.33
Polyphony version: 0.47.5.1 (noticeably not the latest version, due to how the tipi dependency is configured)

Implement Rack::Builder#use

I want to try Tipi with an existing Roda API app. I use Rack Attack middleware.
First thing I notice is that the use Rack::Attack throws an error

config.ru:7:in load': undefined method use' for Tipi::RackAdapter:Module (NoMethodError)

Is because the Rack::Builder#use is not implemented?

Maybe related to #2, too

Application freezes when trying to connect to Mongodb

Trying to setup a connection to a Mongo database, an application just hangs. If I take the connection line in question into irb or pry it works as expected. Strace points to something within io_uring.

Strace output:

clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7ff2fa55c990, parent_tid=0x7ff2fa55c990, exit_signal=0, stack=0x7ff2fa35c000, stack_size=0x1fff80, tls=0x7ff2fa55c6c0} => {parent_tid=[47625]}, 88) = 47625
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 2, 0, 0, NULL, 8)     = 2
io_uring_enter(5, 0, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 0
getpid()                                = 47614
futex(0x5596d2b2ce68, FUTEX_WAIT_BITSET_PRIVATE, 0, {tv_sec=97766, tv_nsec=523406835}, FUTEX_BITSET_MATCH_ANY) = 0
futex(0x5596d2b2cfd8, FUTEX_WAIT_PRIVATE, 2, NULL) = 0
futex(0x7ff2f4003ee8, FUTEX_WAKE_PRIVATE, 1) = 1
futex(0x5596d2b2cfd8, FUTEX_WAKE_PRIVATE, 1) = 0
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 1, 0, 0, NULL, 8)     = 1
io_uring_enter(5, 0, 1, IORING_ENTER_GETEVENTS, NULL, 8

server.rb

require 'logger'
require 'mongo'
require 'tipi'

opts = {
  reuse_addr:  true,
  dont_linger: true
}

L = Logger.new(STDOUT)

L.info 'Connecting to database'

# Offending line that hangs if uncommented 
#M = Mongo::Client.new(['node1.cloud:27017', 'node2.cloud:27017', 'node3.cloud:27017'], database: 'xxxxx', max_pool_size: 200, max_connecting: 10, read: { mode: :secondary_preferred })

L.info "pid: #{Process.pid}"
L.info 'Listening on port 12345...'

server = Tipi.route do |r|
  r.on 'get' do
    L.info 'route /get called'
    r.respond 'get'
  end

spin do
  Tipi.serve('0.0.0.0', 12345, opts, &server)

Ruby crash, try to mark T_NONE object

/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi/http1_adapter.rb:18: [BUG] try to mark T_NONE object
ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0010 p:---- s:0054 e:000053 CFUNC  :initialize
c:0009 p:---- s:0051 e:000050 CFUNC  :new
c:0008 p:0035 s:0045 e:000044 METHOD /home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi/http1_adapter.rb:18 [FINISH]
c:0007 p:---- s:0039 e:000038 CFUNC  :new
c:0006 p:0061 s:0033 e:000032 METHOD /home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi.rb:59
c:0005 p:0020 s:0025 e:000024 METHOD /home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi.rb:49
c:0004 p:0013 s:0017 e:000016 BLOCK  /home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi.rb:41
c:0003 p:0013 s:0014 e:000013 METHOD /home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/polyphony-0.76/lib/polyphony/extensions/fiber.rb:358
c:0002 p:0007 s:0007 e:000006 BLOCK  /home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/polyphony-0.76/lib/polyphony/extensions/fiber.rb:285 [FINISH]
c:0001 p:---- s:0003 e:000002 (none) [FINISH]

-- Ruby level backtrace information ----------------------------------------
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/polyphony-0.76/lib/polyphony/extensions/fiber.rb:285:in `block in spin'
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/polyphony-0.76/lib/polyphony/extensions/fiber.rb:358:in `run'
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi.rb:41:in `block (2 levels) in accept_loop'
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi.rb:49:in `client_loop'
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi.rb:59:in `protocol_adapter'
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi.rb:59:in `new'
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi/http1_adapter.rb:18:in `initialize'
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi/http1_adapter.rb:18:in `new'
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi/http1_adapter.rb:18:in `initialize'

-- C level backtrace information -------------------------------------------
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_print_backtrace+0x19) [0x7fb5768b0dd9] vm_dump.c:755
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_vm_bugreport+0x76b) [0x7fb5768b155b] vm_dump.c:941
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_bug+0xeb) [0x7fb5766ae47b] error.c:645
[0x7fb5766ae8da]
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/h1p-0.3/lib/h1p_ext.so(Parser_mark+0x1a) [0x7fb57254b62a] h1p.c:90
[0x7fb57674bd8b]
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(str_alloc+0xa) [0x7fb57683fcbd] string.c:728
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(str_new_static) string.c:860
/home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/h1p-0.3/lib/h1p_ext.so(Parser_initialize+0x42) [0x7fb572552e52] h1p.c:156
[0x7fb5768a24a6]
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_vm_call0+0xc3) [0x7fb5768a2ad3] vm_eval.c:52
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_vm_call_kw+0x6c) [0x7fb5768a2eac] vm_eval.c:268
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_funcallv_kw+0x122) [0x7fb5768a5892] vm_eval.c:392
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_class_s_new+0x39) [0x7fb5767bcdd9] object.c:2152
[0x7fb57688c678]
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(vm_sendish+0x76) [0x7fb5768877f6] vm_insnhelper.c:4023
[0x7fb57689838a]
[0x7fb57689e780]
[0x7fb5768a26e7]
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_vm_call0+0xc3) [0x7fb5768a2ad3] vm_eval.c:52
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_vm_call_kw+0x6c) [0x7fb5768a2eac] vm_eval.c:268
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_funcallv_kw+0x122) [0x7fb5768a5892] vm_eval.c:392
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_class_s_new+0x39) [0x7fb5767bcdd9] object.c:2152
[0x7fb57688c678]
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(vm_sendish+0x76) [0x7fb5768877f6] vm_insnhelper.c:4023
[0x7fb57689838a]
[0x7fb57689e780]
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(rb_vm_invoke_proc+0x1f6) [0x7fb5768a2106] vm.c:1044
[0x7fb57670b6f0]
/usr/share/rvm/rubies/ruby-2.7.5/bin/../lib/libruby.so.2.7(fiber_entry+0xf) [0x7fb57670b91f] cont.c:694

-- Other runtime information -----------------------------------------------

* Loaded script: /home/deploy/wordle/shared/vendor/bundle/ruby/2.7.0/gems/tipi-0.47/lib/tipi/controller.rb
...

extralite dependence update to version 2.0/2.1

Is it possible to update the gemspec dependence extralite to version ">=1.27", "<2.1".
Otherwise, there will be conflicts, to use of extralite -> 'latest' and the tipi server.

greetings ka :)

Serving static files fails

$ tipi

throws this error (attached stacktrace) in console when accessing http://localhost:1234/somefile_existing_file

tipi_err.txt

It worked once but most of the time the Ruby process stops running.

Improve compliance with Rack spec

See also #2.

Still left to do:

  • SERVER_PORT
  • rack.url_scheme
  • Make InputStream buffered, implement #gets and #read
  • rack.session
  • rack.logger
  • Check correct behaviour with both http1 and http2 (actually I think the two adapters have different standards of capitalization of HTTP headers. They should be homogenized.)
  • Add some tests:
    • test basic functionality under both http1 and http2
    • test downloads
    • test uploads
    • test form submissions

Note: we'll leave rack.multipart.XXX for a later time, the same for rack.hijack.

rack_server example problem

I had to switch away from testing Tipi for a while, but started to look at the rack_server examples in more detail this weekend.

The basic example using hello.ru as the config file works without issue (Invoked via bundle exec ruby ./rack_server ./hello.ru)

The Cuba example, again running rack_server.rb but with cuba.ru as the config file runs, but routing doesn't seem work. I didn't check the hanami_api.ru example, as I'd already tried some quick tests to see if other simple rack apps (sinatra, roda) had the same issue, which they did.

Could you take a quick look? Not at all sure what the cause is.

Benchmarks or comparison with Puma and friends?

Hey there, in your docs you state:

Polyphony, a robust, high-performance library for building highly-concurrent applications in Rub

Do you have any benchmarks to compare this with others?

Thanks!

Implement streaming response compression

def respond_gzip_from_io(io, opts)
  r, w = IO.pipe
  spin do
    writer = Zlib::GzipWriter.new(w)
    io.feed_loop(writer, :<<)
    w.close
  end
  serve_from_io(r, opts)
end

For deflate, we can use the Deflate#deflate:

def respond_deflate_from_io(io, opts)
  deflater = Zlib::Deflate.new
  io.read_loop do |data|
    deflated = deflater.deflate(data)
    adapter.send_chunk(deflated)
  end
  deflated = deflater.flush
  adapter.send_chunk(deflated, done: true)
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.