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,5 @@
1
+ # require 'test_notifier/runner/autotest'
2
+ Autotest.add_hook :initialize do |at|
3
+ at.add_exception(%r{^\./\.git})
4
+ at.add_exception(%r{^\./coverage})
5
+ end
@@ -0,0 +1,9 @@
1
+ coverage/*
2
+ test_objects/rdoc/*
3
+ *.gem
4
+ .bundle
5
+ Gemfile.lock
6
+ pkg/*
7
+ *.rbc
8
+ doc/*
9
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in threaded_server.gemspec
4
+ gemspec
5
+
6
+ # gems for testing
7
+ gem 'autotest'
8
+
9
+ # development gems
10
+ gem 'pry'
@@ -0,0 +1,3 @@
1
+ # Yarn #
2
+
3
+ A multi-threaded webserver written in Ruby 1.9 by Jesper Kjeldgaard.
@@ -0,0 +1,4 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new('spec')
3
+
4
+ require 'bundler/gem_tasks'
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'rubygems'
6
+ require 'trollop'
7
+ require 'yarn'
8
+
9
+ opts = Trollop::options do
10
+ version "Yarn v.#{Yarn::VERSION} 2011 Jesper Kjeldgaard"
11
+ banner <<-EOS
12
+ Yarn v.#{Yarn::VERSION} is a multithreaded webserver written in Ruby 1.9.
13
+
14
+ Usage: yarn [options]
15
+ where [options] are:
16
+ EOS
17
+
18
+ opt :host, "Hostname or IP address of the server", :default => "127.0.0.1"
19
+ opt :port, "Port number to listen on for incomming requests", :default => 3000
20
+ opt :rackup_file, "Rackup file (e.g. config.ru). If not given Yarn will serve static and dynamic (*.rb) files.", :type => String
21
+ end
22
+
23
+ server = Yarn::Server.new(nil,opts)
24
+ server.start
@@ -0,0 +1,3 @@
1
+ default: --color --format pretty
2
+ autotest: --color --format pretty --tag @wip
3
+ # autotest: --color --format pretty
@@ -0,0 +1,15 @@
1
+ Feature: Concurrency
2
+
3
+ As a developer
4
+ I want to be able to serve multiple requests in parallel
5
+ To increase server throughput
6
+
7
+ Background:
8
+ Given the server is running as "dynamic"
9
+
10
+ Scenario: Perform two requests in parallel
11
+ Given a client "A"
12
+ And a client "B"
13
+ When client "A" makes a "3" seconds request
14
+ And client "B" makes a "1" second request
15
+ Then client "B" receives a response before client "A"
@@ -0,0 +1,13 @@
1
+ Feature: Dynamic request
2
+
3
+ As a developer
4
+ I want to be able to serve ruby files
5
+ In order to provide dynamic content
6
+
7
+ Background:
8
+ Given the server is running as "dynamic"
9
+
10
+ Scenario: Serve a dynamic Ruby file
11
+ Given the file "/app.rb" exist
12
+ When I go to "/app.rb"
13
+ Then the response should contain "Dynamic request complete"
@@ -0,0 +1,16 @@
1
+ Feature: Logger
2
+
3
+ As a developer
4
+ I want logging functionality
5
+ To be able to debug and monitor server usage
6
+
7
+ Background:
8
+ Given the server is running
9
+
10
+ Scenario: Log messages
11
+ When I log "log message"
12
+ Then I should see "log message"
13
+
14
+ Scenario: Log debug messages
15
+ When I debug "debug message"
16
+ Then I should see "DEBUG: debug message"
@@ -0,0 +1,18 @@
1
+ Feature: Implement rack interface
2
+
3
+ As a developer
4
+ I want to have a rack handler
5
+ In order to serve rack applications
6
+
7
+ @wip
8
+ Scenario: Serve a one-file rack application
9
+ Given I have a rack application "simple_rack.rb"
10
+ And the server is running as "rack"
11
+ When I go to "/"
12
+ Then the response should contain "rack works"
13
+
14
+ Scenario: Serve a rails application
15
+ Given I have a rails application "rails_test"
16
+ And the server is running as "rack"
17
+ When I go to "/rails_text/home/index"
18
+ Then the response should contain "Rack rails works"
@@ -0,0 +1,18 @@
1
+ Feature: Server control
2
+
3
+ As a developer
4
+ I want to be able to command the server
5
+ So that I can control the server
6
+
7
+ Scenario: Start server
8
+ When I start the server on port 3000
9
+ Then I should see "Server started on port 3000"
10
+
11
+ Scenario: Stop server
12
+ Given the server is running
13
+ When I stop the server
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"
@@ -0,0 +1,25 @@
1
+ Feature: Static file requests
2
+
3
+ As a web-developer
4
+ I want to be able to serve static files
5
+ To provide fast content on the Internet
6
+
7
+ Background:
8
+ Given the server is running as "static"
9
+
10
+ Scenario: Serve a static html file
11
+ Given the file "index.html" exist
12
+ When I go to "/index.html"
13
+ Then the response should contain "Success!"
14
+
15
+ Scenario: Serve an javascript file
16
+ Given the file "jquery.js" exist
17
+ When I go to "/jquery.js"
18
+ Then the response should contain "jQuery JavaScript Library"
19
+ And the response should contain "})(window);"
20
+
21
+ Scenario: Show an error message if a resource doesnt exist
22
+ Given the file "non-existent-file.html" does not exist
23
+ When I go to "non-existent-file.html"
24
+ Then the response should contain "404"
25
+ Then the response should contain "File does not exist"
@@ -0,0 +1,34 @@
1
+ Given /^a client "([^"]*)"$/ do |client|
2
+ @clients = {}
3
+ end
4
+
5
+ When /^client "([^"]*)" makes a "([^"]*)" seconds? request$/ do |client,speed|
6
+ @responses = {}
7
+ filename = "test_objects/#{speed}.rb"
8
+ File.delete filename if File.exists? filename
9
+ File.open(filename, 'w') { |f| f.write "sleep(#{speed}); p 'complete: #{speed}s'\n" }
10
+
11
+ @clients[client] = Thread.new do
12
+ result = get "/#{speed}.rb"
13
+ @responses[client] = result.body
14
+ end
15
+ end
16
+
17
+ Then /^client "([^"]*)" receives a response before client "([^"]*)"$/ do |c1,c2|
18
+ @success = false
19
+ response_listener = Thread.new do
20
+ while !@success do
21
+ if @responses[c1] =~ /complete/ && !@responses[c2]
22
+ @success = true
23
+ break
24
+ elsif @responses[c2] =~ /complete/
25
+ @success = false
26
+ break
27
+ end
28
+ end
29
+ end
30
+
31
+ response_listener.join
32
+
33
+ @success.should be_true
34
+ end
@@ -0,0 +1,3 @@
1
+ Given /^I have a rack application "([^"]*)"$/ do |app|
2
+ testfile_exists?(app).should be_true
3
+ end
@@ -0,0 +1,42 @@
1
+ include Helpers
2
+
3
+ When /^I start the server on port (\d+)$/ do |port|
4
+ start_server port
5
+ end
6
+
7
+ When /^I stop the server$/ do
8
+ stop_server unless @server.nil?
9
+ end
10
+
11
+ Given /^the server is running$/ do
12
+ start_server
13
+ end
14
+
15
+ Given /^the server is running as "([^"]*)"$/ do |handler_type|
16
+ start_server(3000, handler_type.to_sym)
17
+ end
18
+
19
+ Given /^the server is not running$/ do
20
+ stop_server unless @server.nil?
21
+ @server = nil
22
+ end
23
+
24
+ Given /^the file "([^"]*)" exist$/ do |file|
25
+ testfile_exists?(file).should be_true
26
+ end
27
+
28
+ Given /^the file "([^"]*)" does not exist$/ do |file|
29
+ testfile_exists?(file).should be_false
30
+ end
31
+
32
+ When /^I log "([^"]*)"$/ do |message|
33
+ @server.log message
34
+ end
35
+
36
+ When /^I debug "([^"]*)"$/ do |message|
37
+ @server.debug message
38
+ end
39
+
40
+ Then /^I should see "([^"]*)"$/ do |message|
41
+ $console.contains? message
42
+ end
@@ -0,0 +1,7 @@
1
+ When /^(?:|I )go to "([^"]*)"$/ do |url|
2
+ @response = get url
3
+ end
4
+
5
+ Then /^the response should contain "([^"]*)"$/ do |content|
6
+ @response.body.should include(content)
7
+ end
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
2
+ $LOAD_PATH << File.expand_path('../../../spec', __FILE__)
3
+
4
+ require 'rubygems'
5
+
6
+ require 'capybara'
7
+ require 'capybara/dsl'
8
+ require 'capybara/cucumber'
9
+ require 'capybara-webkit'
10
+
11
+ require 'yarn'
12
+ require 'helpers'
13
+
14
+ Capybara.javascript_driver = :webkit
15
+ Capybara.default_driver = :webkit
16
+
17
+ Capybara.run_server = false
18
+ Capybara.app_host = "http://127.0.0.1:3000"
19
+
20
+ Capybara.default_wait_time = 5
@@ -0,0 +1,5 @@
1
+ include Helpers
2
+
3
+ After do |scenario|
4
+ stop_server
5
+ end
@@ -0,0 +1,21 @@
1
+ require "yarn"
2
+ require "rack"
3
+ require "rack/handler"
4
+
5
+ module Rack
6
+ module Handler
7
+ class Yarn
8
+ def self.run(app, options={})
9
+ server = ::Yarn::Server.new(app,options)
10
+ server.start
11
+ end
12
+
13
+ def self.valid_options
14
+ {
15
+ "Host=HOST" => "Hostname to listen on (default: 127.0.0.1)",
16
+ "Port=PORT" => "Port to listen on (default: 3000)",
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ require 'pry'
2
+ require 'rack/handler/yarn'
3
+ require 'rack/handler'
4
+
5
+ module Yarn
6
+
7
+ autoload :Server, "yarn/server"
8
+ autoload :VERSION, "yarn/version"
9
+ autoload :RequestHandler, "yarn/request_handler"
10
+ autoload :RackHandler, "yarn/rack_handler"
11
+ autoload :DirectoryLister, "yarn/directory_lister"
12
+ autoload :ErrorPage, "yarn/error_page"
13
+ autoload :Logging, "yarn/logging"
14
+ autoload :ParsletParser, "yarn/parslet_parser"
15
+ autoload :Response, "yarn/response"
16
+ autoload :STATUS_CODES, "yarn/statuses"
17
+ autoload :Worker, "yarn/worker"
18
+ autoload :WorkerPool, "yarn/worker_pool"
19
+
20
+ Rack::Handler.register 'yarn', 'Yarn'
21
+
22
+ end
@@ -0,0 +1,62 @@
1
+ require 'yarn/logging'
2
+
3
+ module Yarn
4
+ class DirectoryLister
5
+
6
+ include Logging
7
+
8
+ def self.list(path)
9
+ response = []
10
+ response << <<-EOS
11
+ <html><head><title>Directory Listing</title></head><body><h1>Directory Listing</h1><table cellpadding='4'><thead><td><b>Filename</b></td><td><b>Size</b></></thead><tbody>
12
+ EOS
13
+
14
+ real_path = File.join(".",path)
15
+ dir = Dir.entries(real_path).sort
16
+
17
+ dir.each do |entry|
18
+ size = ""
19
+ if entry == "."
20
+ url = ""
21
+ name = "."
22
+ elsif entry == ".."
23
+ next if ["/", ""].include?(path)
24
+ path_arr = path.split("/")
25
+ if path_arr.size == 1
26
+ url = ""
27
+ else
28
+ url = path_arr[0..path_arr.size-2].join("/")
29
+ end
30
+ name = ".."
31
+ elsif File.exist?(File.join(real_path,entry))
32
+ url = ["/", ""].include?(path) ? entry : "#{path}/#{entry}"
33
+ name = entry
34
+ entry_path = "#{real_path}/#{entry}"
35
+ unless File.directory?(entry_path)
36
+ size = format_size File.stat("#{real_path}/#{entry}").size
37
+ end
38
+ else
39
+ next
40
+ end
41
+
42
+ url = "/#{url}"
43
+
44
+ response << "<tr><td><a href=\"#{url}\">#{name}</a></td><td>#{size}</td></tr>"
45
+ end
46
+
47
+ response << ["</tbody>", "</table", "</body>", "</html>"]
48
+
49
+ return response
50
+ end
51
+
52
+ def self.format_size(size)
53
+ count = 0
54
+ while size >= 1024 and count < 4
55
+ size /= 1024.0
56
+ count += 1
57
+ end
58
+ format("%.2f",size) + %w(B KB MB GB TB)[count]
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module Yarn
3
+ module ErrorPage
4
+
5
+ def serve_404_page
6
+ @response.status = 404
7
+ @response.body = ["<html><head><title>404</title></head><body><h1>File does not exist.</h1></body><html>"]
8
+ end
9
+
10
+ def serve_500_page
11
+ @response.status = 500
12
+ @response.body = ["<h1>Yarn!?</h1>\nA server error occured."]
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ require 'logger'
2
+ require 'date'
3
+
4
+ module Yarn
5
+ module Logging
6
+
7
+ def log(msg)
8
+ if msg.respond_to?(:each)
9
+ msg.each do |line|
10
+ output.puts "#{timestamp} #{line}"
11
+ end
12
+ else
13
+ output.puts "#{timestamp} #{msg}"
14
+ end
15
+ end
16
+
17
+ def debug(msg=nil)
18
+ log "DEBUG: #{msg || yield}"
19
+ end
20
+
21
+ def output
22
+ $output || $stdout
23
+ end
24
+
25
+ def timestamp
26
+ current_time = DateTime.now
27
+ "#{current_time.strftime("%d/%m/%y %H:%M:%S")} -"
28
+ end
29
+ end
30
+ end