yarn 0.1.0 → 0.1.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 (87) hide show
  1. data/lib/rack/handler/yarn.rb +3 -7
  2. data/lib/yarn/abstract_handler.rb +19 -4
  3. data/lib/yarn/directory_lister.rb +3 -2
  4. data/lib/yarn/error_page.rb +3 -0
  5. data/lib/yarn/logging.rb +7 -0
  6. data/lib/yarn/parser.rb +3 -0
  7. data/lib/yarn/rack_handler.rb +4 -0
  8. data/lib/yarn/request_handler.rb +23 -13
  9. data/lib/yarn/response.rb +8 -1
  10. data/lib/yarn/server.rb +16 -3
  11. data/lib/yarn/statuses.rb +53 -52
  12. data/lib/yarn/version.rb +2 -1
  13. metadata +27 -108
  14. data/.autotest +0 -5
  15. data/.gitignore +0 -11
  16. data/.rspec +0 -2
  17. data/Gemfile +0 -7
  18. data/LICENCE +0 -22
  19. data/Rakefile +0 -4
  20. data/cucumber.yml +0 -3
  21. data/features/concurrency.feature +0 -13
  22. data/features/dynamic_request.feature +0 -18
  23. data/features/logger.feature +0 -16
  24. data/features/parser.feature +0 -15
  25. data/features/rack.feature +0 -15
  26. data/features/server.feature +0 -14
  27. data/features/static_request.feature +0 -25
  28. data/features/step_definitions/concurrency_steps.rb +0 -34
  29. data/features/step_definitions/parser_steps.rb +0 -23
  30. data/features/step_definitions/rack_steps.rb +0 -16
  31. data/features/step_definitions/server_steps.rb +0 -42
  32. data/features/step_definitions/web_steps.rb +0 -15
  33. data/features/support/env.rb +0 -20
  34. data/features/support/hooks.rb +0 -5
  35. data/spec/helpers.rb +0 -92
  36. data/spec/rack/handler/yarn_spec.rb +0 -21
  37. data/spec/spec_helper.rb +0 -17
  38. data/spec/yarn/abstract_handler_spec.rb +0 -98
  39. data/spec/yarn/directory_lister_spec.rb +0 -41
  40. data/spec/yarn/error_page_spec.rb +0 -33
  41. data/spec/yarn/logging_spec.rb +0 -53
  42. data/spec/yarn/parser_spec.rb +0 -122
  43. data/spec/yarn/rack_handler_spec.rb +0 -55
  44. data/spec/yarn/request_handler_spec.rb +0 -164
  45. data/spec/yarn/response_spec.rb +0 -36
  46. data/spec/yarn/server_spec.rb +0 -102
  47. data/test_objects/.gitignore +0 -10
  48. data/test_objects/config.ru +0 -6
  49. data/test_objects/index.html +0 -13
  50. data/test_objects/jquery.js +0 -8865
  51. data/test_objects/rails_test/.gitignore +0 -5
  52. data/test_objects/rails_test/Gemfile +0 -34
  53. data/test_objects/rails_test/README +0 -261
  54. data/test_objects/rails_test/Rakefile +0 -7
  55. data/test_objects/rails_test/app/assets/images/rails.png +0 -0
  56. data/test_objects/rails_test/app/assets/javascripts/application.js +0 -6
  57. data/test_objects/rails_test/app/assets/stylesheets/application.css +0 -7
  58. data/test_objects/rails_test/app/assets/stylesheets/scaffolds.css.scss +0 -56
  59. data/test_objects/rails_test/app/mailers/.gitkeep +0 -0
  60. data/test_objects/rails_test/app/models/.gitkeep +0 -0
  61. data/test_objects/rails_test/app/views/layouts/application.html.erb +0 -15
  62. data/test_objects/rails_test/app/views/posts/_form.html.erb +0 -25
  63. data/test_objects/rails_test/app/views/posts/edit.html.erb +0 -6
  64. data/test_objects/rails_test/app/views/posts/index.html.erb +0 -20
  65. data/test_objects/rails_test/app/views/posts/new.html.erb +0 -5
  66. data/test_objects/rails_test/app/views/posts/show.html.erb +0 -15
  67. data/test_objects/rails_test/config.ru +0 -4
  68. data/test_objects/rails_test/config/database.yml +0 -25
  69. data/test_objects/rails_test/config/locales/en.yml +0 -5
  70. data/test_objects/rails_test/doc/README_FOR_APP +0 -2
  71. data/test_objects/rails_test/lib/assets/.gitkeep +0 -0
  72. data/test_objects/rails_test/lib/tasks/.gitkeep +0 -0
  73. data/test_objects/rails_test/log/.gitkeep +0 -0
  74. data/test_objects/rails_test/public/404.html +0 -26
  75. data/test_objects/rails_test/public/422.html +0 -26
  76. data/test_objects/rails_test/public/500.html +0 -26
  77. data/test_objects/rails_test/public/favicon.ico +0 -0
  78. data/test_objects/rails_test/public/robots.txt +0 -5
  79. data/test_objects/rails_test/script/rails +0 -6
  80. data/test_objects/rails_test/test/fixtures/.gitkeep +0 -0
  81. data/test_objects/rails_test/test/fixtures/posts.yml +0 -9
  82. data/test_objects/rails_test/test/functional/.gitkeep +0 -0
  83. data/test_objects/rails_test/test/integration/.gitkeep +0 -0
  84. data/test_objects/rails_test/test/unit/.gitkeep +0 -0
  85. data/test_objects/rails_test/vendor/assets/stylesheets/.gitkeep +0 -0
  86. data/test_objects/rails_test/vendor/plugins/.gitkeep +0 -0
  87. data/yarn.gemspec +0 -29
