Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add basic CLI

  • Loading branch information...
commit 77cb6a1c36509859b67502f32937c98866327e3e 1 parent 13db27f
@lgierth authored
View
1  Gemfile
@@ -3,6 +3,7 @@ source :rubygems
gemspec
gem "em-synchrony", :git => "https://github.com/igrigorik/em-synchrony"
+gem "em-http-request", :git => "https://github.com/igrigorik/em-http-request"
gem "rake"
gem "awesome_print"
View
4 bin/hatetepe
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+require "hatetepe/cli"
+Hatetepe::CLI.start
View
3  hatetepe.gemspec
@@ -17,8 +17,11 @@ Gem::Specification.new do |s|
s.add_dependency "em-synchrony"
s.add_dependency "rack"
s.add_dependency "async-rack"
+ s.add_dependency "thor"
s.add_development_dependency "rspec"
+ s.add_development_dependency "fakefs"
+ s.add_development_dependency "em-http-request"
s.files = `git ls-files`.split("\n") - [".gitignore"]
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
View
64 lib/hatetepe/cli.rb
@@ -1,8 +1,72 @@
+require "logger"
require "thor"
require "hatetepe"
module Hatetepe
class CLI < Thor
+ map "--version" => :version
+ map "-v" => :version
+
+ default_task :start
+
+ desc :version, "Print version information"
+ def version
+ say Rity::VERSION
+ end
+
+ desc :start, "Start an instance of Rity"
+ method_option :bind, :aliases => "-b", :type => :string,
+ :banner => "Bind to the specified TCP interface (default: 127.0.0.1)"
+ method_option :port, :aliases => "-p", :type => :numeric,
+ :banner => "Bind to the specified port (default: 3000)"
+ method_option :rackup, :aliases => "-r", :type => :string,
+ :banner => "Load specified rackup (.ru) file (default: config.ru)"
+ method_option :quiet, :aliases => "-q", :type => :boolean,
+ :banner => "Don't log"
+ method_option :verbose, :aliases => "-V", :type => :boolean,
+ :banner => "Log debugging data"
+ def start
+ log = Logger.new($stderr)
+ started_at = Time.now - 0.001
+ log.formatter = proc do |severity, time, progname, message|
+ time -= started_at
+ "[#{time.round 6}] #{message}\n"
+ end
+
+ log.level = if options[:verbose]
+ Logger::DEBUG
+ elsif options[:quiet]
+ Logger::FATAL
+ else
+ Logger::INFO
+ end
+
+ rackup = options[:rackup] || "config.ru"
+ log.info "booting from #{File.expand_path rackup}"
+ app = Rack::Builder.parse_file(rackup)[0]
+
+ EM.synchrony do
+ trap("INT") { EM.stop }
+ trap("TERM") { EM.stop }
+
+ EM.epoll
+
+ host = options[:bind] || "127.0.0.1"
+ port = options[:port] || 3000
+
+ log.info "binding to #{host}:#{port}"
+ Server.start({
+ :app => app,
+ :log => log,
+ :host => host,
+ :port => port
+ })
+ end
+ rescue StandardError => ex
+ log.fatal ex.message
+ log << ex.backtrace.map {|line| " #{line}\n" }.join("")
+ raise ex
+ end
end
end
View
6 lib/hatetepe/request.rb
@@ -14,7 +14,11 @@ def initialize(verb, uri, http_version = "1.1")
def to_hash
{
"hatetepe.request" => self,
- "rack.input" => body
+ "rack.input" => body,
+ "REQUEST_URI" => uri.dup,
+ "REQUEST_METHOD" => verb.dup,
+ "PATH_INFO" => uri.dup,
+ "SCRIPT_NAME" => ""
}.tap {|hash|
headers.each {|key, value|
hash["HTTP_#{key.upcase.gsub(/[^A-Z_]/, "_")}"] = value
View
10 lib/hatetepe/server.rb
@@ -14,7 +14,7 @@ def self.start(config)
#Prefork.run server if config[:prefork]
end
- attr_reader :app, :log
+ attr_reader :app, :log, :config
attr_reader :requests, :parser, :builder
def initialize(config)
@@ -25,6 +25,7 @@ def initialize(config)
}
@log = config[:log]
+ @config = config
super
end
@@ -51,6 +52,13 @@ def process(*)
e["hatetepe.connection"] = self
e["rack.input"].source = self
+ e["SERVER_NAME"] = config[:host].dup
+ e["SERVER_PORT"] = String(config[:port])
+
+ host = e["HTTP_HOST"] || config[:host].dup
+ host += ":#{config[:port]}" unless host.include? ":"
+ e["HTTP_HOST"] = host
+
e["stream.start"] = proc {|response|
EM::Synchrony.sync previous if previous
response[1]["Server"] = "hatetepe/#{VERSION}"
View
169 spec/integration/cli/start_spec.rb
@@ -0,0 +1,169 @@
+require "spec_helper"
+require "hatetepe/cli"
+require "socket"
+
+describe "start command" do
+ def hook_event_loop(&block)
+ EM.spec_hooks << block
+ end
+
+ def add_stop_timer(timeout)
+ hook_event_loop do
+ EM.add_timer(timeout) { EM.stop }
+ end
+ end
+
+ before do
+ $stderr = StringIO.new ""
+
+ FakeFS.activate!
+ File.open("config.ru", "w") do |f|
+ f.write %q{run proc {|e| [200, {"Content-Type" => "text/plain"}, [e["REQUEST_URI"]]] }}
+ end
+ File.open("config2.ru", "w") do |f|
+ f.write %q{run proc {|e| [200, {"Content-Type" => "text/plain"}, ["config2.ru loaded"]] }}
+ end
+ end
+
+ after do
+ $stderr = STDERR
+
+ FakeFS.deactivate!
+ FakeFS::FileSystem.clear
+ end
+
+ it "starts an instance of Rity" do
+ add_stop_timer 0.01
+ hook_event_loop do
+ Socket.tcp("127.0.0.1", 3000) {|*| }
+ end
+ Hatetepe::CLI.start %w{}
+
+ $stderr.string.should include("127.0.0.1:3000")
+ end
+
+ it "answers HTTP requests" do
+ add_stop_timer 0.02
+ hook_event_loop do
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
+ response = EM::Synchrony.sync(request)
+
+ response.response_header.status.should == 200
+ response.response_header["CONTENT_TYPE"].should == "text/plain"
+ response.response.should == "/"
+ end
+ Hatetepe::CLI.start %w{}
+ end
+
+ describe "--port option" do
+ it "changes the listen port" do
+ add_stop_timer 0.01
+ hook_event_loop do
+ Socket.tcp("127.0.0.1", 3001) {|*| }
+ end
+ Hatetepe::CLI.start %w{--port=3001}
+
+ $stderr.string.should include(":3001")
+ end
+
+ it "has an alias: -p" do
+ add_stop_timer 0.01
+ hook_event_loop do
+ Socket.tcp("127.0.0.1", 3002) {|*| }
+ end
+ Hatetepe::CLI.start %w{-p 3002}
+
+ $stderr.string.should include(":3002")
+ end
+ end
+
+ describe "--bind option" do
+ it "changes the listen interface" do
+ add_stop_timer 0.01
+ hook_event_loop do
+ Socket.tcp("127.0.0.2", 3000) {|*| }
+ end
+ Hatetepe::CLI.start %w{--bind=127.0.0.2}
+
+ $stderr.string.should include("127.0.0.2:")
+ end
+
+ it "has an alias: -b" do
+ add_stop_timer 0.01
+ hook_event_loop do
+ Socket.tcp("127.0.0.3", 3000) {|*| }
+ end
+ Hatetepe::CLI.start %w{-b 127.0.0.3}
+
+ $stderr.string.should include("127.0.0.3:")
+ end
+ end
+
+ describe "--rackup option" do
+ it "changes the rackup file that'll be loaded" do
+ add_stop_timer 0.01
+ hook_event_loop do
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
+ response = EM::Synchrony.sync(request)
+ response.response.should include("config2.ru")
+ end
+ Hatetepe::CLI.start %w{--rackup=config2.ru}
+ end
+
+ it "has an alias: -r" do
+ add_stop_timer 0.01
+ hook_event_loop do
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
+ response = EM::Synchrony.sync(request)
+ response.response.should include("config2.ru")
+ end
+ Hatetepe::CLI.start %w{-r config2.ru}
+ end
+ end
+
+ describe "--quiet option" do
+ it "discards all output" do
+ pending
+
+ add_stop_timer 0.01
+ Hatetepe::CLI.start %w{--quiet}
+
+ $stderr.string.should be_empty
+ end
+
+ it "has an alias: -q" do
+ pending
+
+ add_stop_timer 0.01
+ Hatetepe::CLI.start %w{-q}
+
+ $stderr.string.should be_empty
+ end
+ end
+
+ describe "--verbose option" do
+ it "prints debugging data" do
+ pending
+
+ add_stop_timer 0.01
+ hook_event_loop do
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
+ end
+ Hatetepe::CLI.start %w{--verbose}
+
+ $stderr.string.split("\n").size.should > 10
+ end
+
+ it "has an alias: -V" do
+ pending
+
+ add_stop_timer 0.01
+ hook_event_loop do
+ request = EM::HttpRequest.new("http://127.0.0.1:3000").aget
+ end
+ Hatetepe::CLI.start %w{-V}
+
+ $stderr.string.split("\n").size.should > 10
+ end
+ end
+end
View
34 spec/spec_helper.rb
@@ -1,3 +1,37 @@
begin
require "awesome_print"
rescue LoadError; end
+
+require "em-synchrony"
+require "em-synchrony/em-http"
+require "fakefs/safe"
+
+RSpec.configure {|config|
+ config.before {
+ EM.class_eval {
+ @spec_hooks = []
+ class << self
+ attr_reader :spec_hooks
+ def synchrony_with_hooks(blk = nil, tail = nil, &block)
+ synchrony_without_hooks do
+ (blk || block).call
+ @spec_hooks.each {|sh| sh.call }
+ end
+ end
+ alias_method :synchrony_without_hooks, :synchrony
+ alias_method :synchrony, :synchrony_with_hooks
+ end
+ }
+ }
+
+ config.after {
+ EM.class_eval {
+ @spec_hooks = nil
+ class << self
+ remove_method :spec_hooks
+ alias_method :synchrony, :synchrony_without_hooks
+ remove_method :synchrony_with_hooks
+ end
+ }
+ }
+}
View
8 spec/unit/server_spec.rb
@@ -18,8 +18,8 @@
let(:app) { stub "app" }
let(:log) { stub "log" }
- let(:host) { stub "host" }
- let(:port) { stub "port" }
+ let(:host) { "127.0.4.1" }
+ let(:port) { 8081 }
let(:config) {
{
:app => app,
@@ -98,6 +98,10 @@
e.should equal(env)
e["hatetepe.connection"].should equal(server)
e["rack.input"].source.should equal(server)
+ e["SERVER_NAME"].should == host
+ e["SERVER_NAME"].should_not equal(host)
+ e["SERVER_PORT"].should == String(port)
+ e["HTTP_HOST"].should == "#{host}:#{port}"
[-1]
}
server.process
Please sign in to comment.
Something went wrong with that request. Please try again.