yarn 0.0.2 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENCE +22 -0
- data/README.md +14 -13
- data/bin/yarn +3 -2
- data/features/server.feature +2 -6
- data/lib/yarn.rb +2 -4
- data/lib/yarn/abstract_handler.rb +116 -0
- data/lib/yarn/logging.rb +3 -2
- data/lib/yarn/{parslet_parser.rb → parser.rb} +8 -10
- data/lib/yarn/rack_handler.rb +21 -8
- data/lib/yarn/request_handler.rb +30 -160
- data/lib/yarn/server.rb +17 -8
- data/lib/yarn/version.rb +1 -1
- data/spec/spec_helper.rb +5 -8
- data/spec/yarn/abstract_handler_spec.rb +92 -0
- data/spec/yarn/{parslet_parser_spec.rb → parser_spec.rb} +2 -5
- data/spec/yarn/rack_handler_spec.rb +2 -1
- data/spec/yarn/request_handler_spec.rb +1 -87
- data/spec/yarn/server_spec.rb +0 -12
- data/test_objects/rails_test/Gemfile +1 -1
- data/yarn.gemspec +2 -3
- metadata +26 -24
data/.gitignore
CHANGED
data/LICENCE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Portions copyright (c) 2011 Jesper Kjeldgaard
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,10 +1,6 @@
|
|
1
1
|
# Yarn #
|
2
2
|
|
3
|
-
Yarn is a multi-
|
4
|
-
It handles concurrent requests by means of a set of workers and a job queue for incomming requests.
|
5
|
-
|
6
|
-
Supports:
|
7
|
-
*
|
3
|
+
Yarn is a multi-process webserver written in Ruby 1.9 by Jesper Kjeldgaard.
|
8
4
|
|
9
5
|
|
10
6
|
## Installation ##
|
@@ -12,16 +8,21 @@ Supports:
|
|
12
8
|
|
13
9
|
|
14
10
|
## Usage ##
|
15
|
-
To use Yarn with Rack applications:
|
16
|
-
|
17
|
-
`rackup -s Yarn <rackup file (config.ru)>`
|
18
|
-
|
19
|
-
|
20
|
-
To use Yarn for serving static and ruby (*.rb) files:
|
21
11
|
|
22
12
|
|
23
|
-
yarn [options]
|
13
|
+
Usage: yarn [options]
|
24
14
|
where [options] are:
|
25
15
|
--host, -h <s>: Hostname or IP address of the server (default: 127.0.0.1)
|
26
16
|
--port, -p <i>: Port number to listen on for incomming requests (default: 3000)
|
27
|
-
--workers, -w <
|
17
|
+
--workers, -w <i>: Number of worker threads (default: 4)
|
18
|
+
--rack, -r <s>: Rackup file <config.ru> (default: off)
|
19
|
+
--log, -l: Enable logging
|
20
|
+
--debug, -d: Output debug messages
|
21
|
+
--version, -v: Print version and exit
|
22
|
+
--help, -e: Show this message
|
23
|
+
|
24
|
+
|
25
|
+
## Todo list ##
|
26
|
+
|
27
|
+
|
28
|
+
* Support persistent connections.
|
data/bin/yarn
CHANGED
@@ -9,7 +9,7 @@ require 'yarn'
|
|
9
9
|
opts = Trollop::options do
|
10
10
|
version "Yarn v#{Yarn::VERSION} 2011 Jesper Kjeldgaard"
|
11
11
|
banner <<-EOS
|
12
|
-
Yarn v#{Yarn::VERSION} is a multiprocess webserver written in Ruby 1.9
|
12
|
+
Yarn v#{Yarn::VERSION} is a multiprocess webserver written in Ruby 1.9
|
13
13
|
|
14
14
|
Usage: yarn [options]
|
15
15
|
where [options] are:
|
@@ -19,7 +19,8 @@ EOS
|
|
19
19
|
opt :port, "Port number to listen on for incomming requests", :default => 3000
|
20
20
|
opt :workers, "Number of worker threads", :default => 4
|
21
21
|
opt :rack, "Rackup file <config.ru>", :default => "off"
|
22
|
-
opt :
|
22
|
+
opt :log, "Enable logging", :default => false
|
23
|
+
opt :debug, "Output debug messages", :default => false
|
23
24
|
end
|
24
25
|
|
25
26
|
server = Yarn::Server.new(opts)
|
data/features/server.feature
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
Feature: Server control
|
2
2
|
|
3
3
|
As a developer
|
4
|
-
I want to be able to
|
5
|
-
So that I can
|
4
|
+
I want to be able to start and stop
|
5
|
+
So that I can serve files to the web
|
6
6
|
|
7
7
|
Scenario: Start server
|
8
8
|
When I start the server on port 3000
|
@@ -12,7 +12,3 @@ Feature: Server control
|
|
12
12
|
Given the server is running
|
13
13
|
When I stop the server
|
14
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"
|
data/lib/yarn.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'pry'
|
2
1
|
require 'rack/handler/yarn'
|
3
2
|
require 'rack/handler'
|
4
3
|
|
@@ -6,16 +5,15 @@ module Yarn
|
|
6
5
|
|
7
6
|
autoload :Server, "yarn/server"
|
8
7
|
autoload :VERSION, "yarn/version"
|
8
|
+
autoload :AbstractHandler, "yarn/abstract_handler"
|
9
9
|
autoload :RequestHandler, "yarn/request_handler"
|
10
10
|
autoload :RackHandler, "yarn/rack_handler"
|
11
11
|
autoload :DirectoryLister, "yarn/directory_lister"
|
12
12
|
autoload :ErrorPage, "yarn/error_page"
|
13
13
|
autoload :Logging, "yarn/logging"
|
14
|
-
autoload :
|
14
|
+
autoload :Parser, "yarn/parser"
|
15
15
|
autoload :Response, "yarn/response"
|
16
16
|
autoload :STATUS_CODES, "yarn/statuses"
|
17
|
-
autoload :Worker, "yarn/worker"
|
18
|
-
autoload :WorkerPool, "yarn/worker_pool"
|
19
17
|
|
20
18
|
Rack::Handler.register 'yarn', 'Yarn'
|
21
19
|
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'parslet'
|
4
|
+
|
5
|
+
module Yarn
|
6
|
+
|
7
|
+
class EmptyRequestError < StandardError; end
|
8
|
+
class ProcessingError < StandardError; end
|
9
|
+
|
10
|
+
class AbstractHandler
|
11
|
+
|
12
|
+
include Logging
|
13
|
+
include ErrorPage
|
14
|
+
|
15
|
+
attr_accessor :session, :parser, :request, :response
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@parser = Parser.new
|
19
|
+
@response = Response.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def run(session)
|
23
|
+
set_common_headers
|
24
|
+
@session = session
|
25
|
+
begin
|
26
|
+
parse_request
|
27
|
+
debug "Request parsed, path: #{request_path}"
|
28
|
+
prepare_response
|
29
|
+
debug "Response prepared: #{@response.status}"
|
30
|
+
return_response
|
31
|
+
log "#{STATUS_CODES[@response.status]} #{client_address} #{request_path}"
|
32
|
+
rescue EmptyRequestError
|
33
|
+
log "Empty request from #{client_address}"
|
34
|
+
ensure
|
35
|
+
@session.close
|
36
|
+
debug "Connection closed"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_request
|
41
|
+
raw_request = read_request
|
42
|
+
raise EmptyRequestError if raw_request.empty?
|
43
|
+
|
44
|
+
begin
|
45
|
+
@request = @parser.run raw_request
|
46
|
+
rescue Parslet::ParseFailed => e
|
47
|
+
@response.status = 400
|
48
|
+
debug "Parse failed: #{@request}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def prepare_response
|
53
|
+
end
|
54
|
+
|
55
|
+
def return_response
|
56
|
+
@session.puts "HTTP/1.1 #{@response.status} #{STATUS_CODES[@response.status]}"
|
57
|
+
@session.puts @response.headers.map { |k,v| "#{k}: #{v}" }
|
58
|
+
@session.puts ""
|
59
|
+
|
60
|
+
@response.body.each do |line|
|
61
|
+
@session.puts line
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def read_request
|
66
|
+
input = []
|
67
|
+
while (line = @session.gets) do
|
68
|
+
length = line.gsub(/\D/,"") if line =~ /Content-Length/
|
69
|
+
if line == "\r\n"
|
70
|
+
input << line
|
71
|
+
input << @session.read(length.to_i) if length
|
72
|
+
break
|
73
|
+
else
|
74
|
+
input << line
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
debug "Done reading request"
|
79
|
+
input.join
|
80
|
+
end
|
81
|
+
|
82
|
+
def persistent?
|
83
|
+
return @request[:headers]["Connection"] == "keep-alive"
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_common_headers
|
87
|
+
@response.headers[:Server] = "Yarn webserver v#{VERSION}"
|
88
|
+
|
89
|
+
# HTTP date format: Fri, 31 Dec 1999 23:59:59 GMT
|
90
|
+
time ||= DateTime.now.new_offset(0)
|
91
|
+
@response.headers[:Date] = time.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
92
|
+
# Close connection header ( until support for persistent connections )
|
93
|
+
@response.headers[:Connection] = "Close"
|
94
|
+
end
|
95
|
+
|
96
|
+
def extract_path
|
97
|
+
path = @request[:uri][:path].to_s
|
98
|
+
if path[0] == "/" && path != "/"
|
99
|
+
path = path[1..-1]
|
100
|
+
end
|
101
|
+
path.gsub(/%20/, " ").strip
|
102
|
+
end
|
103
|
+
|
104
|
+
def post_body
|
105
|
+
@request ? @request[:body].to_s : ""
|
106
|
+
end
|
107
|
+
|
108
|
+
def request_path
|
109
|
+
@request[:uri][:path] if @request
|
110
|
+
end
|
111
|
+
|
112
|
+
def client_address
|
113
|
+
@session.peeraddr(:numeric)[2] if @session
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/yarn/logging.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'logger'
|
2
1
|
require 'date'
|
3
2
|
|
4
3
|
module Yarn
|
5
4
|
module Logging
|
6
5
|
|
7
6
|
def log(msg)
|
7
|
+
return nil unless $log
|
8
8
|
if msg.respond_to?(:each)
|
9
9
|
msg.each do |line|
|
10
10
|
output.puts "#{timestamp} #{line}"
|
@@ -19,7 +19,8 @@ module Yarn
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def output
|
22
|
-
$output || $stdout
|
22
|
+
out ||= $output || $stdout
|
23
|
+
out
|
23
24
|
end
|
24
25
|
|
25
26
|
def timestamp
|
@@ -1,10 +1,8 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'parslet'
|
3
3
|
|
4
|
-
module Parslet::Atoms::DSL; def once; repeat(1); end; end
|
5
|
-
|
6
4
|
module Yarn
|
7
|
-
class
|
5
|
+
class Parser < Parslet::Parser
|
8
6
|
|
9
7
|
# general rules
|
10
8
|
|
@@ -15,9 +13,9 @@ module Yarn
|
|
15
13
|
rule(:spaces) { match('\s+') }
|
16
14
|
|
17
15
|
# header rules
|
18
|
-
rule(:header_value) { match['^\r\n'].
|
16
|
+
rule(:header_value) { match['^\r\n'].repeat(1) }
|
19
17
|
|
20
|
-
rule(:header_name) { match['a-zA-Z\-'].
|
18
|
+
rule(:header_name) { match['a-zA-Z\-'].repeat(1) }
|
21
19
|
|
22
20
|
rule(:header) do
|
23
21
|
header_name.as(:name) >>
|
@@ -28,7 +26,7 @@ module Yarn
|
|
28
26
|
end
|
29
27
|
|
30
28
|
# request-line rules
|
31
|
-
rule(:http_version) { match['HTTP\/\d\.\d'].
|
29
|
+
rule(:http_version) { match['HTTP\/\d\.\d'].repeat(1) }
|
32
30
|
|
33
31
|
rule(:query) do
|
34
32
|
match['\S+'].repeat(1)
|
@@ -37,12 +35,12 @@ module Yarn
|
|
37
35
|
rule(:path) do
|
38
36
|
match['^\?'].repeat(1).as(:path) >>
|
39
37
|
str("?") >>
|
40
|
-
query.as(:query) | match['^\s'].
|
38
|
+
query.as(:query) | match['^\s'].repeat(1).as(:path)
|
41
39
|
end
|
42
40
|
|
43
41
|
rule(:port) { match['\d+'].repeat(1) }
|
44
42
|
|
45
|
-
rule(:host) { match['^\/:'].
|
43
|
+
rule(:host) { match['^\/:'].repeat(1) }
|
46
44
|
|
47
45
|
rule(:absolute_uri) do
|
48
46
|
str("http://") >>
|
@@ -56,7 +54,7 @@ module Yarn
|
|
56
54
|
|
57
55
|
rule(:spaces) { match('\s+') }
|
58
56
|
|
59
|
-
rule(:method) { match['OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT'].
|
57
|
+
rule(:method) { match['OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT'].repeat(1) }
|
60
58
|
|
61
59
|
# RFC2616: Method SP Request-URI SP HTTP-Version CRLF
|
62
60
|
rule(:request_line) do
|
@@ -69,7 +67,7 @@ module Yarn
|
|
69
67
|
end
|
70
68
|
|
71
69
|
# body rule
|
72
|
-
rule(:body) { match['\S'].
|
70
|
+
rule(:body) { match['\S'].repeat(1) }
|
73
71
|
|
74
72
|
# RFC2616: Request-Line *(( header ) CRLF) CRLF [ message-body ]
|
75
73
|
rule(:request) do
|
data/lib/yarn/rack_handler.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
require 'rack'
|
2
|
-
require 'pry'
|
3
2
|
|
4
3
|
module Yarn
|
5
|
-
class RackHandler <
|
4
|
+
class RackHandler < AbstractHandler
|
6
5
|
|
7
|
-
attr_accessor :env
|
6
|
+
attr_accessor :env, :opts
|
8
7
|
|
9
|
-
def initialize(app)
|
10
|
-
@parser =
|
8
|
+
def initialize(app,opts={})
|
9
|
+
@parser = Parser.new
|
11
10
|
@response = Response.new
|
12
11
|
@app = app
|
12
|
+
@host = opts[:host]
|
13
|
+
@port = opts[:port].to_s
|
13
14
|
end
|
14
15
|
|
15
16
|
def prepare_response
|
@@ -23,14 +24,18 @@ module Yarn
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def make_env
|
27
|
+
input = StringIO.new("").set_encoding(Encoding::ASCII_8BIT)
|
28
|
+
if has_body?
|
29
|
+
input.string = @request[:body]
|
30
|
+
end
|
26
31
|
@env = {
|
27
32
|
"REQUEST_METHOD" => @request[:method].to_s,
|
28
33
|
"PATH_INFO" => @request[:uri][:path].to_s,
|
29
34
|
"QUERY_STRING" => @request[:uri][:query].to_s,
|
30
|
-
"SERVER_NAME" => @
|
31
|
-
"SERVER_PORT" => @
|
35
|
+
"SERVER_NAME" => @host,
|
36
|
+
"SERVER_PORT" => @port,
|
32
37
|
"SCRIPT_NAME" => "",
|
33
|
-
"rack.input" =>
|
38
|
+
"rack.input" => input,
|
34
39
|
"rack.version" => Rack::VERSION,
|
35
40
|
"rack.errors" => $output,
|
36
41
|
"rack.multithread" => true,
|
@@ -38,6 +43,14 @@ module Yarn
|
|
38
43
|
"rack.run_once" => false,
|
39
44
|
"rack.url_scheme" => "http"
|
40
45
|
}
|
46
|
+
@env["CONTENT_LENGTH"] = @request[:body].size.to_i if has_body?
|
47
|
+
|
48
|
+
return @env
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_body?
|
52
|
+
value ||= !! @request[:body]
|
53
|
+
value
|
41
54
|
end
|
42
55
|
|
43
56
|
end
|
data/lib/yarn/request_handler.rb
CHANGED
@@ -1,69 +1,15 @@
|
|
1
|
-
require 'date'
|
2
|
-
require 'rubygems'
|
3
|
-
require 'parslet'
|
4
|
-
|
5
1
|
module Yarn
|
6
|
-
|
7
|
-
class EmptyRequestError < StandardError; end
|
8
|
-
class ProcessingError < StandardError; end
|
9
|
-
|
10
|
-
class RequestHandler
|
11
|
-
|
12
|
-
include Logging
|
13
|
-
include ErrorPage
|
14
|
-
|
15
|
-
attr_accessor :session, :parser, :request, :response
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
@parser = ParsletParser.new
|
19
|
-
@response = Response.new
|
20
|
-
end
|
21
|
-
|
22
|
-
def run(session)
|
23
|
-
set_common_headers
|
24
|
-
@session = session
|
25
|
-
begin
|
26
|
-
parse_request
|
27
|
-
debug "Request parsed, path: #{request_path}"
|
28
|
-
prepare_response
|
29
|
-
debug "Response prepared: #{@response.status}"
|
30
|
-
return_response
|
31
|
-
log "#{STATUS_CODES[@response.status]} #{client_address} #{request_path}"
|
32
|
-
rescue EmptyRequestError
|
33
|
-
log "Empty request from #{client_address}"
|
34
|
-
ensure
|
35
|
-
close_connection
|
36
|
-
debug "Connection closed"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def parse_request
|
41
|
-
raw_request = read_request
|
42
|
-
raise EmptyRequestError if raw_request.empty?
|
43
|
-
|
44
|
-
begin
|
45
|
-
@request = @parser.run raw_request
|
46
|
-
rescue Parslet::ParseFailed => e
|
47
|
-
@response.status = 400
|
48
|
-
debug "Parse failed: #{@request}"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
2
|
+
class RequestHandler < AbstractHandler
|
52
3
|
def prepare_response
|
53
4
|
path = extract_path
|
54
5
|
|
55
6
|
@response.headers["Content-Type"] = "text/html"
|
56
7
|
|
57
8
|
begin
|
58
|
-
if File.directory?
|
59
|
-
serve_directory
|
9
|
+
if File.directory?(path)
|
10
|
+
serve_directory(path)
|
60
11
|
elsif File.exists?(path)
|
61
|
-
|
62
|
-
@response.body << execute_script(path)
|
63
|
-
@response.status = 200
|
64
|
-
else
|
65
|
-
serve_file(path)
|
66
|
-
end
|
12
|
+
path =~ /.*\.rb$/ ? serve_ruby_file(path) : serve_file(path)
|
67
13
|
else
|
68
14
|
serve_404_page
|
69
15
|
end
|
@@ -73,72 +19,15 @@ module Yarn
|
|
73
19
|
end
|
74
20
|
end
|
75
21
|
|
76
|
-
def execute_script(path)
|
77
|
-
response = `ruby #{path} #{post_body}`
|
78
|
-
if !! ($?.to_s =~ /1$/)
|
79
|
-
raise ProcessingError
|
80
|
-
else
|
81
|
-
response
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def post_body
|
86
|
-
@request ? @request[:body].to_s : ""
|
87
|
-
end
|
88
|
-
|
89
|
-
def return_response
|
90
|
-
@session.puts "HTTP/1.1 #{@response.status} #{STATUS_CODES[@response.status]}"
|
91
|
-
@session.puts @response.headers.map { |k,v| "#{k}: #{v}" }
|
92
|
-
@session.puts ""
|
93
|
-
|
94
|
-
@response.body.each do |line|
|
95
|
-
@session.puts line
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def close_connection
|
100
|
-
if @session #&& !persistent?
|
101
|
-
@session.close
|
102
|
-
else
|
103
|
-
# TODO: start some kind of timeout
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def read_request
|
108
|
-
input = []
|
109
|
-
while (line = @session.gets) do
|
110
|
-
length = line.gsub(/\D/,"") if line =~ /Content-Length/
|
111
|
-
if line == "\r\n"
|
112
|
-
input << line
|
113
|
-
input << @session.read(length.to_i) if length
|
114
|
-
break
|
115
|
-
else
|
116
|
-
input << line
|
117
|
-
end
|
118
|
-
end
|
119
|
-
@session.close_read
|
120
|
-
debug "Done reading request"
|
121
|
-
input.join
|
122
|
-
end
|
123
|
-
|
124
|
-
def persistent?
|
125
|
-
return @request[:headers]["Connection"] == "keep-alive"
|
126
|
-
end
|
127
|
-
|
128
|
-
def set_common_headers
|
129
|
-
@response.headers[:Server] = "Yarn webserver v#{VERSION}"
|
130
|
-
|
131
|
-
# HTTP date format: Fri, 31 Dec 1999 23:59:59 GMT
|
132
|
-
time = DateTime.now.new_offset(0)
|
133
|
-
@response.headers[:Date] = time.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
134
|
-
# Close connection header ( until support for persistent connections )
|
135
|
-
@response.headers[:Connection] = "Close"
|
136
|
-
end
|
137
|
-
|
138
22
|
def serve_file(path)
|
139
|
-
@response.status = 200
|
140
23
|
@response.body << read_file(path)
|
141
24
|
@response.headers["Content-Type"] = get_mime_type path
|
25
|
+
@response.status = 200
|
26
|
+
end
|
27
|
+
|
28
|
+
def serve_ruby_file(path)
|
29
|
+
@response.body << execute_script(path)
|
30
|
+
@response.status = 200
|
142
31
|
end
|
143
32
|
|
144
33
|
def serve_directory(path)
|
@@ -148,8 +37,7 @@ module Yarn
|
|
148
37
|
@response.headers["Content-Type"] = "text/html"
|
149
38
|
else
|
150
39
|
@response.headers["Content-Type"] = "text/html"
|
151
|
-
|
152
|
-
@response.body << directory_lister.list(path)
|
40
|
+
@response.body << DirectoryLister.list(path)
|
153
41
|
end
|
154
42
|
end
|
155
43
|
|
@@ -160,22 +48,12 @@ module Yarn
|
|
160
48
|
file_contents
|
161
49
|
end
|
162
50
|
|
163
|
-
def
|
164
|
-
|
165
|
-
if
|
166
|
-
|
167
|
-
end
|
168
|
-
path.gsub(/%20/, " ").strip
|
169
|
-
end
|
170
|
-
|
171
|
-
def serve_directory(path)
|
172
|
-
@response.status = 200
|
173
|
-
if File.exists?("index.html")# || File.exists?("/index.html")
|
174
|
-
@response.body = read_file "index.html"
|
175
|
-
@response.headers["Content-Type"] = "text/html"
|
51
|
+
def execute_script(path)
|
52
|
+
response = `ruby #{path} #{post_body}`
|
53
|
+
if !! ($?.to_s =~ /1$/)
|
54
|
+
raise ProcessingError
|
176
55
|
else
|
177
|
-
|
178
|
-
@response.body << DirectoryLister.list(path)
|
56
|
+
response
|
179
57
|
end
|
180
58
|
end
|
181
59
|
|
@@ -184,28 +62,20 @@ module Yarn
|
|
184
62
|
filetype = path.split('.').last
|
185
63
|
|
186
64
|
return case
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
65
|
+
when ["html", "htm"].include?(filetype)
|
66
|
+
"text/html"
|
67
|
+
when "txt" == filetype
|
68
|
+
"text/plain"
|
69
|
+
when "css" == filetype
|
70
|
+
"text/css"
|
71
|
+
when "js" == filetype
|
72
|
+
"text/javascript"
|
73
|
+
when ["png", "jpg", "jpeg", "gif", "tiff"].include?(filetype)
|
74
|
+
"image/#{filetype}"
|
75
|
+
when ["zip","pdf","postscript","x-tar","x-dvi"].include?(filetype)
|
76
|
+
"application/#{filetype}"
|
77
|
+
else false
|
78
|
+
end
|
200
79
|
end
|
201
80
|
end
|
202
|
-
|
203
|
-
def request_path
|
204
|
-
@request[:uri][:path] if @request
|
205
|
-
end
|
206
|
-
|
207
|
-
def client_address
|
208
|
-
@session.peeraddr(:numeric)[2] if @session
|
209
|
-
end
|
210
|
-
end
|
211
81
|
end
|
data/lib/yarn/server.rb
CHANGED
@@ -14,15 +14,18 @@ module Yarn
|
|
14
14
|
host: '127.0.0.1',
|
15
15
|
port: 3000,
|
16
16
|
workers: 4,
|
17
|
+
log: true,
|
17
18
|
rack: "off"
|
18
19
|
}.merge(options)
|
19
20
|
|
20
21
|
@app = nil
|
21
22
|
@app = load_rack_app(opts[:rack]) unless opts[:rack] == "off"
|
23
|
+
@opts = opts
|
22
24
|
|
23
25
|
@host, @port, @num_workers = opts[:host], opts[:port], opts[:workers]
|
24
26
|
@workers = []
|
25
27
|
$output, $debug = opts[:output], opts[:debug]
|
28
|
+
$log = opts[:log] || opts[:debug]
|
26
29
|
end
|
27
30
|
|
28
31
|
def load_rack_app(app_path)
|
@@ -48,14 +51,20 @@ module Yarn
|
|
48
51
|
|
49
52
|
def init_workers
|
50
53
|
@num_workers.times do
|
51
|
-
@workers <<
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
@workers << fork_worker
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def fork_worker
|
59
|
+
fork { worker }
|
60
|
+
end
|
61
|
+
|
62
|
+
def worker
|
63
|
+
trap("INT") { exit }
|
64
|
+
loop do
|
65
|
+
@handler ||= @app ? RackHandler.new(@app,@opts) : RequestHandler.new
|
66
|
+
session = @socket.accept
|
67
|
+
@handler.run session
|
59
68
|
end
|
60
69
|
end
|
61
70
|
|
data/lib/yarn/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -2,18 +2,15 @@ $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'bundler/setup'
|
5
|
+
require 'simplecov'
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
add_filter "lib/yarn/http*"
|
11
|
-
end
|
12
|
-
# end
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter "/spec/"
|
9
|
+
add_filter "lib/rack/handler/"
|
10
|
+
end
|
13
11
|
|
14
12
|
require 'yarn'
|
15
13
|
require 'helpers'
|
16
|
-
require 'pry'
|
17
14
|
|
18
15
|
RSpec.configure do |config|
|
19
16
|
config.include Helpers
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
describe AbstractHandler do
|
5
|
+
describe "Common behaviour" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@dummy_request = "GET /resource/1 HTTP/1.1\r\n "
|
9
|
+
|
10
|
+
@session = mock('TCPSocket')
|
11
|
+
@session.stub(:gets)
|
12
|
+
@session.stub(:close)
|
13
|
+
|
14
|
+
@handler = AbstractHandler.new
|
15
|
+
@handler.session = @session
|
16
|
+
@handler.stub(:read_request).and_return(@dummy_request)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#read_request" do
|
20
|
+
it "should return a string from a feed" do
|
21
|
+
@handler.unstub!(:read_request)
|
22
|
+
@handler.session = StringIO.new
|
23
|
+
@handler.session.string = "line1\nline2\nline3"
|
24
|
+
|
25
|
+
@handler.read_request.should == "line1\nline2\nline3"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should handle POST body" do
|
29
|
+
@req = "POST /app HTTP/1.1\nCookie: $key=value;\nContent-Length: 17;\n\r\nfield1=1&field2=2"
|
30
|
+
@handler.unstub!(:read_request)
|
31
|
+
@handler.session = StringIO.new
|
32
|
+
@handler.session.string = @req
|
33
|
+
|
34
|
+
@handler.read_request.should == @req
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#parse_request" do
|
39
|
+
it "should invoke the Parser" do
|
40
|
+
@handler.parser.should_receive(:run)
|
41
|
+
|
42
|
+
@handler.parse_request
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should set the bad-request header if parsing fails" do
|
46
|
+
bad_request = "BAD Warble warble request"
|
47
|
+
@handler.response.status.should be_nil
|
48
|
+
|
49
|
+
@session.stub(:gets).and_return(bad_request)
|
50
|
+
@handler.parse_request
|
51
|
+
|
52
|
+
@handler.response.status.should == 400
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#return_response" do
|
57
|
+
it "should write the response to the socket" do
|
58
|
+
@handler.session.should_receive(:puts).at_least(1).times
|
59
|
+
@handler.stub(:response).and_return("HTTP/1.1 201 OK")
|
60
|
+
@handler.return_response
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#persistent?" do
|
65
|
+
it "should return true if the Connection header is set to keep-alive" do
|
66
|
+
@handler.request = { headers: { "Connection" => "keep-alive" } }
|
67
|
+
|
68
|
+
@handler.persistent?.should be_true
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should return false if the Connection header is set to close" do
|
72
|
+
@handler.request = { headers: { "Connection" => "close" } }
|
73
|
+
|
74
|
+
@handler.persistent?.should be_false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#run" do
|
79
|
+
it "should call all relevant template methods" do
|
80
|
+
@handler.stub(:client_address)
|
81
|
+
@handler.should_receive(:parse_request).once
|
82
|
+
@handler.should_receive(:prepare_response).once
|
83
|
+
@handler.should_receive(:return_response).once
|
84
|
+
|
85
|
+
@handler.run(@session)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
module Yarn
|
4
|
-
describe
|
4
|
+
describe Parser do
|
5
5
|
before do
|
6
|
-
@parser =
|
6
|
+
@parser = Parser.new
|
7
7
|
end
|
8
8
|
|
9
9
|
describe "#parse request_line" do
|
@@ -53,13 +53,11 @@ module Yarn
|
|
53
53
|
describe "#parse parameters" do
|
54
54
|
it "parses query in the url" do
|
55
55
|
result = @parser.run("GET /page?param1=1¶m2=2 HTTP/1.1\n")
|
56
|
-
puts result
|
57
56
|
result[:uri][:query].should == "param1=1¶m2=2"
|
58
57
|
end
|
59
58
|
|
60
59
|
it "should parse query on rails assets" do
|
61
60
|
result = @parser.run("GET assets/application.js?body=0 HTTP/1.1\n")
|
62
|
-
puts result
|
63
61
|
result[:uri][:query].should == "body=0"
|
64
62
|
result[:uri][:path].should == "assets/application.js"
|
65
63
|
end
|
@@ -116,7 +114,6 @@ module Yarn
|
|
116
114
|
body = "attr=test&attr2=some_other_value&attr3=1231682368125361823"
|
117
115
|
request = "POST /form HTTP/1.1\r\n#{header}\r\n\r\n#{body}"
|
118
116
|
result = @parser.run(request)
|
119
|
-
puts result
|
120
117
|
result[:body].should == body
|
121
118
|
end
|
122
119
|
end
|
@@ -4,7 +4,8 @@ module Yarn
|
|
4
4
|
describe RackHandler do
|
5
5
|
|
6
6
|
before(:each) do
|
7
|
-
@
|
7
|
+
@app = mock('app')
|
8
|
+
@handler = RackHandler.new(@app, { host: "www.hostname.com", port: 8888 })
|
8
9
|
@handler.request = @handler.parser.run "GET http://www.hostname.com:8888/some_controller/some_action?param1=1¶m2=2 HTTP/1.1"
|
9
10
|
end
|
10
11
|
|
@@ -3,92 +3,6 @@ require 'spec_helper'
|
|
3
3
|
module Yarn
|
4
4
|
describe RequestHandler do
|
5
5
|
|
6
|
-
describe "Common behaviour" do
|
7
|
-
|
8
|
-
before(:each) do
|
9
|
-
@dummy_request = "GET /resource/1 HTTP/1.1\r\n "
|
10
|
-
|
11
|
-
@session = mock('TCPSocket')
|
12
|
-
@session.stub(:gets).and_return(@dummy_request)
|
13
|
-
|
14
|
-
@handler = RequestHandler.new
|
15
|
-
@handler.session = @session
|
16
|
-
@handler.stub(:debug,:log).and_return(true) #silence output
|
17
|
-
@handler.stub(:read_request).and_return(@dummy_request)
|
18
|
-
end
|
19
|
-
|
20
|
-
describe "#read_request" do
|
21
|
-
it "should return a string from a feed" do
|
22
|
-
@handler.unstub!(:read_request)
|
23
|
-
@handler.session = StringIO.new
|
24
|
-
@handler.session.string = "line1\nline2\nline3"
|
25
|
-
|
26
|
-
@handler.read_request.should == "line1\nline2\nline3"
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should handle POST body" do
|
30
|
-
@req = "POST /app HTTP/1.1\nCookie: $key=value;\nContent-Length: 17;\n\r\nfield1=1&field2=2"
|
31
|
-
@handler.unstub!(:read_request)
|
32
|
-
@handler.session = StringIO.new
|
33
|
-
@handler.session.string = @req
|
34
|
-
|
35
|
-
@handler.read_request.should == @req
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
describe "#parse_request" do
|
40
|
-
it "should invoke the Parser" do
|
41
|
-
@handler.parser.should_receive(:run)
|
42
|
-
|
43
|
-
@handler.parse_request
|
44
|
-
end
|
45
|
-
|
46
|
-
it "should set the bad-request header if parsing fails" do
|
47
|
-
bad_request = "BAD Warble warble request"
|
48
|
-
@handler.response.status.should be_nil
|
49
|
-
|
50
|
-
@session.stub(:gets).and_return(bad_request)
|
51
|
-
@handler.parse_request
|
52
|
-
|
53
|
-
@handler.response.status.should == 400
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
describe "#return_response" do
|
58
|
-
it "should write the response to the socket" do
|
59
|
-
@handler.session.should_receive(:puts).at_least(1).times
|
60
|
-
@handler.stub(:response).and_return("HTTP/1.1 201 OK")
|
61
|
-
@handler.return_response
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
describe "#persistent?" do
|
66
|
-
it "should return true if the Connection header is set to keep-alive" do
|
67
|
-
@handler.request = { headers: { "Connection" => "keep-alive" } }
|
68
|
-
|
69
|
-
@handler.persistent?.should be_true
|
70
|
-
end
|
71
|
-
|
72
|
-
it "should return false if the Connection header is set to close" do
|
73
|
-
@handler.request = { headers: { "Connection" => "close" } }
|
74
|
-
|
75
|
-
@handler.persistent?.should be_false
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe "#run" do
|
80
|
-
it "should call all relevant template methods" do
|
81
|
-
@handler.stub(:client_address)
|
82
|
-
@handler.should_receive(:parse_request).once
|
83
|
-
@handler.should_receive(:prepare_response).once
|
84
|
-
@handler.should_receive(:return_response).once
|
85
|
-
@handler.should_receive(:close_connection).once
|
86
|
-
|
87
|
-
@handler.run(@dummy_request)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
6
|
describe "Static requests" do
|
93
7
|
|
94
8
|
before(:each) do
|
@@ -103,7 +17,6 @@ module Yarn
|
|
103
17
|
end
|
104
18
|
|
105
19
|
after(:each) do
|
106
|
-
@handler.close_connection
|
107
20
|
File.delete("index.html") if File.exists?("index.html")
|
108
21
|
File.delete("testdir/index.html") if File.exists?("testdir/index.html")
|
109
22
|
Dir.delete("testdir") if Dir.exists?("testdir")
|
@@ -216,6 +129,7 @@ module Yarn
|
|
216
129
|
end
|
217
130
|
end
|
218
131
|
end
|
132
|
+
|
219
133
|
describe "Dynamic requests" do
|
220
134
|
before(:each) do
|
221
135
|
@file_content = "!#/bin/ruby\nputs 'Success!'"
|
data/spec/yarn/server_spec.rb
CHANGED
@@ -47,18 +47,6 @@ module Yarn
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
# describe "#start" do
|
51
|
-
# it "should start the socket_listener" do
|
52
|
-
# @thread = Thread.new do
|
53
|
-
# @server = Server.new({ output: $console })
|
54
|
-
# @server.start
|
55
|
-
# end
|
56
|
-
# sleep 2
|
57
|
-
# get("/").should be_true
|
58
|
-
# @thread.kill
|
59
|
-
# end
|
60
|
-
# end
|
61
|
-
|
62
50
|
describe "#stop" do
|
63
51
|
it "should notify the server is stopped" do
|
64
52
|
$console = MockIO.new
|
data/yarn.gemspec
CHANGED
@@ -8,9 +8,8 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ["Jesper Kjeldgaard"]
|
9
9
|
s.email = ["jkjeldgaard@gmail.com"]
|
10
10
|
s.homepage = "https://github.com/thejspr/yarn"
|
11
|
-
s.summary = %q{Multi-
|
12
|
-
s.description = %q{A multi-
|
13
|
-
|
11
|
+
s.summary = %q{Multi-process webserver for static, dynamic (*.rb) files and Rack applications.}
|
12
|
+
s.description = %q{A multi-process web-server written in Ruby 1.9.}
|
14
13
|
s.rubyforge_project = "yarn"
|
15
14
|
|
16
15
|
s.files = `git ls-files`.split("\n")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yarn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-10-
|
12
|
+
date: 2011-10-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: parslet
|
16
|
-
requirement: &
|
16
|
+
requirement: &12100240 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '1.2'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *12100240
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: trollop
|
27
|
-
requirement: &
|
27
|
+
requirement: &12099740 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '1.16'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *12099740
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rack
|
38
|
-
requirement: &
|
38
|
+
requirement: &12099280 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '1.3'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *12099280
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: cucumber
|
49
|
-
requirement: &
|
49
|
+
requirement: &12098900 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *12098900
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: faraday
|
60
|
-
requirement: &
|
60
|
+
requirement: &12098440 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *12098440
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: nokogiri
|
71
|
-
requirement: &
|
71
|
+
requirement: &12098020 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *12098020
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: rspec
|
82
|
-
requirement: &
|
82
|
+
requirement: &12097600 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *12097600
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: simplecov
|
93
|
-
requirement: &
|
93
|
+
requirement: &12097180 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,8 +98,8 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
102
|
-
description: A multi-
|
101
|
+
version_requirements: *12097180
|
102
|
+
description: A multi-process web-server written in Ruby 1.9.
|
103
103
|
email:
|
104
104
|
- jkjeldgaard@gmail.com
|
105
105
|
executables:
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- .rvmrc
|
114
114
|
- Gemfile
|
115
115
|
- Gemfile.lock
|
116
|
+
- LICENCE
|
116
117
|
- README.md
|
117
118
|
- Rakefile
|
118
119
|
- bin/yarn
|
@@ -132,10 +133,11 @@ files:
|
|
132
133
|
- features/support/hooks.rb
|
133
134
|
- lib/rack/handler/yarn.rb
|
134
135
|
- lib/yarn.rb
|
136
|
+
- lib/yarn/abstract_handler.rb
|
135
137
|
- lib/yarn/directory_lister.rb
|
136
138
|
- lib/yarn/error_page.rb
|
137
139
|
- lib/yarn/logging.rb
|
138
|
-
- lib/yarn/
|
140
|
+
- lib/yarn/parser.rb
|
139
141
|
- lib/yarn/rack_handler.rb
|
140
142
|
- lib/yarn/request_handler.rb
|
141
143
|
- lib/yarn/response.rb
|
@@ -145,10 +147,11 @@ files:
|
|
145
147
|
- spec/helpers.rb
|
146
148
|
- spec/rack/handler/yarn_spec.rb
|
147
149
|
- spec/spec_helper.rb
|
150
|
+
- spec/yarn/abstract_handler_spec.rb
|
148
151
|
- spec/yarn/directory_lister_spec.rb
|
149
152
|
- spec/yarn/error_page_spec.rb
|
150
153
|
- spec/yarn/logging_spec.rb
|
151
|
-
- spec/yarn/
|
154
|
+
- spec/yarn/parser_spec.rb
|
152
155
|
- spec/yarn/rack_handler_spec.rb
|
153
156
|
- spec/yarn/request_handler_spec.rb
|
154
157
|
- spec/yarn/response_spec.rb
|
@@ -219,9 +222,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
219
222
|
version: '0'
|
220
223
|
requirements: []
|
221
224
|
rubyforge_project: yarn
|
222
|
-
rubygems_version: 1.8.
|
225
|
+
rubygems_version: 1.8.11
|
223
226
|
signing_key:
|
224
227
|
specification_version: 3
|
225
|
-
summary: Multi-
|
226
|
-
applications.
|
228
|
+
summary: Multi-process webserver for static, dynamic (*.rb) files and Rack applications.
|
227
229
|
test_files: []
|