@@ -4,19 +4,15 @@ require "rack/handler"
4
4
 
5
5
  module Rack
6
6
  module Handler
7
+ # Yarn webserver
7
8
  class Yarn
9
+
10
+ # Takes a Rackup file and an options Hash.
8
11
  def self.run(app, options={})
9
12
  options = options.merge({ rack: app })
10
13
  @server = ::Yarn::Server.new(options)
11
14
  @server.start
12
15
  end
13
-
14
- def self.valid_options
15
- {
16
- "Host=HOST" => "Hostname to listen on (default: 127.0.0.1)",
17
- "Port=PORT" => "Port to listen on (default: 3000)"
18
- }
19
- end
20
16
  end
21
17
  end
22
18
  end
@@ -4,9 +4,13 @@ require 'parslet'
4
4
 
5
5
  module Yarn
6
6
 
7
+ # Error for empty requests.
7
8
  class EmptyRequestError < StandardError; end
9
+ # Error if parsing the request fails.
8
10
  class ProcessingError < StandardError; end
9
11
 
12
+ # Base class for the handler classes.
13
+ # Built using the Template Method design pattern.
10
14
  class AbstractHandler
11
15
 
12
16
  include Logging
@@ -19,6 +23,10 @@ module Yarn
19
23
  @response = Response.new
20
24
  end
21
25
 
26
+ # The template method which drives the handlers.
27
+ # Starts by setting common headers and parses the request,
28
+ # prepares the response, returns it to the client, and closes
29
+ # the connection.
22
30
  def run(session)
23
31
  set_common_headers
24
32
  @session = session
@@ -37,6 +45,7 @@ module Yarn
37
45
  end
38
46
  end
39
47
 
48
+ # Invokes the parser upon the request
40
49
  def parse_request
41
50
  raw_request = read_request
42
51
  raise EmptyRequestError if raw_request.empty?
@@ -49,9 +58,11 @@ module Yarn
49
58
  end
50
59
  end
51
60
 
61
+ # Only implemented in the actual handler classes.
52
62
  def prepare_response
53
63
  end
54
64
 
65
+ # returns the reqponse by writing it to the socket.
55
66
  def return_response
56
67
  begin
57
68
  @session.puts "HTTP/1.1 #{@response.status} #{STATUS_CODES[@response.status]}"
@@ -66,6 +77,9 @@ module Yarn
66
77
  end
67
78
  end
68
79
 
80
+ # Reads the request from the socket.
81
+ # If a Content-Length header is given, that means there is an accompanying
82
+ # request body. The body is read according to the set Content-Length.
69
83
  def read_request
70
84
  input = []
71
85
  while (line = @session.gets) do
@@ -83,10 +97,7 @@ module Yarn
83
97
  input.join
84
98
  end
85
99
 
86
- def persistent?
87
- return @request[:headers]["Connection"] == "keep-alive"
88
- end
89
-
100
+ # Sets common headers like server name and date.
90
101
  def set_common_headers
