yarn 0.0.2 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -8,3 +8,4 @@ pkg/*
8
8
  *.rbc
9
9
  doc/*
10
10
  .rvmrc
11
+ .rbx/
data/LICENCE ADDED
@@ -0,0 +1,22 @@
1
+ Portions copyright (c) 2011 Jesper Kjeldgaard
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,10 +1,6 @@
1
1
  # Yarn #
2
2
 
3
- Yarn is a multi-threaded webserver written in Ruby 1.9 by Jesper Kjeldgaard.
4
- It handles concurrent requests by means of a set of workers and a job queue for incomming requests.
5
-
6
- Supports:
7
- *
3
+ Yarn is a multi-process webserver written in Ruby 1.9 by Jesper Kjeldgaard.
8
4
 
9
5
 
10
6
  ## Installation ##
@@ -12,16 +8,21 @@ Supports:
12
8
 
13
9
 
14
10
  ## Usage ##
15
- To use Yarn with Rack applications:
16
-
17
- `rackup -s Yarn <rackup file (config.ru)>`
18
-
19
-
20
- To use Yarn for serving static and ruby (*.rb) files:
21
11
 
22
12
 
23
- yarn [options]
13
+ Usage: yarn [options]
24
14
  where [options] are:
25
15
  --host, -h <s>: Hostname or IP address of the server (default: 127.0.0.1)
26
16
  --port, -p <i>: Port number to listen on for incomming requests (default: 3000)
27
- --workers, -w <s>: Number of worker threads (default: 32)
17
+ --workers, -w <i>: Number of worker threads (default: 4)
18
+ --rack, -r <s>: Rackup file <config.ru> (default: off)
19
+ --log, -l: Enable logging
20
+ --debug, -d: Output debug messages
21
+ --version, -v: Print version and exit
22
+ --help, -e: Show this message
23
+
24
+
25
+ ## Todo list ##
26
+
27
+
28
+ * Support persistent connections.
data/bin/yarn CHANGED
@@ -9,7 +9,7 @@ require 'yarn'
9
9
  opts = Trollop::options do
10
10
  version "Yarn v#{Yarn::VERSION} 2011 Jesper Kjeldgaard"
11
11
  banner <<-EOS
12
- Yarn v#{Yarn::VERSION} is a multiprocess webserver written in Ruby 1.9.2
12
+ Yarn v#{Yarn::VERSION} is a multiprocess webserver written in Ruby 1.9
13
13
 
14
14
  Usage: yarn [options]
15
15
  where [options] are:
@@ -19,7 +19,8 @@ EOS
19
19
  opt :port, "Port number to listen on for incomming requests", :default => 3000
20
20
  opt :workers, "Number of worker threads", :default => 4
21
21
  opt :rack, "Rackup file <config.ru>", :default => "off"
22
- opt :debug, "Output debug messages"
22
+ opt :log, "Enable logging", :default => false
23
+ opt :debug, "Output debug messages", :default => false
23
24
  end
24
25
 
25
26
  server = Yarn::Server.new(opts)
@@ -1,8 +1,8 @@
1
1
  Feature: Server control
2
2
 
3
3
  As a developer
4
- I want to be able to command the server
5
- So that I can control the server
4
+ I want to be able to start and stop
5
+ So that I can serve files to the web
6
6
 
7
7
  Scenario: Start server
8
8
  When I start the server on port 3000
@@ -12,7 +12,3 @@ Feature: Server control
12
12
  Given the server is running
13
13
  When I stop the server
14
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"
@@ -1,4 +1,3 @@
1
- require 'pry'
2
1
  require 'rack/handler/yarn'
3
2
  require 'rack/handler'
4
3
 
@@ -6,16 +5,15 @@ module Yarn
6
5
 
7
6
  autoload :Server, "yarn/server"
8
7
  autoload :VERSION, "yarn/version"
8
+ autoload :AbstractHandler, "yarn/abstract_handler"
9
9
  autoload :RequestHandler, "yarn/request_handler"
10
10
  autoload :RackHandler, "yarn/rack_handler"
11
11
  autoload :DirectoryLister, "yarn/directory_lister"
12
12
  autoload :ErrorPage, "yarn/error_page"
13
13
  autoload :Logging, "yarn/logging"
14
- autoload :ParsletParser, "yarn/parslet_parser"
14
+ autoload :Parser, "yarn/parser"
15
15
  autoload :Response, "yarn/response"
16
16
  autoload :STATUS_CODES, "yarn/statuses"
17
- autoload :Worker, "yarn/worker"
18
- autoload :WorkerPool, "yarn/worker_pool"
19
17
 
20
18
  Rack::Handler.register 'yarn', 'Yarn'
21
19
 
@@ -0,0 +1,116 @@
1
+ require 'date'
2
+ require 'rubygems'
3
+ require 'parslet'
4
+
5
+ module Yarn
6
+
7
+ class EmptyRequestError < StandardError; end
8
+ class ProcessingError < StandardError; end
9
+
10
+ class AbstractHandler
11
+
12
+ include Logging
13
+ include ErrorPage
14
+
15
+ attr_accessor :session, :parser, :request, :response
16
+
17
+ def initialize
18
+ @parser = Parser.new
19
+ @response = Response.new
20
+ end
21
+
22
+ def run(session)
23
+ set_common_headers
24
+ @session = session
25
+ begin
26
+ parse_request
27
+ debug "Request parsed, path: #{request_path}"
28
+ prepare_response
29
+ debug "Response prepared: #{@response.status}"
30
+ return_response
31
+ log "#{STATUS_CODES[@response.status]} #{client_address} #{request_path}"
32
+ rescue EmptyRequestError
33
+ log "Empty request from #{client_address}"
34
+ ensure
35
+ @session.close
36
+ debug "Connection closed"
37
+ end
38
+ end
39
+
40
+ def parse_request
41
+ raw_request = read_request
42
+ raise EmptyRequestError if raw_request.empty?
43
+
44
+ begin
45
+ @request = @parser.run raw_request
46
+ rescue Parslet::ParseFailed => e
47
+ @response.status = 400
48
+ debug "Parse failed: #{@request}"
49
+ end
50
+ end
51
+
52
+ def prepare_response
53
+ end
54
+
55
+ def return_response
56
+ @session.puts "HTTP/1.1 #{@response.status} #{STATUS_CODES[@response.status]}"
57
+ @session.puts @response.headers.map { |k,v| "#{k}: #{v}" }
58
+ @session.puts ""
59
+
60
+ @response.body.each do |line|
61
+ @session.puts line
62
+ end
63
+ end
64
+
65
+ def read_request
66
+ input = []
67
+ while (line = @session.gets) do
68
+ length = line.gsub(/\D/,"") if line =~ /Content-Length/
69
+ if line == "\r\n"
70
+ input << line
71
+ input << @session.read(length.to_i) if length
72
+ break
73
+ else
74
+ input << line
75
+ end
76
+ end
77
+
78
+ debug "Done reading request"
79
+ input.join
80
+ end
81
+
82
+ def persistent?
83
+ return @request[:headers]["Connection"] == "keep-alive"
84
+ end
85
+
86
+ def set_common_headers
87
+ @response.headers[:Server] = "Yarn webserver v#{VERSION}"
88
+
89
+ # HTTP date format: Fri, 31 Dec 1999 23:59:59 GMT
90
+ time ||= DateTime.now.new_offset(0)
91
+ @response.headers[:Date] = time.strftime("%a, %d %b %Y %H:%M:%S GMT")
92
+ # Close connection header ( until support for persistent connections )
93
+ @response.headers[:Connection] = "Close"
94
+ end
95
+
96
+ def extract_path
97
+ path = @request[:uri][:path].to_s
98
+ if path[0] == "/" && path != "/"
99
+ path = path[1..-1]
100
+ end
101
+ path.gsub(/%20/, " ").strip
102
+ end
103
+
104
+ def post_body
105
+ @request ? @request[:body].to_s : ""
106
+ end
107
+
108
+ def request_path
109
+ @request[:uri][:path] if @request
110
+ end
111
+
112
+ def client_address
113
+ @session.peeraddr(:numeric)[2] if @session
114
+ end
115
+ end
116
+ end
@@ -1,10 +1,10 @@
1
- require 'logger'
2
1
  require 'date'
3
2
 
4
3
  module Yarn
5
4
  module Logging
6
5
 
7
6
  def log(msg)
7
+ return nil unless $log
8
8
  if msg.respond_to?(:each)
9
9
  msg.each do |line|
10
10
  output.puts "#{timestamp} #{line}"
@@ -19,7 +19,8 @@ module Yarn
19
19
  end
20
20
 
21
21
  def output
22
- $output || $stdout
22
+ out ||= $output || $stdout
23
+ out
23
24
  end
24
25
 
25
26
  def timestamp
@@ -1,10 +1,8 @@
1
1
  require 'rubygems'
2
2
  require 'parslet'
3
3
 
4
- module Parslet::Atoms::DSL; def once; repeat(1); end; end
5
-
6
4
  module Yarn
7
- class ParsletParser < Parslet::Parser
5
+ class Parser < Parslet::Parser
8
6
 
9
7
  # general rules
10
8
 
@@ -15,9 +13,9 @@ module Yarn
15
13
  rule(:spaces) { match('\s+') }
16
14
 
17
15
  # header rules
18
- rule(:header_value) { match['^\r\n'].once }
16
+ rule(:header_value) { match['^\r\n'].repeat(1) }
19
17
 
20
- rule(:header_name) { match['a-zA-Z\-'].once }
18
+ rule(:header_name) { match['a-zA-Z\-'].repeat(1) }
21
19
 
22
20
  rule(:header) do
23
21
  header_name.as(:name) >>
@@ -28,7 +26,7 @@ module Yarn
28
26
  end
29
27
 
30
28
  # request-line rules
31
- rule(:http_version) { match['HTTP\/\d\.\d'].once }
29
+ rule(:http_version) { match['HTTP\/\d\.\d'].repeat(1) }
32
30
 
33
31
  rule(:query) do
34
32
  match['\S+'].repeat(1)
@@ -37,12 +35,12 @@ module Yarn
37
35
  rule(:path) do
38
36
  match['^\?'].repeat(1).as(:path) >>
39
37
  str("?") >>
40
- query.as(:query) | match['^\s'].once.as(:path)
38
+ query.as(:query) | match['^\s'].repeat(1).as(:path)
41
39
  end
42
40
 
43
41
  rule(:port) { match['\d+'].repeat(1) }
44
42
 
45
- rule(:host) { match['^\/:'].once }
43
+ rule(:host) { match['^\/:'].repeat(1) }
46
44
 
47
45
  rule(:absolute_uri) do
48
46
  str("http://") >>
@@ -56,7 +54,7 @@ module Yarn
56
54
 
57
55
  rule(:spaces) { match('\s+') }
58
56
 
59
- rule(:method) { match['OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT'].once }
57
+ rule(:method) { match['OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT'].repeat(1) }
60
58
 
61
59
  # RFC2616: Method SP Request-URI SP HTTP-Version CRLF
62
60
  rule(:request_line) do
@@ -69,7 +67,7 @@ module Yarn
69
67
  end
70
68
 
71
69
  # body rule
72
- rule(:body) { match['\S'].once }
70
+ rule(:body) { match['\S'].repeat(1) }
73
71
 
74
72
  # RFC2616: Request-Line *(( header ) CRLF) CRLF [ message-body ]
75
73
  rule(:request) do
@@ -1,15 +1,16 @@
1
1
  require 'rack'
2
- require 'pry'
3
2
 
4
3
  module Yarn
5
- class RackHandler < RequestHandler
4
+ class RackHandler < AbstractHandler
6
5
 
7
- attr_accessor :env
6
+ attr_accessor :env, :opts
8
7
 
9
- def initialize(app)
10
- @parser = ParsletParser.new
8
+ def initialize(app,opts={})
9
+ @parser = Parser.new
11
10
  @response = Response.new
12
11
  @app = app
12
+ @host = opts[:host]
13
+ @port = opts[:port].to_s
13
14
  end
14
15
 
15
16
  def prepare_response
@@ -23,14 +24,18 @@ module Yarn
23
24
  end
24
25
 
25
26
  def make_env
27
+ input = StringIO.new("").set_encoding(Encoding::ASCII_8BIT)
28
+ if has_body?
29
+ input.string = @request[:body]
30
+ end
26
31
  @env = {
27
32
  "REQUEST_METHOD" => @request[:method].to_s,
28
33
  "PATH_INFO" => @request[:uri][:path].to_s,
29
34
  "QUERY_STRING" => @request[:uri][:query].to_s,
30
- "SERVER_NAME" => @request[:uri][:host].to_s,
31
- "SERVER_PORT" => @request[:uri][:port].to_s,
35
+ "SERVER_NAME" => @host,
36
+ "SERVER_PORT" => @port,
32
37
  "SCRIPT_NAME" => "",
33
- "rack.input" => StringIO.new("").set_encoding(Encoding::ASCII_8BIT),
38
+ "rack.input" => input,
34
39
  "rack.version" => Rack::VERSION,
35
40
  "rack.errors" => $output,
36
41
  "rack.multithread" => true,
@@ -38,6 +43,14 @@ module Yarn
38
43
  "rack.run_once" => false,
39
44
  "rack.url_scheme" => "http"
40
45
  }
46
+ @env["CONTENT_LENGTH"] = @request[:body].size.to_i if has_body?
47
+
48
+ return @env
49
+ end
50
+
51
+ def has_body?
52
+ value ||= !! @request[:body]
53
+ value
41
54
  end
42
55
 
43
56
  end
@@ -1,69 +1,15 @@
1
- require 'date'
2
- require 'rubygems'
3
- require 'parslet'
4
-
5
1
  module Yarn
6
-
7
- class EmptyRequestError < StandardError; end
8
- class ProcessingError < StandardError; end
9
-
10
- class RequestHandler
11
-
12
- include Logging
13
- include ErrorPage
14
-
15
- attr_accessor :session, :parser, :request, :response
16
-
17
- def initialize
18
- @parser = ParsletParser.new
19
- @response = Response.new
20
- end
21
-
22
- def run(session)
23
- set_common_headers
24
- @session = session
25
- begin
26
- parse_request
27
- debug "Request parsed, path: #{request_path}"
28
- prepare_response
29
- debug "Response prepared: #{@response.status}"
30
- return_response
31
- log "#{STATUS_CODES[@response.status]} #{client_address} #{request_path}"
32
- rescue EmptyRequestError
33
- log "Empty request from #{client_address}"
34
- ensure
35
- close_connection
36
- debug "Connection closed"
37
- end
38
- end
39
-
40
- def parse_request
41
- raw_request = read_request
42
- raise EmptyRequestError if raw_request.empty?
43
-
44
- begin
45
- @request = @parser.run raw_request
46
- rescue Parslet::ParseFailed => e
47
- @response.status = 400
48
- debug "Parse failed: #{@request}"
49
- end
50
- end
51
-
2
+ class RequestHandler < AbstractHandler
52
3
  def prepare_response
53
4
  path = extract_path
54
5
 
55
6
  @response.headers["Content-Type"] = "text/html"
56
7
 
57
8
  begin
58
- if File.directory? path
59
- serve_directory path
9
+ if File.directory?(path)
10
+ serve_directory(path)
60
11
  elsif File.exists?(path)
61
- if path =~ /.*\.rb$/
62
- @response.body << execute_script(path)
63
- @response.status = 200
64
- else
65
- serve_file(path)
66
- end
12
+ path =~ /.*\.rb$/ ? serve_ruby_file(path) : serve_file(path)
67
13
  else
68
14
  serve_404_page
69
15
  end
@@ -73,72 +19,15 @@ module Yarn
73
19
  end
74
20
  end
75
21
 
76
- def execute_script(path)
77
- response = `ruby #{path} #{post_body}`
78
- if !! ($?.to_s =~ /1$/)
79
- raise ProcessingError
80
- else
81
- response
82
- end
83
- end
84
-
85
- def post_body
86
- @request ? @request[:body].to_s : ""
87
- end
88
-
89
- def return_response
90
- @session.puts "HTTP/1.1 #{@response.status} #{STATUS_CODES[@response.status]}"
91
- @session.puts @response.headers.map { |k,v| "#{k}: #{v}" }
92
- @session.puts ""
93
-
94
- @response.body.each do |line|
95
- @session.puts line
96
- end
97
- end
98
-
99
- def close_connection
100
- if @session #&& !persistent?
101
- @session.close
102
- else
103
- # TODO: start some kind of timeout
104
- end
105
- end
106
-
107
- def read_request
108
- input = []
109
- while (line = @session.gets) do
110
- length = line.gsub(/\D/,"") if line =~ /Content-Length/
111
- if line == "\r\n"
112
- input << line
113
- input << @session.read(length.to_i) if length
114
- break
115
- else
116
- input << line
117
- end
118
- end
119
- @session.close_read
120
- debug "Done reading request"
121
- input.join
122
- end
123
-
124
- def persistent?
125
- return @request[:headers]["Connection"] == "keep-alive"
126
- end
127
-
128
- def set_common_headers
129
- @response.headers[:Server] = "Yarn webserver v#{VERSION}"
130
-
131
- # HTTP date format: Fri, 31 Dec 1999 23:59:59 GMT
132
- time = DateTime.now.new_offset(0)
133
- @response.headers[:Date] = time.strftime("%a, %d %b %Y %H:%M:%S GMT")
134
- # Close connection header ( until support for persistent connections )
135
- @response.headers[:Connection] = "Close"
136
- end
137
-
138
22
  def serve_file(path)
139
- @response.status = 200
140
23
  @response.body << read_file(path)
141
24
  @response.headers["Content-Type"] = get_mime_type path
25
+ @response.status = 200
26
+ end
27
+
28
+ def serve_ruby_file(path)
29
+ @response.body << execute_script(path)
30
+ @response.status = 200
142
31
  end
143
32
 
144
33
  def serve_directory(path)
@@ -148,8 +37,7 @@ module Yarn
148
37
  @response.headers["Content-Type"] = "text/html"
149
38
  else
150
39
  @response.headers["Content-Type"] = "text/html"
151
- directory_lister = DirectoryLister.new
152
- @response.body << directory_lister.list(path)
40
+ @response.body << DirectoryLister.list(path)
153
41
  end
154
42
  end
155
43
 
@@ -160,22 +48,12 @@ module Yarn
160
48
  file_contents
161
49
  end
162
50
 
163
- def extract_path
164
- path = @request[:uri][:path].to_s
165
- if path[0] == "/" && path != "/"
166
- path = path[1..-1]
167
- end
168
- path.gsub(/%20/, " ").strip
169
- end
170
-
171
- def serve_directory(path)
172
- @response.status = 200
173
- if File.exists?("index.html")# || File.exists?("/index.html")
174
- @response.body = read_file "index.html"
175
- @response.headers["Content-Type"] = "text/html"
51
+ def execute_script(path)
52
+ response = `ruby #{path} #{post_body}`
53
+ if !! ($?.to_s =~ /1$/)
54
+ raise ProcessingError
176
55
  else
177
- @response.headers["Content-Type"] = "text/html"
178
- @response.body << DirectoryLister.list(path)
56
+ response
179
57
  end
180
58
  end
181
59
 
@@ -184,28 +62,20 @@ module Yarn
184
62
  filetype = path.split('.').last
185
63
 
186
64
  return case
187
- when ["html", "htm"].include?(filetype)
188
- "text/html"
189
- when "txt" == filetype
190
- "text/plain"
191
- when "css" == filetype
192
- "text/css"
193
- when "js" == filetype
194
- "text/javascript"
195
- when ["png", "jpg", "jpeg", "gif", "tiff"].include?(filetype)
196
- "image/#{filetype}"
197
- when ["zip","pdf","postscript","x-tar","x-dvi"].include?(filetype)
198
- "application/#{filetype}"
199
- else false
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
78
+ end
200
79
  end
201
80
  end
202
-
203
- def request_path
204
- @request[:uri][:path] if @request
205
- end
206
-
207
- def client_address
208
- @session.peeraddr(:numeric)[2] if @session
209
- end
210
- end
211
81
  end
@@ -14,15 +14,18 @@ module Yarn
14
14
  host: '127.0.0.1',
15
15
  port: 3000,
16
16
  workers: 4,
17
+ log: true,
17
18
  rack: "off"
18
19
  }.merge(options)
19
20
 
20
21
  @app = nil
21
22
  @app = load_rack_app(opts[:rack]) unless opts[:rack] == "off"
23
+ @opts = opts
22
24
 
23
25
  @host, @port, @num_workers = opts[:host], opts[:port], opts[:workers]
24
26
  @workers = []
25
27
  $output, $debug = opts[:output], opts[:debug]
28
+ $log = opts[:log] || opts[:debug]
26
29
  end
27
30
 
28
31
  def load_rack_app(app_path)
@@ -48,14 +51,20 @@ module Yarn
48
51
 
49
52
  def init_workers
50
53
  @num_workers.times do
51
- @workers << fork do
52
- trap("INT") { exit }
53
- loop do
54
- handler ||= @app ? RackHandler.new(@app) : RequestHandler.new
55
- session = @socket.accept
56
- handler.run session
57
- end
58
- end
54
+ @workers << fork_worker
55
+ end
56
+ end
57
+
58
+ def fork_worker
59
+ fork { worker }
60
+ end
61
+
62
+ def worker
63
+ trap("INT") { exit }
64
+ loop do
65
+ @handler ||= @app ? RackHandler.new(@app,@opts) : RequestHandler.new
66
+ session = @socket.accept
67
+ @handler.run session
59
68
  end
60
69
  end
61
70
 
@@ -1,3 +1,3 @@
1
1
  module Yarn
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.9"
3
3
  end
@@ -2,18 +2,15 @@ $LOAD_PATH << File.expand_path('../../../lib', __FILE__)
2
2
 
3
3
  require 'rubygems'
4
4
  require 'bundler/setup'
5
+ require 'simplecov'
5
6
 
6
- # unless /jruby/ =~ `ruby -v`
7
- require 'simplecov'
8
- SimpleCov.start do
9
- add_filter "/spec/"
10
- add_filter "lib/yarn/http*"
11
- end
12
- # end
7
+ SimpleCov.start do
8
+ add_filter "/spec/"
9
+ add_filter "lib/rack/handler/"
10
+ end
13
11
 
14
12
  require 'yarn'
15
13
  require 'helpers'
16
- require 'pry'
17
14
 
18
15
  RSpec.configure do |config|
19
16
  config.include Helpers
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ module Yarn
4
+ describe AbstractHandler do
5
+ describe "Common behaviour" do
6
+
7
+ before(:each) do
8
+ @dummy_request = "GET /resource/1 HTTP/1.1\r\n "
9
+
10
+ @session = mock('TCPSocket')
11
+ @session.stub(:gets)
12
+ @session.stub(:close)
13
+
14
+ @handler = AbstractHandler.new
15
+ @handler.session = @session
16
+ @handler.stub(:read_request).and_return(@dummy_request)
17
+ end
18
+
19
+ describe "#read_request" do
20
+ it "should return a string from a feed" do
21
+ @handler.unstub!(:read_request)
22
+ @handler.session = StringIO.new
23
+ @handler.session.string = "line1\nline2\nline3"
24
+
25
+ @handler.read_request.should == "line1\nline2\nline3"
26
+ end
27
+
28
+ it "should handle POST body" do
29
+ @req = "POST /app HTTP/1.1\nCookie: $key=value;\nContent-Length: 17;\n\r\nfield1=1&field2=2"
30
+ @handler.unstub!(:read_request)
31
+ @handler.session = StringIO.new
32
+ @handler.session.string = @req
33
+
34
+ @handler.read_request.should == @req
35
+ end
36
+ end
37
+
38
+ describe "#parse_request" do
39
+ it "should invoke the Parser" do
40
+ @handler.parser.should_receive(:run)
41
+
42
+ @handler.parse_request
43
+ end
44
+
45
+ it "should set the bad-request header if parsing fails" do
46
+ bad_request = "BAD Warble warble request"
47
+ @handler.response.status.should be_nil
48
+
49
+ @session.stub(:gets).and_return(bad_request)
50
+ @handler.parse_request
51
+
52
+ @handler.response.status.should == 400
53
+ end
54
+ end
55
+
56
+ describe "#return_response" do
57
+ it "should write the response to the socket" do
58
+ @handler.session.should_receive(:puts).at_least(1).times
59
+ @handler.stub(:response).and_return("HTTP/1.1 201 OK")
60
+ @handler.return_response
61
+ end
62
+ end
63
+
64
+ describe "#persistent?" do
65
+ it "should return true if the Connection header is set to keep-alive" do
66
+ @handler.request = { headers: { "Connection" => "keep-alive" } }
67
+
68
+ @handler.persistent?.should be_true
69
+ end
70
+
71
+ it "should return false if the Connection header is set to close" do
72
+ @handler.request = { headers: { "Connection" => "close" } }
73
+
74
+ @handler.persistent?.should be_false
75
+ end
76
+ end
77
+
78
+ describe "#run" do
79
+ it "should call all relevant template methods" do
80
+ @handler.stub(:client_address)
81
+ @handler.should_receive(:parse_request).once
82
+ @handler.should_receive(:prepare_response).once
83
+ @handler.should_receive(:return_response).once
84
+
85
+ @handler.run(@session)
86
+ end
87
+ end
88
+ end
89
+
90
+
91
+ end
92
+ end
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module Yarn
4
- describe ParsletParser do
4
+ describe Parser do
5
5
  before do
6
- @parser = ParsletParser.new
6
+ @parser = Parser.new
7
7
  end
8
8
 
9
9
  describe "#parse request_line" do
@@ -53,13 +53,11 @@ module Yarn
53
53
  describe "#parse parameters" do
54
54
  it "parses query in the url" do
55
55
  result = @parser.run("GET /page?param1=1&param2=2 HTTP/1.1\n")
56
- puts result
57
56
  result[:uri][:query].should == "param1=1&param2=2"
58
57
  end
59
58
 
60
59
  it "should parse query on rails assets" do
61
60
  result = @parser.run("GET assets/application.js?body=0 HTTP/1.1\n")
62
- puts result
63
61
  result[:uri][:query].should == "body=0"
64
62
  result[:uri][:path].should == "assets/application.js"
65
63
  end
@@ -116,7 +114,6 @@ module Yarn
116
114
  body = "attr=test&attr2=some_other_value&attr3=1231682368125361823"
117
115
  request = "POST /form HTTP/1.1\r\n#{header}\r\n\r\n#{body}"
118
116
  result = @parser.run(request)
119
- puts result
120
117
  result[:body].should == body
121
118
  end
122
119
  end
@@ -4,7 +4,8 @@ module Yarn
4
4
  describe RackHandler do
5
5
 
6
6
  before(:each) do
7
- @handler = RackHandler.new(nil)
7
+ @app = mock('app')
8
+ @handler = RackHandler.new(@app, { host: "www.hostname.com", port: 8888 })
8
9
  @handler.request = @handler.parser.run "GET http://www.hostname.com:8888/some_controller/some_action?param1=1&param2=2 HTTP/1.1"
9
10
  end
10
11
 
@@ -3,92 +3,6 @@ require 'spec_helper'
3
3
  module Yarn
4
4
  describe RequestHandler do
5
5
 
6
- describe "Common behaviour" do
7
-
8
- before(:each) do
9
- @dummy_request = "GET /resource/1 HTTP/1.1\r\n "
10
-
11
- @session = mock('TCPSocket')
12
- @session.stub(:gets).and_return(@dummy_request)
13
-
14
- @handler = RequestHandler.new
15
- @handler.session = @session
16
- @handler.stub(:debug,:log).and_return(true) #silence output
17
- @handler.stub(:read_request).and_return(@dummy_request)
18
- end
19
-
20
- describe "#read_request" do
21
- it "should return a string from a feed" do
22
- @handler.unstub!(:read_request)
23
- @handler.session = StringIO.new
24
- @handler.session.string = "line1\nline2\nline3"
25
-
26
- @handler.read_request.should == "line1\nline2\nline3"
27
- end
28
-
29
- it "should handle POST body" do
30
- @req = "POST /app HTTP/1.1\nCookie: $key=value;\nContent-Length: 17;\n\r\nfield1=1&field2=2"
31
- @handler.unstub!(:read_request)
32
- @handler.session = StringIO.new
33
- @handler.session.string = @req
34
-
35
- @handler.read_request.should == @req
36
- end
37
- end
38
-
39
- describe "#parse_request" do
40
- it "should invoke the Parser" do
41
- @handler.parser.should_receive(:run)
42
-
43
- @handler.parse_request
44
- end
45
-
46
- it "should set the bad-request header if parsing fails" do
47
- bad_request = "BAD Warble warble request"
48
- @handler.response.status.should be_nil
49
-
50
- @session.stub(:gets).and_return(bad_request)
51
- @handler.parse_request
52
-
53
- @handler.response.status.should == 400
54
- end
55
- end
56
-
57
- describe "#return_response" do
58
- it "should write the response to the socket" do
59
- @handler.session.should_receive(:puts).at_least(1).times
60
- @handler.stub(:response).and_return("HTTP/1.1 201 OK")
61
- @handler.return_response
62
- end
63
- end
64
-
65
- describe "#persistent?" do
66
- it "should return true if the Connection header is set to keep-alive" do
67
- @handler.request = { headers: { "Connection" => "keep-alive" } }
68
-
69
- @handler.persistent?.should be_true
70
- end
71
-
72
- it "should return false if the Connection header is set to close" do
73
- @handler.request = { headers: { "Connection" => "close" } }
74
-
75
- @handler.persistent?.should be_false
76
- end
77
- end
78
-
79
- describe "#run" do
80
- it "should call all relevant template methods" do
81
- @handler.stub(:client_address)
82
- @handler.should_receive(:parse_request).once
83
- @handler.should_receive(:prepare_response).once
84
- @handler.should_receive(:return_response).once
85
- @handler.should_receive(:close_connection).once
86
-
87
- @handler.run(@dummy_request)
88
- end
89
- end
90
- end
91
-
92
6
  describe "Static requests" do
93
7
 
94
8
  before(:each) do
@@ -103,7 +17,6 @@ module Yarn
103
17
  end
104
18
 
105
19
  after(:each) do
106
- @handler.close_connection
107
20
  File.delete("index.html") if File.exists?("index.html")
108
21
  File.delete("testdir/index.html") if File.exists?("testdir/index.html")
109
22
  Dir.delete("testdir") if Dir.exists?("testdir")
@@ -216,6 +129,7 @@ module Yarn
216
129
  end
217
130
  end
218
131
  end
132
+
219
133
  describe "Dynamic requests" do
220
134
  before(:each) do
221
135
  @file_content = "!#/bin/ruby\nputs 'Success!'"
@@ -47,18 +47,6 @@ module Yarn
47
47
  end
48
48
  end
49
49
 
50
- # describe "#start" do
51
- # it "should start the socket_listener" do
52
- # @thread = Thread.new do
53
- # @server = Server.new({ output: $console })
54
- # @server.start
55
- # end
56
- # sleep 2
57
- # get("/").should be_true
58
- # @thread.kill
59
- # end
60
- # end
61
-
62
50
  describe "#stop" do
63
51
  it "should notify the server is stopped" do
64
52
  $console = MockIO.new
@@ -6,7 +6,7 @@ gem 'rails', '3.1.0'
6
6
  # gem 'rails', :git => 'git://github.com/rails/rails.git'
7
7
 
8
8
  gem 'sqlite3'
9
- gem 'yarn'
9
+ gem 'yarn', '~> 0.1'
10
10
  gem 'pry'
11
11
 
12
12
  # Gems used only for assets and not required
@@ -8,9 +8,8 @@ Gem::Specification.new do |s|
8
8
  s.authors = ["Jesper Kjeldgaard"]
9
9
  s.email = ["jkjeldgaard@gmail.com"]
10
10
  s.homepage = "https://github.com/thejspr/yarn"
11
- s.summary = %q{Multi-threaded webserver for static and dynamic (*.rb) files as well as rack applications.}
12
- s.description = %q{A multi-threaded web-server written in Ruby 1.9.}
13
-
11
+ s.summary = %q{Multi-process webserver for static, dynamic (*.rb) files and Rack applications.}
12
+ s.description = %q{A multi-process web-server written in Ruby 1.9.}
14
13
  s.rubyforge_project = "yarn"
15
14
 
16
15
  s.files = `git ls-files`.split("\n")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yarn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-02 00:00:00.000000000 Z
12
+ date: 2011-10-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parslet
16
- requirement: &19662180 !ruby/object:Gem::Requirement
16
+ requirement: &12100240 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '1.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *19662180
24
+ version_requirements: *12100240
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: trollop
27
- requirement: &20080020 !ruby/object:Gem::Requirement
27
+ requirement: &12099740 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '1.16'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *20080020
35
+ version_requirements: *12099740
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rack
38
- requirement: &20077620 !ruby/object:Gem::Requirement
38
+ requirement: &12099280 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '1.3'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *20077620
46
+ version_requirements: *12099280
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: cucumber
49
- requirement: &20075460 !ruby/object:Gem::Requirement
49
+ requirement: &12098900 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *20075460
57
+ version_requirements: *12098900
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: faraday
60
- requirement: &20069600 !ruby/object:Gem::Requirement
60
+ requirement: &12098440 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *20069600
68
+ version_requirements: *12098440
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: nokogiri
71
- requirement: &20066540 !ruby/object:Gem::Requirement
71
+ requirement: &12098020 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *20066540
79
+ version_requirements: *12098020
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: rspec
82
- requirement: &20064440 !ruby/object:Gem::Requirement
82
+ requirement: &12097600 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *20064440
90
+ version_requirements: *12097600
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: simplecov
93
- requirement: &20057640 !ruby/object:Gem::Requirement
93
+ requirement: &12097180 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,8 +98,8 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *20057640
102
- description: A multi-threaded web-server written in Ruby 1.9.
101
+ version_requirements: *12097180
102
+ description: A multi-process web-server written in Ruby 1.9.
103
103
  email:
104
104
  - jkjeldgaard@gmail.com
105
105
  executables:
@@ -113,6 +113,7 @@ files:
113
113
  - .rvmrc
114
114
  - Gemfile
115
115
  - Gemfile.lock
116
+ - LICENCE
116
117
  - README.md
117
118
  - Rakefile
118
119
  - bin/yarn
@@ -132,10 +133,11 @@ files:
132
133
  - features/support/hooks.rb
133
134
  - lib/rack/handler/yarn.rb
134
135
  - lib/yarn.rb
136
+ - lib/yarn/abstract_handler.rb
135
137
  - lib/yarn/directory_lister.rb
136
138
  - lib/yarn/error_page.rb
137
139
  - lib/yarn/logging.rb
138
- - lib/yarn/parslet_parser.rb
140
+ - lib/yarn/parser.rb
139
141
  - lib/yarn/rack_handler.rb
140
142
  - lib/yarn/request_handler.rb
141
143
  - lib/yarn/response.rb
@@ -145,10 +147,11 @@ files:
145
147
  - spec/helpers.rb
146
148
  - spec/rack/handler/yarn_spec.rb
147
149
  - spec/spec_helper.rb
150
+ - spec/yarn/abstract_handler_spec.rb
148
151
  - spec/yarn/directory_lister_spec.rb
149
152
  - spec/yarn/error_page_spec.rb
150
153
  - spec/yarn/logging_spec.rb
151
- - spec/yarn/parslet_parser_spec.rb
154
+ - spec/yarn/parser_spec.rb
152
155
  - spec/yarn/rack_handler_spec.rb
153
156
  - spec/yarn/request_handler_spec.rb
154
157
  - spec/yarn/response_spec.rb
@@ -219,9 +222,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
222
  version: '0'
220
223
  requirements: []
221
224
  rubyforge_project: yarn
222
- rubygems_version: 1.8.10
225
+ rubygems_version: 1.8.11
223
226
  signing_key:
224
227
  specification_version: 3
225
- summary: Multi-threaded webserver for static and dynamic (*.rb) files as well as rack
226
- applications.
228
+ summary: Multi-process webserver for static, dynamic (*.rb) files and Rack applications.
227
229
  test_files: []