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.
- data/lib/rack/handler/yarn.rb +3 -7
- data/lib/yarn/abstract_handler.rb +19 -4
- data/lib/yarn/directory_lister.rb +3 -2
- data/lib/yarn/error_page.rb +3 -0
- data/lib/yarn/logging.rb +7 -0
- data/lib/yarn/parser.rb +3 -0
- data/lib/yarn/rack_handler.rb +4 -0
- data/lib/yarn/request_handler.rb +23 -13
- data/lib/yarn/response.rb +8 -1
- data/lib/yarn/server.rb +16 -3
- data/lib/yarn/statuses.rb +53 -52
- data/lib/yarn/version.rb +2 -1
- metadata +27 -108
- data/.autotest +0 -5
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/Gemfile +0 -7
- data/LICENCE +0 -22
- data/Rakefile +0 -4
- data/cucumber.yml +0 -3
- data/features/concurrency.feature +0 -13
- data/features/dynamic_request.feature +0 -18
- data/features/logger.feature +0 -16
- data/features/parser.feature +0 -15
- data/features/rack.feature +0 -15
- data/features/server.feature +0 -14
- data/features/static_request.feature +0 -25
- data/features/step_definitions/concurrency_steps.rb +0 -34
- data/features/step_definitions/parser_steps.rb +0 -23
- data/features/step_definitions/rack_steps.rb +0 -16
- data/features/step_definitions/server_steps.rb +0 -42
- data/features/step_definitions/web_steps.rb +0 -15
- data/features/support/env.rb +0 -20
- data/features/support/hooks.rb +0 -5
- data/spec/helpers.rb +0 -92
- data/spec/rack/handler/yarn_spec.rb +0 -21
- data/spec/spec_helper.rb +0 -17
- data/spec/yarn/abstract_handler_spec.rb +0 -98
- data/spec/yarn/directory_lister_spec.rb +0 -41
- data/spec/yarn/error_page_spec.rb +0 -33
- data/spec/yarn/logging_spec.rb +0 -53
- data/spec/yarn/parser_spec.rb +0 -122
- data/spec/yarn/rack_handler_spec.rb +0 -55
- data/spec/yarn/request_handler_spec.rb +0 -164
- data/spec/yarn/response_spec.rb +0 -36
- data/spec/yarn/server_spec.rb +0 -102
- data/test_objects/.gitignore +0 -10
- data/test_objects/config.ru +0 -6
- data/test_objects/index.html +0 -13
- data/test_objects/jquery.js +0 -8865
- data/test_objects/rails_test/.gitignore +0 -5
- data/test_objects/rails_test/Gemfile +0 -34
- data/test_objects/rails_test/README +0 -261
- data/test_objects/rails_test/Rakefile +0 -7
- data/test_objects/rails_test/app/assets/images/rails.png +0 -0
- data/test_objects/rails_test/app/assets/javascripts/application.js +0 -6
- data/test_objects/rails_test/app/assets/stylesheets/application.css +0 -7
- data/test_objects/rails_test/app/assets/stylesheets/scaffolds.css.scss +0 -56
- data/test_objects/rails_test/app/mailers/.gitkeep +0 -0
- data/test_objects/rails_test/app/models/.gitkeep +0 -0
- data/test_objects/rails_test/app/views/layouts/application.html.erb +0 -15
- data/test_objects/rails_test/app/views/posts/_form.html.erb +0 -25
- data/test_objects/rails_test/app/views/posts/edit.html.erb +0 -6
- data/test_objects/rails_test/app/views/posts/index.html.erb +0 -20
- data/test_objects/rails_test/app/views/posts/new.html.erb +0 -5
- data/test_objects/rails_test/app/views/posts/show.html.erb +0 -15
- data/test_objects/rails_test/config.ru +0 -4
- data/test_objects/rails_test/config/database.yml +0 -25
- data/test_objects/rails_test/config/locales/en.yml +0 -5
- data/test_objects/rails_test/doc/README_FOR_APP +0 -2
- data/test_objects/rails_test/lib/assets/.gitkeep +0 -0
- data/test_objects/rails_test/lib/tasks/.gitkeep +0 -0
- data/test_objects/rails_test/log/.gitkeep +0 -0
- data/test_objects/rails_test/public/404.html +0 -26
- data/test_objects/rails_test/public/422.html +0 -26
- data/test_objects/rails_test/public/500.html +0 -26
- data/test_objects/rails_test/public/favicon.ico +0 -0
- data/test_objects/rails_test/public/robots.txt +0 -5
- data/test_objects/rails_test/script/rails +0 -6
- data/test_objects/rails_test/test/fixtures/.gitkeep +0 -0
- data/test_objects/rails_test/test/fixtures/posts.yml +0 -9
- data/test_objects/rails_test/test/functional/.gitkeep +0 -0
- data/test_objects/rails_test/test/integration/.gitkeep +0 -0
- data/test_objects/rails_test/test/unit/.gitkeep +0 -0
- data/test_objects/rails_test/vendor/assets/stylesheets/.gitkeep +0 -0
- data/test_objects/rails_test/vendor/plugins/.gitkeep +0 -0
- data/yarn.gemspec +0 -29
data/lib/rack/handler/yarn.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/yarn/error_page.rb
CHANGED
@@ -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."]
|
data/lib/yarn/logging.rb
CHANGED
@@ -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")} -"
|
data/lib/yarn/parser.rb
CHANGED
@@ -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 = {}
|
data/lib/yarn/rack_handler.rb
CHANGED
@@ -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
|
data/lib/yarn/request_handler.rb
CHANGED
@@ -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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
data/lib/yarn/response.rb
CHANGED
@@ -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
|
data/lib/yarn/server.rb
CHANGED
@@ -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
|
|