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