jaws 0.5.0

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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Graham Batty
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ = JAWS
2
+
3
+ Just Another Web Server. Obviously there are already a few in the Ruby eco-system, so I'll just list the design goals:
4
+
5
+ * Have a concurrency model that's predictable and easy to understand
6
+ It doesn't use EventMachine and uses a small and manageable thread pool to serve requests.
7
+ Specifically, it can handle a concurrency of N with N threads, but doesn't accept any connections
8
+ above that concurrency. This is so that it doesn't drop connections if it gets overloaded, nor
9
+ does it advertise a concurrency level (like 950) that in a practical web app is just impossible.
10
+ * Have a pluggable listen/accept system
11
+ Things like Swiftiply and CloudBridge, which have unusual means of accepting incoming connections,
12
+ currently monkey patch mongrel to enable their behaviour. This server allows you to override the
13
+ standard accept behaviour so these systems can work without fragile object surgery.
14
+ * Be built for rack right from the start
15
+ This server talks rack and only rack. It doesn't expect to be used as a standalone server.
16
+ * Be capable of being run in pure ruby, but provide better performance if possible
17
+ As things move forward, having a pure ruby implementation is important for enabling people
18
+ to work on and improve ruby software. This server uses the http_parser gem for http parsing,
19
+ which provides a first class implementation in ruby and (will eventually) support using a
20
+ C extension that conforms to the same interface, as set out by the specs in that gem,
21
+ so that it can achieve the same or better parsing performance as mongrel.
22
+
23
+ == Note on Patches/Pull Requests
24
+
25
+ * Fork the project.
26
+ * Make your feature addition or bug fix.
27
+ * Add tests for it. This is important so I don't break it in a
28
+ future version unintentionally.
29
+ * Commit, do not mess with rakefile, version, or history.
30
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
31
+ * Send me a pull request. Bonus points for topic branches.
32
+
33
+ == Copyright
34
+
35
+ Copyright (c) 2010 Graham Batty. See LICENSE for details.
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "jaws"
8
+ gem.summary = %Q{Just Another Web Server}
9
+ gem.description = %Q{A Ruby web server designed to have a predictable and simple concurrency model, and to be capable of running in a pure-ruby environment.}
10
+ gem.email = "graham@stormbrew.ca"
11
+ gem.homepage = "http://github.com/stormbrew/jaws"
12
+ gem.authors = ["Graham Batty"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_dependency "http_parser", ">= 0.1.2"
15
+ gem.add_dependency "rack", ">= 1.1.0"
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.spec_files = FileList['spec/**/*_spec.rb']
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+ task :spec => :check_dependencies
36
+
37
+ task :default => :spec
38
+
39
+ require 'rake/rdoctask'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "jaws #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
@@ -0,0 +1,324 @@
1
+ require 'rack/utils'
2
+ require 'http/parser'
3
+ require 'mutex_m'
4
+ require 'socket'
5
+
6
+ module Jaws
7
+ def self.decapse_name(name)
8
+ name.gsub(%r{^([A-Z])}) { $1.downcase }.gsub(%r{([a-z])([A-Z])}) { $1 + "_" + $2.downcase }
9
+ end
10
+ def self.encapse_name(name)
11
+ name.gsub(%r{(^|_)([a-z])}) { $2.upcase }
12
+ end
13
+
14
+ class Server
15
+ DefaultOptions = {
16
+ :Host => '0.0.0.0',
17
+ :Port => 8080,
18
+ :MaxClients => 20,
19
+ :SystemCores => nil,
20
+ :ReadTimeout => 2,
21
+ }
22
+
23
+ # The default values for most of the rack environment variables
24
+ DefaultRackEnv = {
25
+ "rack.version" => [1,1],
26
+ "rack.url_scheme" => "http",
27
+ "rack.input" => StringIO.new,
28
+ "rack.errors" => $stderr,
29
+ "rack.multithread" => true,
30
+ "rack.multiprocess" => false,
31
+ "rack.run_once" => false,
32
+ "SCRIPT_NAME" => "",
33
+ "PATH_INFO" => "",
34
+ "QUERY_STRING" => "",
35
+ "SERVER_SOFTWARE" => "Rack+Jaws",
36
+ }
37
+
38
+ StatusStrings = Rack::Utils::HTTP_STATUS_CODES
39
+ CodesWithoutBody = Rack::Utils::STATUS_WITH_NO_ENTITY_BODY
40
+
41
+ # The host to listen on when run(app) is called. Also set with options[:Host]
42
+ attr_accessor :host
43
+ # The port to listen on when run(app) is called. Also set with options[:Port]
44
+ attr_accessor :port
45
+ # The maximum number of requests this server should handle concurrently. Also set with options[:MaxClients]
46
+ # Note that you should set this legitimately to the number of clients you can actually handle and not
47
+ # some arbitrary high number like with Mongrel. This server will simply not accept more connections
48
+ # than it can handle, which allows you to run other server instances on other machines to take up the slack.
49
+ # A really really good rule of thumb for a database driven site is to have it be less than the number
50
+ # of database connections your (hopefuly properly tuned) database server can handle. If you run
51
+ # more than one web server machine, the TOTAL max_clients from all those servers should be less than what
52
+ # the database can handle.
53
+ attr_accessor :max_clients
54
+ # The number of cores the system has. This may eventually be used to determine if the process should fork
55
+ # if it's running on a ruby implementation that doesn't support multiprocessing. If set to nil,
56
+ # it'll auto-detect, and failing that just assume it shouldn't fork at all. If you want it to never
57
+ # fork, you should set it to 1 (1 core means 1 process).
58
+ # Also set with options[:SystemCores]
59
+ attr_accessor :system_cores
60
+ # The amount of time, in seconds, the server will wait without input before disconnecting the client.
61
+ # Also set with options[:Timeout]
62
+ attr_accessor :read_timeout
63
+
64
+ # Initializes a new Jaws server object. Pass it a hash of options (:Host, :Port, :MaxClients, and :SystemCores valid)
65
+ def initialize(options = DefaultOptions)
66
+ @options = DefaultOptions.merge(options)
67
+ self.class::DefaultOptions.each do |k,v|
68
+ send(:"#{Jaws.decapse_name(k.to_s)}=", @options[k])
69
+ end
70
+ self.extend Mutex_m
71
+ end
72
+
73
+ # You can re-implement this in a derived class in order to use a different
74
+ # mechanism to listen for connections. It should return
75
+ # an object that responds to accept() by returning an open connection to a
76
+ # client. It also has to respond to synchronize and yield to the block
77
+ # given to that method and be thread safe in that block. It must also
78
+ # respond to close() by immediately terminating any waiting accept() calls
79
+ # and responding to closed? with true thereafter. Close may be called
80
+ # from outside the object's synchronize block.
81
+ def create_listener(options)
82
+ l = TCPServer.new(@host, @port)
83
+ # let 10 requests back up for each request we can handle concurrently.
84
+ # note that this value is often truncated by the OS to numbers like 128
85
+ # or even 5. You may be able to raise this maximum using sysctl (on BSD/OSX)
86
+ # or /proc/sys/net/core/somaxconn on linux 2.6.
87
+ l.listen(@max_clients * 10)
88
+ l.extend Mutex_m # lock around use of the listener object.
89
+ return l
90
+ end
91
+ protected :create_listener
92
+
93
+ # Builds an env object from the information provided. Derived handlers
94
+ # can override this to provide additional information.
95
+ def build_env(client, req)
96
+ rack_env = DefaultRackEnv.dup
97
+ req.fill_rack_env(rack_env)
98
+ rack_env["SERVER_PORT"] ||= @port.to_s
99
+
100
+ if (rack_env["rack.input"].respond_to? :set_encoding)
101
+ rack_env["rack.input"].set_encoding "ASCII-8BIT"
102
+ end
103
+
104
+ rack_env["REMOTE_PORT"], rack_env["REMOTE_ADDR"] = Socket::unpack_sockaddr_in(client.getpeername)
105
+ rack_env["REMOTE_PORT"] &&= rack_env["REMOTE_PORT"].to_s
106
+ rack_env["SERVER_PROTOCOL"] = "HTTP/" << req.version.join('.')
107
+
108
+ return rack_env
109
+ end
110
+ protected :build_env
111
+
112
+ # Reads from a connection, yielding chunks of data as it goes,
113
+ # until the connection closes. Once the connection closes, it returns.
114
+ def chunked_read(io, timeout)
115
+ begin
116
+ loop do
117
+ list = IO.select([io], [], [], @read_timeout)
118
+ if (list.nil? || list.empty?)
119
+ # IO.select tells us we timed out by giving us nil,
120
+ # disconnect the non-talkative client.
121
+ return
122
+ end
123
+ data = io.recv(4096)
124
+ if (data == "")
125
+ # If recv returns an empty string, that means the other
126
+ # end closed the connection (either in response to our
127
+ # end closing the write pipe or because they just felt
128
+ # like it) so we close the connection from our end too.
129
+ return
130
+ end
131
+ yield data
132
+ end
133
+ ensure
134
+ io.close if (!io.closed?)
135
+ end
136
+ end
137
+ private :read_timeout
138
+
139
+ def process_request(client, req, app)
140
+ rack_env = build_env(client, req)
141
+
142
+ # call the app
143
+ begin
144
+ status, headers, body = app.call(rack_env)
145
+
146
+ # headers
147
+ match = %r{^([0-9]{3,3})( +([[:graph:] ]+))?}.match(status.to_s)
148
+ code = match[1].to_i
149
+ response = "HTTP/1.1 #{match[1]} #{match[3] || StatusStrings[code] || "Unknown"}\r\n"
150
+
151
+ if (!headers["Transfer-Encoding"] || headers["Transfer-Encoding"] == "identity")
152
+ body_len = headers["Content-Length"] && headers["Content-Length"].to_i
153
+ if (!body_len)
154
+ headers["Transfer-Encoding"] = "chunked"
155
+ end
156
+ else
157
+ headers.delete("Content-Length")
158
+ end
159
+
160
+ headers.each do |key, vals|
161
+ vals.each_line do |val|
162
+ response << "#{key}: #{val}\r\n"
163
+ end
164
+ end
165
+ response << "\r\n"
166
+
167
+ client.write(response)
168
+
169
+ # only output a body if the request wants one and the status code
170
+ # should have one.
171
+ if (req.method != "HEAD" && !CodesWithoutBody.include?(code))
172
+ if (body_len)
173
+ # If the app set a content length, we output that length
174
+ written = 0
175
+ body.each do |chunk|
176
+ remain = body_len - written
177
+ if (chunk.size > remain)
178
+ chunk[remain, chunk.size] = ""
179
+ end
180
+ client.write(chunk)
181
+ written += chunk.size
182
+ if (written >= body_len)
183
+ break
184
+ end
185
+ end
186
+ if (written < body_len)
187
+ $stderr.puts("Request gave Content-Length(#{body_len}) but gave less data(#{written}). Aborting connection.")
188
+ return
189
+ end
190
+ else
191
+ # If the app didn't set a length, we do it chunked.
192
+ body.each do |chunk|
193
+ client.write(chunk.size.to_s(16) + "\r\n")
194
+ client.write(chunk)
195
+ client.write("\r\n")
196
+ end
197
+ client.write("0\r\n")
198
+ client.write("\r\n")
199
+ end
200
+ end
201
+
202
+ # if the conditions are right, close the connection
203
+ if ((req.headers["CONNECTION"] && req.headers["CONNECTION"] =~ /close/) ||
204
+ (headers["Connection"] && headers["Connection"] =~ /close/) ||
205
+ (req.version == [1,0]))
206
+ client.close_write
207
+ end
208
+ rescue Errno::EPIPE
209
+ raise # pass the buck up.
210
+ rescue Object => e
211
+ err_str = "<h2>500 Internal Server Error</h2>"
212
+ err_str << "<p>#{e}: #{e.backtrace.first}</p>"
213
+ client.write("HTTP/1.1 500 Internal Server Error\r\n")
214
+ client.write("Connection: close\r\n")
215
+ client.write("Content-Length: #{err_str.length}\r\n")
216
+ client.write("Content-Type: text/html\r\n")
217
+ client.write("\r\n")
218
+ client.write(err_str)
219
+ client.close_write
220
+ return
221
+ ensure
222
+ body.close if (body.respond_to? :close)
223
+ end
224
+ end
225
+ private :process_request
226
+
227
+ # Accepts a connection from a client and handles requests on it until
228
+ # the connection closes.
229
+ def process_client(app)
230
+ loop do
231
+ begin
232
+ client = @listener.synchronize do
233
+ begin
234
+ @listener && @listener.accept()
235
+ rescue => e
236
+ return # this means we've been turned off, so exit the loop.
237
+ end
238
+ end
239
+ if (!client)
240
+ return # nil return means we're quitting, exit loop.
241
+ end
242
+
243
+ req = Http::Parser.new()
244
+ buf = ""
245
+ chunked_read(client, @timeout) do |data|
246
+ begin
247
+ buf << data
248
+ req.parse!(buf)
249
+ if (req.done?)
250
+ process_request(client, req, app)
251
+ req = Http::Parser.new()
252
+ if (@listener.closed?)
253
+ return # ignore any more requests from this client if we're shutting down.
254
+ end
255
+ end
256
+ rescue Http::ParserError => e
257
+ err_str = "<h2>#{e.code} #{e.message}</h2>"
258
+ client.write("HTTP/1.1 #{e.code} #{e.message}\r\n")
259
+ client.write("Connection: close\r\n")
260
+ client.write("Content-Length: #{err_str.length}\r\n")
261
+ client.write("Content-Type: text/html\r\n")
262
+ client.write("\r\n")
263
+ client.write(err_str)
264
+ client.close_write
265
+ end
266
+ end
267
+ rescue Errno::EPIPE
268
+ # do nothing, just let the connection close.
269
+ rescue Object => e
270
+ $stderr.puts("Unhandled error #{e}:")
271
+ e.backtrace.each do |line|
272
+ $stderr.puts(line)
273
+ end
274
+ ensure
275
+ client.close if (client && !client.closed?)
276
+ end
277
+ end
278
+ end
279
+ private :process_client
280
+
281
+ # Runs the application through the configured handler.
282
+ # Can only be run once at a time. If you try to run it more than
283
+ # once, the second run will block until the first finishes.
284
+ def run(app)
285
+ synchronize do
286
+ begin
287
+ @listener = create_listener(@options)
288
+ if (@max_clients > 1)
289
+ @master = Thread.current
290
+ @workers = (0...@max_clients).collect do
291
+ Thread.new do
292
+ process_client(app)
293
+ end
294
+ end
295
+ @workers.each do |worker|
296
+ worker.join
297
+ end
298
+ else
299
+ @master = Thread.current
300
+ @workers = [Thread.current]
301
+ process_client(app)
302
+ end
303
+ ensure
304
+ @listener.close if (@listener && !@listener.closed?)
305
+ @listener = @master = @workers = nil
306
+ end
307
+ end
308
+ end
309
+
310
+ def stop()
311
+ # close the connection, the handler threads will exit
312
+ # the next time they try to load.
313
+ # TODO: Make it force them to exit after a timeout.
314
+ @listener.close if !@listener.closed?
315
+ end
316
+
317
+ def running?
318
+ !@workers.nil?
319
+ end
320
+ def stopped?
321
+ @workers.nil?
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,13 @@
1
+ require 'jaws/server'
2
+
3
+ module Rack
4
+ module Handler
5
+ # This class is here for rackup-style automatic server detection.
6
+ # See Jaws::Server for more details.
7
+ class Jaws
8
+ def self.run(app, options = Jaws::Server::DefaultOptions)
9
+ ::Jaws::Server.new(options).run(app)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,63 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'jaws/server'
4
+ require 'rack/lint'
5
+
6
+ describe Jaws::Server do
7
+ include TestRequest::Helpers
8
+
9
+ before :all do
10
+ @server = Jaws::Server.new(:Host => @host='0.0.0.0',:Port => @port=9204)
11
+ @thread = Thread.new do
12
+ @server.run(Rack::Lint.new(TestRequest.new))
13
+ end
14
+ Thread.pass until @server.running?
15
+ end
16
+
17
+ after :all do
18
+ @server.stop
19
+ Thread.pass until @server.stopped?
20
+ end
21
+
22
+ it "should respond to a simple get request" do
23
+ GET "/"
24
+ status.should == 200
25
+ end
26
+
27
+ it "should have CGI headers on GET" do
28
+ GET("/")
29
+ response["REQUEST_METHOD"].should == "GET"
30
+ response["SCRIPT_NAME"].should == ''
31
+ response["PATH_INFO"].should == "/"
32
+ response["QUERY_STRING"].should == ""
33
+ response["test.postdata"].should == ""
34
+
35
+ GET("/test/foo?quux=1")
36
+ response["REQUEST_METHOD"].should == "GET"
37
+ response["SCRIPT_NAME"].should == ''
38
+ response["REQUEST_URI"].should == "/test/foo?quux=1"
39
+ response["PATH_INFO"].should == "/test/foo"
40
+ response["QUERY_STRING"].should == "quux=1"
41
+ end
42
+
43
+ it "should have CGI headers on POST" do
44
+ POST("/", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
45
+ status.should == 200
46
+ response["REQUEST_METHOD"].should == "POST"
47
+ response["REQUEST_URI"].should == "/"
48
+ response["QUERY_STRING"].should == ""
49
+ response["HTTP_X_TEST_HEADER"].should == "42"
50
+ response["test.postdata"].should == "rack-form-data=23"
51
+ end
52
+
53
+ it "should support HTTP auth" do
54
+ GET("/test", {:user => "ruth", :passwd => "secret"})
55
+ response["HTTP_AUTHORIZATION"].should == "Basic cnV0aDpzZWNyZXQ="
56
+ end
57
+
58
+ it "should set status" do
59
+ GET("/test?secret")
60
+ status.should == 403
61
+ response["rack.url_scheme"].should == "http"
62
+ end
63
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,69 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'spec'
4
+ require 'spec/autorun'
5
+ require 'yaml'
6
+ require 'net/http'
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
11
+
12
+ # Borrowed from Rack's own specs.
13
+ class TestRequest
14
+ def call(env)
15
+ status = env["QUERY_STRING"] =~ /secret/ ? 403 : 200
16
+ env["test.postdata"] = env["rack.input"].read
17
+ body = env.to_yaml
18
+ size = body.respond_to?(:bytesize) ? body.bytesize : body.size
19
+ [status, {"Content-Type" => "text/yaml", "Content-Length" => size.to_s}, [body]]
20
+ end
21
+
22
+ module Helpers
23
+ attr_reader :status, :response
24
+
25
+ ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
26
+ ENV["RUBYOPT"] = "-I#{ROOT}/lib -rubygems"
27
+
28
+ def root
29
+ ROOT
30
+ end
31
+
32
+ def rackup
33
+ "#{ROOT}/bin/rackup"
34
+ end
35
+
36
+ def GET(path, header={})
37
+ Net::HTTP.start(@host, @port) { |http|
38
+ user = header.delete(:user)
39
+ passwd = header.delete(:passwd)
40
+
41
+ get = Net::HTTP::Get.new(path, header)
42
+ get.basic_auth user, passwd if user && passwd
43
+ http.request(get) { |response|
44
+ @status = response.code.to_i
45
+ begin
46
+ @response = YAML.load(response.body)
47
+ rescue ArgumentError
48
+ @response = nil
49
+ end
50
+ }
51
+ }
52
+ end
53
+
54
+ def POST(path, formdata={}, header={})
55
+ Net::HTTP.start(@host, @port) { |http|
56
+ user = header.delete(:user)
57
+ passwd = header.delete(:passwd)
58
+
59
+ post = Net::HTTP::Post.new(path, header)
60
+ post.form_data = formdata
61
+ post.basic_auth user, passwd if user && passwd
62
+ http.request(post) { |response|
63
+ @status = response.code.to_i
64
+ @response = YAML.load(response.body)
65
+ }
66
+ }
67
+ end
68
+ end
69
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jaws
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
+ platform: ruby
11
+ authors:
12
+ - Graham Batty
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-16 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 2
30
+ - 9
31
+ version: 1.2.9
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: http_parser
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ - 1
44
+ - 2
45
+ version: 0.1.2
46
+ type: :runtime
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: rack
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 1
57
+ - 1
58
+ - 0
59
+ version: 1.1.0
60
+ type: :runtime
61
+ version_requirements: *id003
62
+ description: A Ruby web server designed to have a predictable and simple concurrency model, and to be capable of running in a pure-ruby environment.
63
+ email: graham@stormbrew.ca
64
+ executables: []
65
+
66
+ extensions: []
67
+
68
+ extra_rdoc_files:
69
+ - LICENSE
70
+ - README.rdoc
71
+ files:
72
+ - .document
73
+ - .gitignore
74
+ - LICENSE
75
+ - README.rdoc
76
+ - Rakefile
77
+ - VERSION
78
+ - lib/jaws/server.rb
79
+ - lib/rack/handler/jaws.rb
80
+ - spec/jaws_spec.rb
81
+ - spec/spec.opts
82
+ - spec/spec_helper.rb
83
+ has_rdoc: true
84
+ homepage: http://github.com/stormbrew/jaws
85
+ licenses: []
86
+
87
+ post_install_message:
88
+ rdoc_options:
89
+ - --charset=UTF-8
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ requirements: []
107
+
108
+ rubyforge_project:
109
+ rubygems_version: 1.3.6
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Just Another Web Server
113
+ test_files:
114
+ - spec/jaws_spec.rb
115
+ - spec/spec_helper.rb