Skip to content

Commit

Permalink
Migration of code from async-http.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Jun 15, 2017
0 parents commit e48f093
Show file tree
Hide file tree
Showing 15 changed files with 590 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .gitignore
@@ -0,0 +1,12 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

# rspec failure tracking
.rspec_status
3 changes: 3 additions & 0 deletions .rspec
@@ -0,0 +1,3 @@
--format documentation
--warnings
--require spec_helper
18 changes: 18 additions & 0 deletions .travis.yml
@@ -0,0 +1,18 @@
language: ruby
sudo: false
dist: trusty
cache: bundler
rvm:
- 2.0
- 2.1
- 2.2
- 2.3
- 2.4
- jruby-head
- ruby-head
- rbx-3
matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
- rvm: rbx-3
9 changes: 9 additions & 0 deletions Gemfile
@@ -0,0 +1,9 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in async-io.gemspec
gemspec

group :test do
gem 'simplecov'
gem 'coveralls', require: false
end
100 changes: 100 additions & 0 deletions README.md
@@ -0,0 +1,100 @@
# Falcon

A multi-process, multi-fiber Rack HTTP server built on top of [async], [async-io] and [async-http]. Each request is run within a light weight fiber and can block on up-stream requests without stalling the entire server process. Uses a multi-process model for handling blocking requests.

[![Build Status](https://secure.travis-ci.org/socketry/falcon.svg)](http://travis-ci.org/socketry/falcon)
[![Code Climate](https://codeclimate.com/github/socketry/falcon.svg)](https://codeclimate.com/github/socketry/falcon)
[![Coverage Status](https://coveralls.io/repos/socketry/falcon/badge.svg)](https://coveralls.io/r/socketry/falcon)

[async]: https://github.com/socketry/async
[async-io]: https://github.com/socketry/async-io
[async-http]: https://github.com/socketry/async-http

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'falcon'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install falcon

## Usage

You can run `falcon` directly, and it will load the `config.ru` and start serving on port 8080.

### Integration with Guard

Falcon can restart very quickly and is ideal for use with guard. In your `Guardfile`:

```ruby

guard :falcon do
end

```

### Deploying with Passenger

You can run Falcon within Passenger to improve asyncronicity by using the `Falcon::Hijack` middleware. The first request from a client will be parsed by Passenger, but `rack.hijack` allows us to start parsing requests using Falcon within a separate `Async::Reactor` which reduces latency and avoids blocking IO where possible.

```ruby

if RACK_ENV == :production
use Falcon::Hijack
end

run MyApp

```

## Performance

Falcon is uses an asynchronous event-driven reactor to provide non-blocking IO. It can handle an arbitrary number of in-flight requests with minimal overhead per request.

It uses one Fiber per request, which yields in the presence of blocking IO.

### Memory Usage

Falcon uses a pre-fork model which loads the entire rack application before forking. This reduces per-process memory usage.

### Throughput

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request

## License

Released under the MIT license.

Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
71 changes: 71 additions & 0 deletions Rakefile
@@ -0,0 +1,71 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:test)

task :default => :test

task :server do
require 'async/reactor'
require 'async/http/server'

app = lambda do |env|
[200, {}, ["Hello World"]]
end

server = Async::HTTP::Server.new([
Async::IO::Address.tcp('127.0.0.1', 9294, reuse_port: true)
], app)

Async::Reactor.run do
server.run
end
end

task :client do
require 'async/reactor'
require 'async/http/client'

client = Async::HTTP::Client.new([
Async::IO::Address.tcp('127.0.0.1', 9294, reuse_port: true)
])

Async::Reactor.run do
response = client.get("/")

puts response.inspect
end
end

task :wrk do
require 'async/reactor'
require 'async/http/server'

app = lambda do |env|
[200, {}, ["Hello World"]]
end

server = Async::HTTP::Server.new([
Async::IO::Address.tcp('127.0.0.1', 9294, reuse_port: true)
], app)

process_count = Etc.nprocessors

pids = process_count.times.collect do
fork do
Async::Reactor.run do
server.run
end
end
end

url = "http://127.0.0.1:9294/"

connections = process_count
system("wrk", "-c", connections.to_s, "-d", "2", "-t", connections.to_s, url)

pids.each do |pid|
Process.kill(:KILL, pid)
Process.wait pid
end
end
28 changes: 28 additions & 0 deletions bin/falcon
@@ -0,0 +1,28 @@
#!/usr/bin/env ruby

# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require_relative '../lib/falcon'

begin
Falcon::Command.parse(ARGV).invoke
rescue Interrupt
end
29 changes: 29 additions & 0 deletions falcon.gemspec
@@ -0,0 +1,29 @@
# coding: utf-8
require_relative 'lib/falcon/version'

Gem::Specification.new do |spec|
spec.name = "falcon"
spec.version = Falcon::VERSION
spec.authors = ["Samuel Williams"]
spec.email = ["samuel.williams@oriontransfer.co.nz"]

spec.summary = ""
spec.homepage = "https://github.com/socketry/falcon"

spec.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/})
end
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency("async-http", "~> 0.2")
spec.add_dependency("rack", ">= 1.0")

spec.add_dependency('samovar', "~> 1.3")

spec.add_development_dependency "async-rspec", "~> 1.1"

spec.add_development_dependency "bundler", "~> 1.3"
spec.add_development_dependency "rspec", "~> 3.6"
spec.add_development_dependency "rake"
end
21 changes: 21 additions & 0 deletions lib/falcon.rb
@@ -0,0 +1,21 @@
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require_relative "falcon/command"
82 changes: 82 additions & 0 deletions lib/falcon/command.rb
@@ -0,0 +1,82 @@
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require_relative 'server'

require 'async/reactor'

require 'samovar'
require 'etc'

require 'rack/builder'
require 'rack/server'

module Falcon
module Command
def self.parse(*args)
Top.parse(*args)
end

class Serve < Samovar::Command
options do
option '-c/--config <path>', "Rackup configuration file to load", default: 'config.ru'
option '-n/--concurrency <count>', "Number of processes to start", default: Etc.nprocessors, type: Integer

option '-b/--bind <address>', "Bind to the given hostname/address", default: "tcp://localhost:9292"
end

def run
app, options = Rack::Builder.parse_file(@options[:config])

Async::Container.new(concurrency: @options[:concurrency]) do
server = Falcon::Server.new(app, [
Async::IO::Address.parse(@options[:bind], reuse_port: true)
])

server.run
end
end

def invoke
run

sleep
end
end

class Top < Samovar::Command
nested '<command>',
'serve' => Serve
# 'get' => Get
# 'post' => Post
# 'head' => Head,
# 'put' => Put,
# 'delete' => Delete

def invoke(program_name: File.basename($0))
if @command
@command.invoke
else
print_usage(program_name)
end
end
end
end
end

0 comments on commit e48f093

Please sign in to comment.