91
102
  @response.headers[:Server] = "Yarn webserver v#{VERSION}"
92
103
 
@@ -97,6 +108,7 @@ module Yarn
97
108
  @response.headers[:Connection] = "Close"
98
109
  end
99
110
 
111
+ # Extracts the path from the parsed request
100
112
  def extract_path
101
113
  path = @request[:uri][:path].to_s
102
114
  if path[0] == "/" && path != "/"
@@ -105,14 +117,17 @@ module Yarn
105
117
  path.gsub(/%20/, " ").strip
106
118
  end
107
119
 
120
+ # Proxy to getting the request body.
108
121
  def post_body
109
122
  @request ? @request[:body].to_s : ""
110
123
  end
111
124
 
125
+ # Proxy for getting the request path.
112
126
  def request_path
113
127
  @request[:uri][:path] if @request
114
128
  end
115
129
 
130
+ # Proxy for the clients address.
116
131
  def client_address
117
132
  begin
118
133
  @session.peeraddr(:numeric)[2] if @session
@@ -1,10 +1,10 @@
1
- require 'yarn/logging'
2
-
3
1
  module Yarn
2
+ # Creates directory listings in HTML
4
3
  class DirectoryLister
5
4
 
6
5
  include Logging
7
6
 
7
+ # Creates a directory listing for the given path.
8
8
  def self.list(path)
9
9
  response = []
10
10
  response << <<-EOS
@@ -49,6 +49,7 @@ module Yarn
49
49
  return response
50
50
  end
51
51
 
52
+ # Formats file sizes into more readable formats.
52
53
  def self.format_size(size)
53
54
  count = 0
54
55
  while size >= 1024 and count < 4
@@ -1,13 +1,16 @@
1
1
 
2
2
  module Yarn
3
+ # HTML error pages
3
4
  module ErrorPage
4
5
 
6
+ # Makes the response a 404 error page
5
7
  def serve_404_page
6
8
  @response.status = 404
7
9
  fn = @request[:uri][:path] if @request
8
10
  @response.body = ["<html><head><title>404</title></head><body><h1>File #{fn} does not exist.</h1></body><html>"]
9
11
  end
10
12
 
13
+ # Makes the response a 505 error page
11
14
  def serve_500_page
12
15
  @response.status = 500
13
16
  @response.body = ["<h1>Yarn!?</h1>\nA server error occured."]
@@ -1,8 +1,11 @@
1
1
  require 'date'
2
2
 
3
3
  module Yarn
4
+ # Enables logging messages to $output.
4
5
  module Logging
5
6
 
7
+ # Logs a message to $output.
8
+ # Doesnt do anything unless logging or debugging is enabled.
6
9
  def log(msg)
7
10
  return nil unless $log
8
11
  if msg.respond_to?(:each)
@@ -14,15 +17,19 @@ module Yarn
14
17
  end
15
18
  end
16
19
 
20
+ # Appends DEBUG to a log message.
21
+ # Doesnt do anything if debugging is disabled.
17
22
  def debug(msg=nil)
18
23
  log "DEBUG: #{msg}" if $debug
19
24
  end
20
25
 
26
+ # Proxy for the output variable.
21
27
  def output
22
28
  out ||= $output || $stdout
23
29
  out
24
30
  end
25
31
 
32
+ # Returns a formattet timestamp.
26
33
  def timestamp
27
34
  current_time = DateTime.now
28
35
  "#{current_time.strftime("%d/%m/%y %H:%M:%S")} -"
@@ -2,6 +2,7 @@ require 'rubygems'
2
2
  require 'parslet'
3
3
 
4
4
  module Yarn
5
+ # LPEG style HTTP parser.
5
6
  class Parser < Parslet::Parser
6
7
 
7
8
  # general rules
@@ -81,12 +82,14 @@ module Yarn
81
82
  # starts parsing from the beginning using the :request rule
82
83
  root(:request)
83
84
 
85
+ # Executes the parser on a given input string.
84
86
  def run(input)
85
87
  tree = parse input
86
88
  HeadersTransformer.new.apply tree
87
89
  end
88
90
  end
89
91
 
92
+ # Formats the headers into a Hash with the header-name as the key.
90
93
  class HeadersTransformer < Parslet::Transform
91
94
  rule(:_process_headers => subtree(:headers)) do
92
95
  hash = {}
@@ -1,6 +1,7 @@
1
1
  require 'rack'
