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.
Files changed (58) hide show
  1. data/.autotest +5 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +10 -0
  5. data/README.md +3 -0
  6. data/Rakefile +4 -0
  7. data/bin/yarn +24 -0
  8. data/cucumber.yml +3 -0
  9. data/features/concurrency.feature +15 -0
  10. data/features/dynamic_request.feature +13 -0
  11. data/features/logger.feature +16 -0
  12. data/features/rack.feature +18 -0
  13. data/features/server.feature +18 -0
  14. data/features/static_request.feature +25 -0
  15. data/features/step_definitions/concurrency_steps.rb +34 -0
  16. data/features/step_definitions/rack_steps.rb +3 -0
  17. data/features/step_definitions/server_steps.rb +42 -0
  18. data/features/step_definitions/web_steps.rb +7 -0
  19. data/features/support/env.rb +20 -0
  20. data/features/support/hooks.rb +5 -0
  21. data/lib/rack/handler/yarn.rb +21 -0
  22. data/lib/yarn.rb +22 -0
  23. data/lib/yarn/directory_lister.rb +62 -0
  24. data/lib/yarn/error_page.rb +16 -0
  25. data/lib/yarn/logging.rb +30 -0
  26. data/lib/yarn/parslet_parser.rb +114 -0
  27. data/lib/yarn/rack_handler.rb +42 -0
  28. data/lib/yarn/request_handler.rb +202 -0
  29. data/lib/yarn/response.rb +40 -0
  30. data/lib/yarn/server.rb +59 -0
  31. data/lib/yarn/statuses.rb +54 -0
  32. data/lib/yarn/version.rb +3 -0
  33. data/lib/yarn/worker.rb +19 -0
  34. data/lib/yarn/worker_pool.rb +36 -0
  35. data/spec/helpers.rb +84 -0
  36. data/spec/spec_helper.rb +23 -0
  37. data/spec/yarn/directory_lister_spec.rb +46 -0
  38. data/spec/yarn/error_page_spec.rb +33 -0
  39. data/spec/yarn/logging_spec.rb +52 -0
  40. data/spec/yarn/parslet_parser_spec.rb +99 -0
  41. data/spec/yarn/rack_handler_spec.rb +43 -0
  42. data/spec/yarn/request_handler_spec.rb +240 -0
  43. data/spec/yarn/response_spec.rb +36 -0
  44. data/spec/yarn/server_spec.rb +34 -0
  45. data/spec/yarn/worker_pool_spec.rb +23 -0
  46. data/spec/yarn/worker_spec.rb +26 -0
  47. data/test_objects/.gitignore +9 -0
  48. data/test_objects/1.rb +1 -0
  49. data/test_objects/3.rb +1 -0
  50. data/test_objects/5.rb +1 -0
  51. data/test_objects/app.rb +6 -0
  52. data/test_objects/app2.rb +6 -0
  53. data/test_objects/config.ru +54 -0
  54. data/test_objects/index.html +13 -0
  55. data/test_objects/jquery.js +8865 -0
  56. data/test_objects/simple_rack.rb +12 -0
  57. data/yarn.gemspec +34 -0
  58. 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&param2=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&param2=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
@@ -0,0 +1,9 @@
1
+ coverage/*
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
6
+ *.rbc
7
+ doc/*
8
+ rails_app
9
+ rails_app/*
@@ -0,0 +1 @@
1
+ sleep(1); p 'complete: 1s'
@@ -0,0 +1 @@
1
+ sleep(3); p 'complete: 3s'
@@ -0,0 +1 @@
1
+ sleep(5); p 'complete: 5s'
@@ -0,0 +1,6 @@
1
+ #!/bin/ruby
2
+
3
+ puts "<h1>Dynamic script: #{__FILE__}</h1>"
4
+ puts "2^8 = #{2**8}"
5
+ puts "<br>"
6
+ puts "Dynamic request complete"
@@ -0,0 +1,6 @@
1
+ #!/bin/ruby
2
+
3
+ puts "<h1>Dynamic script: #{__FILE__}</h1>"
4
+ puts "2^8 = #{2**8}"
5
+ puts "<br>"
6
+ puts "Dynamic request complete"
@@ -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