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,54 @@
|
|
1
|
+
module Yarn
|
2
|
+
STATUS_CODES = {
|
3
|
+
# 1xx Informational
|
4
|
+
100 => 'Continue',
|
5
|
+
101 => 'Switching Protocols',
|
6
|
+
102 => 'Processing',
|
7
|
+
103 => 'Checkpoint',
|
8
|
+
122 => 'Request-URI too long',
|
9
|
+
# 2xx Success
|
10
|
+
200 => 'OK',
|
11
|
+
201 => 'Created',
|
12
|
+
202 => 'Accepted',
|
13
|
+
203 => 'Non-Authoritative Information',
|
14
|
+
204 => 'No Content',
|
15
|
+
205 => 'Reset Content',
|
16
|
+
206 => 'Partial Content',
|
17
|
+
207 => 'Multi-Status',
|
18
|
+
226 => 'IM Used',
|
19
|
+
# 3xx Redirection
|
20
|
+
300 => 'Multiple Choices',
|
21
|
+
301 => 'Moved Permanently',
|
22
|
+
302 => 'Moved Temporarily',
|
23
|
+
303 => 'See Other',
|
24
|
+
304 => 'Not Modified',
|
25
|
+
305 => 'Use Proxy',
|
26
|
+
307 => 'Temporary Redirect',
|
27
|
+
308 => 'Resume Incomplete',
|
28
|
+
# 4xx Client Error
|
29
|
+
400 => 'Bad Request',
|
30
|
+
401 => 'Unauthorized',
|
31
|
+
402 => 'Payment Required',
|
32
|
+
403 => 'Forbidden',
|
33
|
+
404 => 'Not Found',
|
34
|
+
405 => 'Method Not Allowed',
|
35
|
+
406 => 'Not Acceptable',
|
36
|
+
407 => 'Proxy Authentication Required',
|
37
|
+
408 => 'Request Time-out',
|
38
|
+
409 => 'Conflict',
|
39
|
+
410 => 'Gone',
|
40
|
+
411 => 'Length Required',
|
41
|
+
412 => 'Precondition Failed',
|
42
|
+
413 => 'Request Entity Too Large',
|
43
|
+
414 => 'Request-URI Too Large',
|
44
|
+
415 => 'Unsupported Media Type',
|
45
|
+
416 => 'Requested Range Not Satisfiable',
|
46
|
+
# 5xx Server Error
|
47
|
+
500 => 'Internal Server Error',
|
48
|
+
501 => 'Not Implemented',
|
49
|
+
502 => 'Bad Gateway',
|
50
|
+
503 => 'Service Unavailable',
|
51
|
+
504 => 'Gateway Time-out',
|
52
|
+
505 => 'HTTP Version not supported'
|
53
|
+
}
|
54
|
+
end
|
data/lib/yarn/version.rb
ADDED
data/lib/yarn/worker.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'yarn/worker'
|
2
|
+
require 'yarn/request_handler'
|
3
|
+
require 'yarn/logging'
|
4
|
+
|
5
|
+
require 'thread'
|
6
|
+
|
7
|
+
module Yarn
|
8
|
+
class WorkerPool
|
9
|
+
|
10
|
+
include Logging
|
11
|
+
|
12
|
+
attr_reader :size, :workers
|
13
|
+
|
14
|
+
def initialize(size=1024)
|
15
|
+
@size = size
|
16
|
+
@workers = create_pool
|
17
|
+
@jobs = Queue.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_pool
|
21
|
+
return Array.new(@size) do
|
22
|
+
Thread.new do
|
23
|
+
loop do
|
24
|
+
job = @jobs.pop
|
25
|
+
job.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def schedule(&block)
|
32
|
+
@jobs << block
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
data/spec/helpers.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'faraday'
|
3
|
+
|
4
|
+
module Helpers
|
5
|
+
def send_data(data)
|
6
|
+
socket = TCPSocket.new(@server.host, @server.port)
|
7
|
+
socket.write data
|
8
|
+
out = socket.read
|
9
|
+
socket.close
|
10
|
+
out
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(url)
|
14
|
+
setup
|
15
|
+
@connection.get "test_objects#{url}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def post(url, params={})
|
19
|
+
setup
|
20
|
+
@connection.post url, params
|
21
|
+
end
|
22
|
+
|
23
|
+
def stop_server
|
24
|
+
@thread.kill if @thread
|
25
|
+
@server.stop if @server
|
26
|
+
end
|
27
|
+
|
28
|
+
def start_server(port=3000,handler=:static)
|
29
|
+
$console = MockIO.new
|
30
|
+
@server = Yarn::Server.new({ port: port, output: $console })
|
31
|
+
@thread = Thread.new { @server.start }
|
32
|
+
sleep 0.1 until @server.socket # wait for socket to be created
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def testfile_exists?(filename)
|
37
|
+
File.exists? File.join(File.join(File.dirname(__FILE__), "/../"), "test_objects/#{filename}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_html?(response)
|
41
|
+
begin
|
42
|
+
lambda { Nokogiri::HTML(response) { |config| config.strict } }
|
43
|
+
rescue Nokogiri::HTML::SyntaxError
|
44
|
+
false
|
45
|
+
else
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
private
|
50
|
+
|
51
|
+
def setup
|
52
|
+
host = URI.parse("http://#{@server.host}:#{@server.port}")
|
53
|
+
@connection ||= Faraday.new(host)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class MockIO
|
58
|
+
def initialize(content="")
|
59
|
+
@contents ||= []
|
60
|
+
@contents << content
|
61
|
+
end
|
62
|
+
|
63
|
+
def puts(string)
|
64
|
+
@contents << string
|
65
|
+
end
|
66
|
+
|
67
|
+
def gets
|
68
|
+
@contents.last
|
69
|
+
end
|
70
|
+
|
71
|
+
def contains?(string)
|
72
|
+
@contents.each do |line|
|
73
|
+
if line.include? string
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
false
|
78
|
+
end
|
79
|
+
|
80
|
+
def include?(string)
|
81
|
+
contains?(string)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('../../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'fakefs'
|
6
|
+
|
7
|
+
# unless /jruby/ =~ `ruby -v`
|
8
|
+
require 'simplecov'
|
9
|
+
SimpleCov.start do
|
10
|
+
FakeFS.deactivate!
|
11
|
+
add_filter "/spec/"
|
12
|
+
add_filter "lib/yarn/http*"
|
13
|
+
end
|
14
|
+
# end
|
15
|
+
|
16
|
+
require 'capybara/rspec'
|
17
|
+
|
18
|
+
require 'yarn'
|
19
|
+
require 'helpers'
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.include Helpers
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Yarn
|
5
|
+
|
6
|
+
describe DirectoryLister do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
FakeFS.deactivate!
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#list" do
|
13
|
+
it "returns valid HTML for a directory" do
|
14
|
+
response = DirectoryLister.list("lib")
|
15
|
+
response.should_not be_nil
|
16
|
+
valid_html?(response).should be_true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns valid HTML a long path" do
|
20
|
+
FakeFS.deactivate!
|
21
|
+
response = DirectoryLister.list("lib/yarn")
|
22
|
+
response.should_not be_nil
|
23
|
+
valid_html?(response).should be_true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#format_size" do
|
28
|
+
it "should format bytes into B" do
|
29
|
+
DirectoryLister.format_size(1.0).should == "1.00B"
|
30
|
+
end
|
31
|
+
it "should format bytes into KB" do
|
32
|
+
DirectoryLister.format_size(1024.0).should == "1.00KB"
|
33
|
+
end
|
34
|
+
it "should format bytes into MB" do
|
35
|
+
DirectoryLister.format_size(1024**2).should == "1.00MB"
|
36
|
+
end
|
37
|
+
it "should format bytes into GB" do
|
38
|
+
DirectoryLister.format_size(1024**3).should == "1.00GB"
|
39
|
+
end
|
40
|
+
it "should format bytes into TB" do
|
41
|
+
DirectoryLister.format_size(1024.0**4).should == "1.00TB"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
describe ErrorPage do
|
5
|
+
before(:each) do
|
6
|
+
@handler = RequestHandler.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#serve_404_page" do
|
10
|
+
it "should set the HTTP code" do
|
11
|
+
@handler.serve_404_page
|
12
|
+
@handler.response.status.should == 404
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should set the body" do
|
16
|
+
@handler.serve_404_page
|
17
|
+
@handler.response.body.should_not be_empty
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#serve_500_page" do
|
22
|
+
it "should set the HTTP code" do
|
23
|
+
@handler.serve_500_page
|
24
|
+
@handler.response.status.should == 500
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should set the body" do
|
28
|
+
@handler.serve_500_page
|
29
|
+
@handler.response.body.should_not be_empty
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
|
5
|
+
class TestLoggging
|
6
|
+
include Logging
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Logging do
|
10
|
+
|
11
|
+
describe "#log" do
|
12
|
+
it "should make the logging methods available" do
|
13
|
+
@server = Server.new(output: $output)
|
14
|
+
@server.should respond_to(:log)
|
15
|
+
@server.should respond_to(:debug)
|
16
|
+
@server.socket.close
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be available in the handler classes" do
|
20
|
+
@handler = RequestHandler.new
|
21
|
+
|
22
|
+
@handler.should respond_to(:log)
|
23
|
+
@handler.should respond_to(:debug)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should send the message to output" do
|
27
|
+
test_logger = TestLoggging.new
|
28
|
+
test_logger.output.should_receive(:puts).once
|
29
|
+
|
30
|
+
test_logger.log "testing"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should handles arrays" do
|
34
|
+
test_logger = TestLoggging.new
|
35
|
+
test_logger.output.should_receive(:puts).twice
|
36
|
+
|
37
|
+
test_logger.log [1,2]
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#debug" do
|
43
|
+
it "should invoke the log method with a message" do
|
44
|
+
test_logger = TestLoggging.new
|
45
|
+
test_logger.should_receive(:log).with("DEBUG: testing").once
|
46
|
+
|
47
|
+
test_logger.debug "testing"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Yarn
|
4
|
+
describe ParsletParser do
|
5
|
+
before do
|
6
|
+
@parser = ParsletParser.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#parse request_line" do
|
10
|
+
it "parses all request method types" do
|
11
|
+
['OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT'].each do |method|
|
12
|
+
@parser.run(method + ' / HTTP/1.1')[:method].to_s.should == method
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "parses no-resource URI" do
|
17
|
+
@parser.run("GET * HTTP/1.1")[:uri].to_s.should == "*"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "parses the host" do
|
21
|
+
result = @parser.run("GET http://www.test.com/index.html HTTP/1.1")
|
22
|
+
result[:uri][:host].to_s.should == "www.test.com"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "parses the path" do
|
26
|
+
result = @parser.run("GET http://www.test.com/index.html HTTP/1.1")
|
27
|
+
result[:uri][:path].to_s.should == "/index.html"
|
28
|
+
end
|
29
|
+
|
30
|
+
it "parses absolute URI" do
|
31
|
+
result = @parser.run("GET http://187.123.231.12:8976/some/resource HTTP/1.1")
|
32
|
+
result[:uri][:host].should == "187.123.231.12"
|
33
|
+
result[:uri][:port].should == "8976"
|
34
|
+
result[:uri][:path].should == "/some/resource"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "parses absolute paths" do
|
38
|
+
result = @parser.run("GET /another/resource HTTP/1.1")
|
39
|
+
result[:uri][:path].should == "/another/resource"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "parses HTTP version" do
|
43
|
+
result = @parser.run("GET /another/resource HTTP/1.1")
|
44
|
+
result[:version].to_s.should == "HTTP/1.1"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "parses crlf" do
|
48
|
+
lambda { @parser.run("GET / HTTP/1.1\r\n") }.should_not raise_error
|
49
|
+
lambda { @parser.run("GET / HTTP/1.1") }.should_not raise_error
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#parse parameters" do
|
54
|
+
it "parses query in the url" do
|
55
|
+
result = @parser.run("GET /page?param1=1¶m2=2 HTTP/1.1\n")
|
56
|
+
puts result
|
57
|
+
result[:uri][:query].should == "param1=1¶m2=2"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#parse headers" do
|
62
|
+
it "accepts a request with no headers" do
|
63
|
+
result = @parser.run("GET /another/resource HTTP/1.1\r\n")
|
64
|
+
result[:headers].should be_empty
|
65
|
+
end
|
66
|
+
|
67
|
+
it "parses a Content-Length header" do
|
68
|
+
request = "POST /resource/1/save HTTP/1.1\r\nContent-Length: 345\r\n"
|
69
|
+
result = @parser.run(request)
|
70
|
+
|
71
|
+
result[:headers]["Content-Length"].should == "345"
|
72
|
+
end
|
73
|
+
|
74
|
+
it "parses a Cookie header" do
|
75
|
+
cookie = "Cookie: $Version=1; Skin=new;"
|
76
|
+
request = "POST /form HTTP/1.1\r\n#{cookie}\r\n"
|
77
|
+
result = @parser.run(request)
|
78
|
+
result[:headers]["Cookie"].should == "$Version=1; Skin=new;"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "parses multiple headers" do
|
82
|
+
cookie = "Cookie: $Version=1; Skin=new;"
|
83
|
+
user_agent_value = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"
|
84
|
+
user_agent = "User-Agent: #{user_agent_value}"
|
85
|
+
request = "POST /form HTTP/1.1\r\n#{cookie}\r\n#{user_agent}"
|
86
|
+
result = @parser.run(request)
|
87
|
+
|
88
|
+
result[:headers].length.should == 2
|
89
|
+
result[:headers]["User-Agent"].should == user_agent_value
|
90
|
+
end
|
91
|
+
|
92
|
+
it "parses an empty header" do
|
93
|
+
lambda { @parser.run("GET / HTTP/1.1\r\nAccept: \r\n") }.should_not raise_error
|
94
|
+
lambda { @parser.run("GET / HTTP/1.1\r\nAccept:") }.should_not raise_error
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|