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,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
@@ -0,0 +1,3 @@
1
+ module Yarn
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,19 @@
1
+ # require 'celluloid'
2
+
3
+ module Yarn
4
+ class Worker
5
+
6
+ # include Celluloid
7
+
8
+ attr_reader :handler
9
+
10
+ def initialize(handler)
11
+ @handler = handler
12
+ end
13
+
14
+ def process(session)
15
+ @handler.run session
16
+ end
17
+
18
+ end
19
+ end
@@ -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
@@ -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
+
@@ -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&param2=2 HTTP/1.1\n")
56
+ puts result
57
+ result[:uri][:query].should == "param1=1&param2=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