yarn 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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