2
2
 
3
3
  module Yarn
4
+ # handler for Rack applications.
4
5
  class RackHandler < AbstractHandler
5
6
 
6
7
  attr_accessor :env, :opts
@@ -13,6 +14,7 @@ module Yarn
13
14
  @port = opts[:port].to_s
14
15
  end
15
16
 
17
+ # Prepares the response by setting the environment and calling the Rack app.
16
18
  def prepare_response
17
19
  begin
18
20
  make_env
@@ -24,6 +26,7 @@ module Yarn
24
26
  end
25
27
  end
26
28
 
29
+ # Creates and formats the Rack environment.
27
30
  def make_env
28
31
  input = StringIO.new("").set_encoding(Encoding::ASCII_8BIT)
29
32
  if has_body?
@@ -49,6 +52,7 @@ module Yarn
49
52
  return @env
50
53
  end
51
54
 
55
+ # Proxy for whether the request has body data.
52
56
  def has_body?
53
57
  value ||= !! @request[:body]
54
58
  end
@@ -1,5 +1,9 @@
1
1
  module Yarn
2
+ # handler for static and dynamic requests
2
3
  class RequestHandler < AbstractHandler
4
+
5
+ # Determines whether to serve a directory listing, the contetents of a file,
6
+ # or an error page.
3
7
  def prepare_response
4
8
  path = extract_path
5
9
 
@@ -19,17 +23,20 @@ module Yarn
19
23
  end
20
24
  end
21
25
 
26
+ # Sets the response for a static file.
22
27
  def serve_file(path)
23
28
  @response.body << read_file(path)
24
29
  @response.headers["Content-Type"] = get_mime_type path
25
30
  @response.status = 200
26
31
  end
27
32
 
33
+ # Sets the response for a dynamic file.
28
34
  def serve_ruby_file(path)
29
35
  @response.body << execute_script(path)
30
36
  @response.status = 200
31
37
  end
32
38
 
39
+ # Sets the reponse for a directory listing.
33
40
  def serve_directory(path)
34
41
  @response.status = 200
35
42
  if File.exists?("index.html") || File.exists?("/index.html")
@@ -41,6 +48,7 @@ module Yarn
41
48
  end
42
49
  end
43
50
 
51
+ # Reads the contents of a file into an Array.
44
52
  def read_file(path)
45
53
  file_contents = []
46
54
  File.open(path).each { |line| file_contents << line }
@@ -48,6 +56,7 @@ module Yarn
48
56
  file_contents
49
57
  end
50
58
 
59
+ # Evaluates the contents of a ruby file and returns the output.
51
60
  def execute_script(path)
52
61
  response = `ruby #{path} #{post_body}`
53
62
  if !! ($?.to_s =~ /1$/)
@@ -57,24 +66,25 @@ module Yarn
57
66
  end
58
67
  end
59
68
 
69
+ # Determines the MIME type based on the file extension.
60
70
  def get_mime_type(path)
61
71
  return false unless path.include? '.'
62
72
  filetype = path.split('.').last
63
73
 
64
74
  return case
65
- when ["html", "htm"].include?(filetype)
66
- "text/html"
67
- when "txt" == filetype
68
- "text/plain"
69
- when "css" == filetype
70
- "text/css"
71
- when "js" == filetype
72
- "text/javascript"
73
- when ["png", "jpg", "jpeg", "gif", "tiff"].include?(filetype)
74
- "image/#{filetype}"
75
- when ["zip","pdf","postscript","x-tar","x-dvi"].include?(filetype)
76
- "application/#{filetype}"
77
- else false
75
+ when ["html", "htm"].include?(filetype)
76
+ "text/html"
77
+ when "txt" == filetype
78
+ "text/plain"
79
+ when "css" == filetype
80
+ "text/css"
81
+ when "js" == filetype
82
+ "text/javascript"
83
+ when ["png", "jpg", "jpeg", "gif", "tiff"].include?(filetype)
84
+ "image/#{filetype}"
85
+ when ["zip","pdf","postscript","x-tar","x-dvi"].include?(filetype)
86
+ "application/#{filetype}"
87
+ else false
78
88
  end
79
89
  end
80
90
  end
@@ -1,5 +1,5 @@
1
-
2
1
  module Yarn
2
+ # Holds the response status, headers and body in an Array.
3
3
  class Response
4
4
 
