yarn 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +5 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/Gemfile +10 -0
- data/README.md +3 -0
- data/Rakefile +4 -0
- data/bin/yarn +24 -0
- data/cucumber.yml +3 -0
- data/features/concurrency.feature +15 -0
- data/features/dynamic_request.feature +13 -0
- data/features/logger.feature +16 -0
- data/features/rack.feature +18 -0
- data/features/server.feature +18 -0
- data/features/static_request.feature +25 -0
- data/features/step_definitions/concurrency_steps.rb +34 -0
- data/features/step_definitions/rack_steps.rb +3 -0
- data/features/step_definitions/server_steps.rb +42 -0
- data/features/step_definitions/web_steps.rb +7 -0
- data/features/support/env.rb +20 -0
- data/features/support/hooks.rb +5 -0
- data/lib/rack/handler/yarn.rb +21 -0
- data/lib/yarn.rb +22 -0
- data/lib/yarn/directory_lister.rb +62 -0
- data/lib/yarn/error_page.rb +16 -0
- data/lib/yarn/logging.rb +30 -0
- data/lib/yarn/parslet_parser.rb +114 -0
- data/lib/yarn/rack_handler.rb +42 -0
- data/lib/yarn/request_handler.rb +202 -0
- data/lib/yarn/response.rb +40 -0
- data/lib/yarn/server.rb +59 -0
- data/lib/yarn/statuses.rb +54 -0
- data/lib/yarn/version.rb +3 -0
- data/lib/yarn/worker.rb +19 -0
- data/lib/yarn/worker_pool.rb +36 -0
- data/spec/helpers.rb +84 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/yarn/directory_lister_spec.rb +46 -0
- data/spec/yarn/error_page_spec.rb +33 -0
- data/spec/yarn/logging_spec.rb +52 -0
- data/spec/yarn/parslet_parser_spec.rb +99 -0
- data/spec/yarn/rack_handler_spec.rb +43 -0
- data/spec/yarn/request_handler_spec.rb +240 -0
- data/spec/yarn/response_spec.rb +36 -0
- data/spec/yarn/server_spec.rb +34 -0
- data/spec/yarn/worker_pool_spec.rb +23 -0
- data/spec/yarn/worker_spec.rb +26 -0
- data/test_objects/.gitignore +9 -0
- data/test_objects/1.rb +1 -0
- data/test_objects/3.rb +1 -0
- data/test_objects/5.rb +1 -0
- data/test_objects/app.rb +6 -0
- data/test_objects/app2.rb +6 -0
- data/test_objects/config.ru +54 -0
- data/test_objects/index.html +13 -0
- data/test_objects/jquery.js +8865 -0
- data/test_objects/simple_rack.rb +12 -0
- data/yarn.gemspec +34 -0
- metadata +227 -0
data/.autotest
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/yarn
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'trollop'
|
7
|
+
require 'yarn'
|
8
|
+
|
9
|
+
opts = Trollop::options do
|
10
|
+
version "Yarn v.#{Yarn::VERSION} 2011 Jesper Kjeldgaard"
|
11
|
+
banner <<-EOS
|
12
|
+
Yarn v.#{Yarn::VERSION} is a multithreaded webserver written in Ruby 1.9.
|
13
|
+
|
14
|
+
Usage: yarn [options]
|
15
|
+
where [options] are:
|
16
|
+
EOS
|
17
|
+
|
18
|
+
opt :host, "Hostname or IP address of the server", :default => "127.0.0.1"
|
19
|
+
opt :port, "Port number to listen on for incomming requests", :default => 3000
|
20
|
+
opt :rackup_file, "Rackup file (e.g. config.ru). If not given Yarn will serve static and dynamic (*.rb) files.", :type => String
|
21
|
+
end
|
22
|
+
|
23
|
+
server = Yarn::Server.new(nil,opts)
|
24
|
+
server.start
|
data/cucumber.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Feature: Concurrency
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want to be able to serve multiple requests in parallel
|
5
|
+
To increase server throughput
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the server is running as "dynamic"
|
9
|
+
|
10
|
+
Scenario: Perform two requests in parallel
|
11
|
+
Given a client "A"
|
12
|
+
And a client "B"
|
13
|
+
When client "A" makes a "3" seconds request
|
14
|
+
And client "B" makes a "1" second request
|
15
|
+
Then client "B" receives a response before client "A"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Feature: Dynamic request
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want to be able to serve ruby files
|
5
|
+
In order to provide dynamic content
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the server is running as "dynamic"
|
9
|
+
|
10
|
+
Scenario: Serve a dynamic Ruby file
|
11
|
+
Given the file "/app.rb" exist
|
12
|
+
When I go to "/app.rb"
|
13
|
+
Then the response should contain "Dynamic request complete"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Feature: Logger
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want logging functionality
|
5
|
+
To be able to debug and monitor server usage
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the server is running
|
9
|
+
|
10
|
+
Scenario: Log messages
|
11
|
+
When I log "log message"
|
12
|
+
Then I should see "log message"
|
13
|
+
|
14
|
+
Scenario: Log debug messages
|
15
|
+
When I debug "debug message"
|
16
|
+
Then I should see "DEBUG: debug message"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: Implement rack interface
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want to have a rack handler
|
5
|
+
In order to serve rack applications
|
6
|
+
|
7
|
+
@wip
|
8
|
+
Scenario: Serve a one-file rack application
|
9
|
+
Given I have a rack application "simple_rack.rb"
|
10
|
+
And the server is running as "rack"
|
11
|
+
When I go to "/"
|
12
|
+
Then the response should contain "rack works"
|
13
|
+
|
14
|
+
Scenario: Serve a rails application
|
15
|
+
Given I have a rails application "rails_test"
|
16
|
+
And the server is running as "rack"
|
17
|
+
When I go to "/rails_text/home/index"
|
18
|
+
Then the response should contain "Rack rails works"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: Server control
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want to be able to command the server
|
5
|
+
So that I can control the server
|
6
|
+
|
7
|
+
Scenario: Start server
|
8
|
+
When I start the server on port 3000
|
9
|
+
Then I should see "Server started on port 3000"
|
10
|
+
|
11
|
+
Scenario: Stop server
|
12
|
+
Given the server is running
|
13
|
+
When I stop the server
|
14
|
+
Then I should see "Server stopped"
|
15
|
+
|
16
|
+
Scenario: Supply port number when starting the server
|
17
|
+
When I start the server on port 4000
|
18
|
+
Then I should see "Server started on port 4000"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Feature: Static file requests
|
2
|
+
|
3
|
+
As a web-developer
|
4
|
+
I want to be able to serve static files
|
5
|
+
To provide fast content on the Internet
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the server is running as "static"
|
9
|
+
|
10
|
+
Scenario: Serve a static html file
|
11
|
+
Given the file "index.html" exist
|
12
|
+
When I go to "/index.html"
|
13
|
+
Then the response should contain "Success!"
|
14
|
+
|
15
|
+
Scenario: Serve an javascript file
|
16
|
+
Given the file "jquery.js" exist
|
17
|
+
When I go to "/jquery.js"
|
18
|
+
Then the response should contain "jQuery JavaScript Library"
|
19
|
+
And the response should contain "})(window);"
|
20
|
+
|
21
|
+
Scenario: Show an error message if a resource doesnt exist
|
22
|
+
Given the file "non-existent-file.html" does not exist
|
23
|
+
When I go to "non-existent-file.html"
|
24
|
+
Then the response should contain "404"
|
25
|
+
Then the response should contain "File does not exist"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Given /^a client "([^"]*)"$/ do |client|
|
2
|
+
@clients = {}
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^client "([^"]*)" makes a "([^"]*)" seconds? request$/ do |client,speed|
|
6
|
+
@responses = {}
|
7
|
+
filename = "test_objects/#{speed}.rb"
|
8
|
+
File.delete filename if File.exists? filename
|
9
|
+
File.open(filename, 'w') { |f| f.write "sleep(#{speed}); p 'complete: #{speed}s'\n" }
|
10
|
+
|
11
|
+
@clients[client] = Thread.new do
|
12
|
+
result = get "/#{speed}.rb"
|
13
|
+
@responses[client] = result.body
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Then /^client "([^"]*)" receives a response before client "([^"]*)"$/ do |c1,c2|
|
18
|
+
@success = false
|
19
|
+
response_listener = Thread.new do
|
20
|
+
while !@success do
|
21
|
+
if @responses[c1] =~ /complete/ && !@responses[c2]
|
22
|
+
@success = true
|
23
|
+
break
|
24
|
+
elsif @responses[c2] =~ /complete/
|
25
|
+
@success = false
|
26
|
+
break
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
response_listener.join
|
32
|
+
|
33
|
+
@success.should be_true
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
include Helpers
|
2
|
+
|
3
|
+
When /^I start the server on port (\d+)$/ do |port|
|
4
|
+
start_server port
|
5
|
+
end
|
6
|
+
|
7
|
+
When /^I stop the server$/ do
|
8
|
+
stop_server unless @server.nil?
|
9
|
+
end
|
10
|
+
|
11
|
+
Given /^the server is running$/ do
|
12
|
+
start_server
|
13
|
+
end
|
14
|
+
|
15
|
+
Given /^the server is running as "([^"]*)"$/ do |handler_type|
|
16
|
+
start_server(3000, handler_type.to_sym)
|
17
|
+
end
|
18
|
+
|
19
|
+
Given /^the server is not running$/ do
|
20
|
+
stop_server unless @server.nil?
|
21
|
+
@server = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
Given /^the file "([^"]*)" exist$/ do |file|
|
25
|
+
testfile_exists?(file).should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
Given /^the file "([^"]*)" does not exist$/ do |file|
|
29
|
+
testfile_exists?(file).should be_false
|
30
|
+
end
|
31
|
+
|
32
|
+
When /^I log "([^"]*)"$/ do |message|
|
33
|
+
@server.log message
|
34
|
+
end
|
35
|
+
|
36
|
+
When /^I debug "([^"]*)"$/ do |message|
|
37
|
+
@server.debug message
|
38
|
+
end
|
39
|
+
|
40
|
+
Then /^I should see "([^"]*)"$/ do |message|
|
41
|
+
$console.contains? message
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('../../../lib', __FILE__)
|
2
|
+
$LOAD_PATH << File.expand_path('../../../spec', __FILE__)
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
require 'capybara'
|
7
|
+
require 'capybara/dsl'
|
8
|
+
require 'capybara/cucumber'
|
9
|
+
require 'capybara-webkit'
|
10
|
+
|
11
|
+
require 'yarn'
|
12
|
+
require 'helpers'
|
13
|
+
|
14
|
+
Capybara.javascript_driver = :webkit
|
15
|
+
Capybara.default_driver = :webkit
|
16
|
+
|
17
|
+
Capybara.run_server = false
|
18
|
+
Capybara.app_host = "http://127.0.0.1:3000"
|
19
|
+
|
20
|
+
Capybara.default_wait_time = 5
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "yarn"
|
2
|
+
require "rack"
|
3
|
+
require "rack/handler"
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Handler
|
7
|
+
class Yarn
|
8
|
+
def self.run(app, options={})
|
9
|
+
server = ::Yarn::Server.new(app,options)
|
10
|
+
server.start
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.valid_options
|
14
|
+
{
|
15
|
+
"Host=HOST" => "Hostname to listen on (default: 127.0.0.1)",
|
16
|
+
"Port=PORT" => "Port to listen on (default: 3000)",
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/yarn.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'rack/handler/yarn'
|
3
|
+
require 'rack/handler'
|
4
|
+
|
5
|
+
module Yarn
|
6
|
+
|
7
|
+
autoload :Server, "yarn/server"
|
8
|
+
autoload :VERSION, "yarn/version"
|
9
|
+
autoload :RequestHandler, "yarn/request_handler"
|
10
|
+
autoload :RackHandler, "yarn/rack_handler"
|
11
|
+
autoload :DirectoryLister, "yarn/directory_lister"
|
12
|
+
autoload :ErrorPage, "yarn/error_page"
|
13
|
+
autoload :Logging, "yarn/logging"
|
14
|
+
autoload :ParsletParser, "yarn/parslet_parser"
|
15
|
+
autoload :Response, "yarn/response"
|
16
|
+
autoload :STATUS_CODES, "yarn/statuses"
|
17
|
+
autoload :Worker, "yarn/worker"
|
18
|
+
autoload :WorkerPool, "yarn/worker_pool"
|
19
|
+
|
20
|
+
Rack::Handler.register 'yarn', 'Yarn'
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'yarn/logging'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
class DirectoryLister
|
5
|
+
|
6
|
+
include Logging
|
7
|
+
|
8
|
+
def self.list(path)
|
9
|
+
response = []
|
10
|
+
response << <<-EOS
|
11
|
+
<html><head><title>Directory Listing</title></head><body><h1>Directory Listing</h1><table cellpadding='4'><thead><td><b>Filename</b></td><td><b>Size</b></></thead><tbody>
|
12
|
+
EOS
|
13
|
+
|
14
|
+
real_path = File.join(".",path)
|
15
|
+
dir = Dir.entries(real_path).sort
|
16
|
+
|
17
|
+
dir.each do |entry|
|
18
|
+
size = ""
|
19
|
+
if entry == "."
|
20
|
+
url = ""
|
21
|
+
name = "."
|
22
|
+
elsif entry == ".."
|
23
|
+
next if ["/", ""].include?(path)
|
24
|
+
path_arr = path.split("/")
|
25
|
+
if path_arr.size == 1
|
26
|
+
url = ""
|
27
|
+
else
|
28
|
+
url = path_arr[0..path_arr.size-2].join("/")
|
29
|
+
end
|
30
|
+
name = ".."
|
31
|
+
elsif File.exist?(File.join(real_path,entry))
|
32
|
+
url = ["/", ""].include?(path) ? entry : "#{path}/#{entry}"
|
33
|
+
name = entry
|
34
|
+
entry_path = "#{real_path}/#{entry}"
|
35
|
+
unless File.directory?(entry_path)
|
36
|
+
size = format_size File.stat("#{real_path}/#{entry}").size
|
37
|
+
end
|
38
|
+
else
|
39
|
+
next
|
40
|
+
end
|
41
|
+
|
42
|
+
url = "/#{url}"
|
43
|
+
|
44
|
+
response << "<tr><td><a href=\"#{url}\">#{name}</a></td><td>#{size}</td></tr>"
|
45
|
+
end
|
46
|
+
|
47
|
+
response << ["</tbody>", "</table", "</body>", "</html>"]
|
48
|
+
|
49
|
+
return response
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.format_size(size)
|
53
|
+
count = 0
|
54
|
+
while size >= 1024 and count < 4
|
55
|
+
size /= 1024.0
|
56
|
+
count += 1
|
57
|
+
end
|
58
|
+
format("%.2f",size) + %w(B KB MB GB TB)[count]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
module Yarn
|
3
|
+
module ErrorPage
|
4
|
+
|
5
|
+
def serve_404_page
|
6
|
+
@response.status = 404
|
7
|
+
@response.body = ["<html><head><title>404</title></head><body><h1>File does not exist.</h1></body><html>"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def serve_500_page
|
11
|
+
@response.status = 500
|
12
|
+
@response.body = ["<h1>Yarn!?</h1>\nA server error occured."]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
data/lib/yarn/logging.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Yarn
|
5
|
+
module Logging
|
6
|
+
|
7
|
+
def log(msg)
|
8
|
+
if msg.respond_to?(:each)
|
9
|
+
msg.each do |line|
|
10
|
+
output.puts "#{timestamp} #{line}"
|
11
|
+
end
|
12
|
+
else
|
13
|
+
output.puts "#{timestamp} #{msg}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def debug(msg=nil)
|
18
|
+
log "DEBUG: #{msg || yield}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def output
|
22
|
+
$output || $stdout
|
23
|
+
end
|
24
|
+
|
25
|
+
def timestamp
|
26
|
+
current_time = DateTime.now
|
27
|
+
"#{current_time.strftime("%d/%m/%y %H:%M:%S")} -"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|