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
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
describe RackHandler do
|
5
|
+
|
6
|
+
describe "#prepare_response" do
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#make_env" do
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
@handler = RackHandler.new(nil,{})
|
14
|
+
@handler.request = @handler.parser.run "GET http://www.hostname.com:8888/some_controller/some_action?param1=1¶m2=2 HTTP/1.1"
|
15
|
+
@env = @handler.make_env
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set the REQUEST_METHOD" do
|
19
|
+
@env["REQUEST_METHOD"].should == "GET"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should set the SCRIPT_NAME" do
|
23
|
+
@env["SCRIPT_NAME"].should == ""
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set the PATH_INFO" do
|
27
|
+
@env["PATH_INFO"].should == "/some_controller/some_action"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should set the QUERY_STRING" do
|
31
|
+
@env["QUERY_STRING"].should == "param1=1¶m2=2"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should set the SERVER_NAME" do
|
35
|
+
@env["SERVER_NAME"].should == "www.hostname.com"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should set the SERVER PORT" do
|
39
|
+
@env["SERVER_PORT"].should == "8888"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
describe RequestHandler do
|
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
|
+
end
|
29
|
+
|
30
|
+
describe "#parse_request" do
|
31
|
+
it "should invoke the Parser" do
|
32
|
+
@handler.parser.should_receive(:run)
|
33
|
+
|
34
|
+
@handler.parse_request
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should set the bad-request header if parsing fails" do
|
38
|
+
bad_request = "BAD Warble warble request"
|
39
|
+
@handler.response.status.should be_nil
|
40
|
+
|
41
|
+
@session.stub(:gets).and_return(bad_request)
|
42
|
+
@handler.parse_request
|
43
|
+
|
44
|
+
@handler.response.status.should == 400
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#return_response" do
|
49
|
+
it "should write the response to the socket" do
|
50
|
+
@handler.session.should_receive(:puts).at_least(1).times
|
51
|
+
@handler.stub(:response).and_return("HTTP/1.1 201 OK")
|
52
|
+
@handler.return_response
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#persistent?" do
|
57
|
+
it "should return true if the Connection header is set to keep-alive" do
|
58
|
+
@handler.request = { headers: { "Connection" => "keep-alive" } }
|
59
|
+
|
60
|
+
@handler.persistent?.should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return false if the Connection header is set to close" do
|
64
|
+
@handler.request = { headers: { "Connection" => "close" } }
|
65
|
+
|
66
|
+
@handler.persistent?.should be_false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#run" do
|
71
|
+
it "should call all relevant template methods" do
|
72
|
+
@handler.stub(:client_address)
|
73
|
+
@handler.should_receive(:parse_request).once
|
74
|
+
@handler.should_receive(:prepare_response).once
|
75
|
+
@handler.should_receive(:return_response).once
|
76
|
+
@handler.should_receive(:close_connection).once
|
77
|
+
|
78
|
+
@handler.run(@dummy_request)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "Static requests" do
|
84
|
+
|
85
|
+
before(:each) do
|
86
|
+
@handler = RequestHandler.new
|
87
|
+
@handler.session = @session
|
88
|
+
|
89
|
+
FakeFS.activate!
|
90
|
+
@file_content = "<html><body>success!</body></html>"
|
91
|
+
@file = File.open("index.html", 'w') do |file|
|
92
|
+
file.write @file_content
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
after(:each) do
|
97
|
+
@handler.close_connection
|
98
|
+
FakeFS.deactivate!
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "#prepare_response" do
|
102
|
+
it "should handle a directory" do
|
103
|
+
File.delete("index.html")
|
104
|
+
Dir.mkdir("testdir")
|
105
|
+
@handler.stub(:extract_path).and_return("testdir")
|
106
|
+
@handler.should_receive(:serve_directory).once
|
107
|
+
@handler.prepare_response
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns a file if it exists" do
|
111
|
+
@file = File.new("test.html", "w")
|
112
|
+
@handler.stub(:extract_path).and_return("test.html")
|
113
|
+
@handler.should_receive(:serve_file).once
|
114
|
+
@handler.prepare_response
|
115
|
+
end
|
116
|
+
|
117
|
+
it "handles missing files" do
|
118
|
+
@handler.stub(:extract_path).and_return("non-existing.html")
|
119
|
+
@handler.should_receive(:serve_404_page).once
|
120
|
+
@handler.prepare_response
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#serve_file" do
|
125
|
+
it "should read an existing file" do
|
126
|
+
@handler.should_receive(:read_file).once
|
127
|
+
@handler.serve_file("index.html")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "#serve_directory" do
|
132
|
+
it "should read index file if it exists" do
|
133
|
+
Dir.mkdir("test")
|
134
|
+
File.new("test/index.html", "w")
|
135
|
+
@handler.should_receive(:read_file).once
|
136
|
+
@handler.serve_directory("test")
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should list a directory" do
|
140
|
+
File.delete "index.html"
|
141
|
+
DirectoryLister.should_receive(:list).once
|
142
|
+
@handler.serve_directory(Dir.pwd)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe "#read_file" do
|
147
|
+
it "should return the contents of a file it it exists" do
|
148
|
+
@handler.response.body.should be_empty
|
149
|
+
@handler.read_file("index.html").should == [@file_content]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "#get_mime_type" do
|
154
|
+
it "should return false if the filetype cannot be detected" do
|
155
|
+
@handler.get_mime_type("dumbfile.asdf").should be_false
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should detect plain text files" do
|
159
|
+
@handler.get_mime_type("file.txt").should == "text/plain"
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should return false if the path doent have an extension" do
|
163
|
+
@handler.get_mime_type("asdfasdf").should be_false
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should detect css files" do
|
167
|
+
@handler.get_mime_type("stylesheet.css").should == "text/css"
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should detect javascript files" do
|
171
|
+
@handler.get_mime_type("jquery.js").should == "text/javascript"
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should detect html files" do
|
175
|
+
@handler.get_mime_type("index.html").should == "text/html"
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should detect image files" do
|
179
|
+
["png", "jpg", "jpeg", "gif", "tiff"].each do |filetype|
|
180
|
+
@handler.get_mime_type("image.#{filetype}").should == "image/#{filetype}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should detect application formats" do
|
185
|
+
formats = ["zip","pdf","postscript","x-tar","x-dvi"]
|
186
|
+
formats.each do |filetype|
|
187
|
+
@handler.get_mime_type("file.#{filetype}").should == "application/#{filetype}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "#extract_path" do
|
193
|
+
it "should remove any leading /'s" do
|
194
|
+
@handler.request = { :uri => { :path => "/asdf" } }
|
195
|
+
@handler.extract_path.should == "asdf"
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should replace %20 with space" do
|
199
|
+
@handler.request = { :uri => { :path => "asdf%20sdf%20" } }
|
200
|
+
@handler.extract_path.should == "asdf sdf"
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should return / if the path is /" do
|
204
|
+
@handler.request = { :uri => { :path => "/" } }
|
205
|
+
@handler.extract_path.should == "/"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
describe "Dynamic requests" do
|
210
|
+
before(:each) do
|
211
|
+
@file_content = "!#/bin/ruby\nputs 'Success!'"
|
212
|
+
@file = File.open("app.rb", 'w') do |file|
|
213
|
+
file.write @file_content
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
after(:each) do
|
218
|
+
File.delete "app.rb" if File.exists? "app.rb"
|
219
|
+
end
|
220
|
+
|
221
|
+
describe "#prepare_response" do
|
222
|
+
it "should execute the contents of a ruby file if it exists" do
|
223
|
+
handler = RequestHandler.new
|
224
|
+
handler.stub(:extract_path).and_return("app.rb")
|
225
|
+
handler.prepare_response
|
226
|
+
handler.response.body.should include "Success!\n"
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should should handle interpreter errors" do
|
230
|
+
handler = RequestHandler.new
|
231
|
+
File.open("app.rb", 'w') { |f| f.write "this resolves in an error" }
|
232
|
+
handler.stub(:extract_path).and_return("app.rb")
|
233
|
+
handler.prepare_response
|
234
|
+
handler.response.status.should == 500
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
|
5
|
+
describe Response do
|
6
|
+
before do
|
7
|
+
@response = Response.new
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#status" do
|
11
|
+
it "should is a HTTP status code" do
|
12
|
+
@response.status = 404
|
13
|
+
@response.status.should == 404
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#headers" do
|
18
|
+
it "should is a headers hash" do
|
19
|
+
@response.headers = { "Connection" => "Close" }
|
20
|
+
@response.headers["Connection"].should == "Close"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#body" do
|
25
|
+
it "should be an array containing the response body" do
|
26
|
+
@response.body << "Line 1" << "Line 2"
|
27
|
+
@response.body.size.should == 2
|
28
|
+
@response.body[0].should == "Line 1"
|
29
|
+
@response.body[1].should == "Line 2"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
describe Server do
|
5
|
+
|
6
|
+
after(:each) do
|
7
|
+
stop_server
|
8
|
+
@server.stop if @server
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#start" do
|
12
|
+
it "creates a TCP server" do
|
13
|
+
start_server
|
14
|
+
@server.socket.should_not be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "starts on the supplied port" do
|
18
|
+
@server = Server.new(nil,port: 4000)
|
19
|
+
|
20
|
+
@server.socket.addr.should include(4000)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#stop" do
|
25
|
+
it "notifies the server is stopped" do
|
26
|
+
@server = Server.new(nil,{ output: $console })
|
27
|
+
@server.stop
|
28
|
+
|
29
|
+
$console.should include("Server stopped")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
|
5
|
+
describe WorkerPool do
|
6
|
+
|
7
|
+
describe "#new" do
|
8
|
+
it "should accept a pool size and set it accordingly" do
|
9
|
+
pool = WorkerPool.new 8
|
10
|
+
|
11
|
+
pool.size.should == 8
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should instantiate the pool from the given size" do
|
15
|
+
pool = WorkerPool.new 8
|
16
|
+
|
17
|
+
pool.workers.size.should == 8
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
describe Worker do
|
5
|
+
describe "#new" do
|
6
|
+
it "should accept a handler and store it" do
|
7
|
+
handler = RequestHandler.new
|
8
|
+
worker = Worker.new handler
|
9
|
+
|
10
|
+
worker.handler.should == handler
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#process" do
|
15
|
+
it "should execute the handler with the supplied session" do
|
16
|
+
handler = RequestHandler.new
|
17
|
+
handler.stub(:run).and_return(:response)
|
18
|
+
worker = Worker.new handler
|
19
|
+
|
20
|
+
worker.process("test").should == :response
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/test_objects/1.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
sleep(1); p 'complete: 1s'
|
data/test_objects/3.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
sleep(3); p 'complete: 3s'
|
data/test_objects/5.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
sleep(5); p 'complete: 5s'
|
data/test_objects/app.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
# The root of our app
|
5
|
+
@root = File.expand_path(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
run Proc.new { |env|
|
8
|
+
# Extract the requested path from the request
|
9
|
+
path = Rack::Utils.unescape(env['PATH_INFO'])
|
10
|
+
index_file = @root + "#{path}/index.html"
|
11
|
+
|
12
|
+
if File.exists?(index_file)
|
13
|
+
# Return the index
|
14
|
+
[200, {'Content-Type' => get_mime_type(index_file)}, read_file(index_file)]
|
15
|
+
elsif File.exists?(path) && !File.directory?(path)
|
16
|
+
# Return the file
|
17
|
+
[200, {'Content-Type' => get_mime_type(path)}, read_file(path)]
|
18
|
+
else
|
19
|
+
# Return a directory listing
|
20
|
+
Rack::Directory.new(@root).call(env)
|
21
|
+
end
|
22
|
+
}
|
23
|
+
|
24
|
+
def read_file(path)
|
25
|
+
file_contents = []
|
26
|
+
File.open(path, "r") do |file|
|
27
|
+
while (line = file.gets) do
|
28
|
+
file_contents << line
|
29
|
+
end
|
30
|
+
end
|
31
|
+
file_contents
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def get_mime_type(path)
|
36
|
+
return false unless path.include? '.'
|
37
|
+
filetype = path.split('.').last
|
38
|
+
|
39
|
+
return case
|
40
|
+
when ["html", "htm"].include?(filetype)
|
41
|
+
"text/html"
|
42
|
+
when "txt" == filetype
|
43
|
+
"text/plain"
|
44
|
+
when "css" == filetype
|
45
|
+
"text/css"
|
46
|
+
when "js" == filetype
|
47
|
+
"text/javascript"
|
48
|
+
when ["png", "jpg", "jpeg", "gif", "tiff"].include?(filetype)
|
49
|
+
"image/#{filetype}"
|
50
|
+
when ["zip","pdf","postscript","x-tar","x-dvi"].include?(filetype)
|
51
|
+
"application/#{filetype}"
|
52
|
+
else false
|
53
|
+
end
|
54
|
+
end
|