5
5
  attr_accessor :content
@@ -8,30 +8,37 @@ module Yarn
8
8
  @content = [nil, {}, []]
9
9
  end
10
10
 
11
+ # HTTP status code set
11
12
  def status=(status)
12
13
  @content[0] = status
13
14
  end
14
15
 
16
+ # HTTP status code get
15
17
  def status
16
18
  @content[0]
17
19
  end
18
20
 
21
+ # Headers Hash set
19
22
  def headers=(headers)
20
23
  @content[1] = headers
21
24
  end
22
25
 
26
+ # Headers Hash get
23
27
  def headers
24
28
  @content[1]
25
29
  end
26
30
 
31
+ # Body Array set
27
32
  def body=(body)
28
33
  @content[2] = body
29
34
  end
30
35
 
36
+ # Body Array get
31
37
  def body
32
38
  @content[2]
33
39
  end
34
40
 
41
+ # Format to string
35
42
  def to_s
36
43
  @content
37
44
  end
@@ -1,6 +1,8 @@
1
1
  require 'socket'
2
2
 
3
+ # Yarn namespace
3
4
  module Yarn
5
+ # The heart of Yarn which starts a TCP server and forks workers which handles requests.
4
6
  class Server
5
7
 
6
8
  include Logging
@@ -17,6 +19,7 @@ module Yarn
17
19
 
18
20
  attr_accessor :host, :port, :socket, :workers
19
21
 
22
+ # Initializes a new Server with the given options Hash.
20
23
  def initialize(options={})
21
24
  # merge given options with default values
22
25
  opts = {
@@ -28,8 +31,7 @@ module Yarn
28
31
  rack: "off"
29
32
  }.merge(options)
30
33
 
31
- @app = nil
32
- @app = load_rack_app(opts[:rack]) unless opts[:rack] == "off"
34
+ @app = opts[:rack] == "off" ? nil : load_rack_app(opts[:rack])
33
35
  @opts = opts
34
36
 
35
37
  @host, @port, @num_workers = opts[:host], opts[:port], opts[:workers]
@@ -38,16 +40,19 @@ module Yarn
38
40
  $log = opts[:log] || opts[:debug]
39
41
  end
40
42
 
43
+ # Loads a Rack application from a given file path.
44
+ # If the file does not exist, the program exits.
41
45
  def load_rack_app(app_path)
42
46
  if File.exists?(app_path)
43
47
  config_file = File.read(app_path)
44
- rack_application = eval("Rack::Builder.new { #{config_file} }")
48
+ rack_application = eval("Rack::Builder.new { #{config_file} }.to_app", TOPLEVEL_BINDING, app_path)
45
49
  else
46
50
  log "#{app_path} does not exist. Exiting."
47
51
  Kernel::exit
48
52
  end
49
53
  end
50
54
 
55
+ # Creates a new TCPServer and invokes init_workers
51
56
  def start
52
57
  trap("INT") { stop }
53
58
  @socket = TCPServer.new(@host, @port)
@@ -61,18 +66,24 @@ module Yarn
61
66
  Process.waitall
62
67
  end
63
68
 
69
+ # Applies TCP optimizations to the TCP socket
64
70
  def configure_socket
65
71
  TCP_OPTS.each { |opt| @session.setsockopt(*opt) }
66
72
  end
67
73
 
74
+ # Runs fork_worker @num_worker times
68
75
  def init_workers
69
76
  @num_workers.times { @workers << fork_worker }
70
77
  end
71
78
 
79
+ # Forks a new process with a worker
72
80
  def fork_worker
73
81
  fork { worker }
74
82
  end
75
83
 
84
+ # Contains the logic performed by each worker. It first determines the
85
+ # handler type and then start an infinite loop listening for incomming
86
+ # requests. Upon receiving a request, it fires the run method on the handler.
76
87
  def worker
77
88
  trap("INT") { exit }
78
89
  handler = get_handler
@@ -83,10 +94,12 @@ module Yarn
83
94
  end
84
95
  end
85
96
 
97
+ # Returns the handler corresponding to whether a Rack application is present.
86
98
  def get_handler
87
99
  @app ? RackHandler.new(@app,@opts) : RequestHandler.new
88
100
  end
89
101
 
102
+ # Closes the TCPServer and exits with a message.
90
103
  def stop
91
104
  @socket.close if (@socket && !@socket.closed?)
92
105