puma 0.8.2-java

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (63) hide show
  1. data/.gemtest +0 -0
  2. data/COPYING +55 -0
  3. data/Gemfile +6 -0
  4. data/History.txt +69 -0
  5. data/LICENSE +26 -0
  6. data/Manifest.txt +60 -0
  7. data/README.md +60 -0
  8. data/Rakefile +12 -0
  9. data/TODO +5 -0
  10. data/bin/puma +15 -0
  11. data/examples/builder.rb +29 -0
  12. data/examples/camping/README +3 -0
  13. data/examples/camping/blog.rb +294 -0
  14. data/examples/camping/tepee.rb +149 -0
  15. data/examples/httpd.conf +474 -0
  16. data/examples/mime.yaml +3 -0
  17. data/examples/mongrel.conf +9 -0
  18. data/examples/monitrc +57 -0
  19. data/examples/random_thrash.rb +19 -0
  20. data/examples/simpletest.rb +52 -0
  21. data/examples/webrick_compare.rb +20 -0
  22. data/ext/puma_http11/PumaHttp11Service.java +13 -0
  23. data/ext/puma_http11/ext_help.h +15 -0
  24. data/ext/puma_http11/extconf.rb +5 -0
  25. data/ext/puma_http11/http11_parser.c +1225 -0
  26. data/ext/puma_http11/http11_parser.h +63 -0
  27. data/ext/puma_http11/http11_parser.java.rl +161 -0
  28. data/ext/puma_http11/http11_parser.rl +146 -0
  29. data/ext/puma_http11/http11_parser_common.rl +54 -0
  30. data/ext/puma_http11/org/jruby/puma/Http11.java +225 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +488 -0
  32. data/ext/puma_http11/puma_http11.c +482 -0
  33. data/lib/puma.rb +18 -0
  34. data/lib/puma/cli.rb +164 -0
  35. data/lib/puma/const.rb +132 -0
  36. data/lib/puma/events.rb +36 -0
  37. data/lib/puma/gems.rb +20 -0
  38. data/lib/puma/mime_types.yml +616 -0
  39. data/lib/puma/rack_patch.rb +22 -0
  40. data/lib/puma/server.rb +429 -0
  41. data/lib/puma/thread_pool.rb +95 -0
  42. data/lib/puma/utils.rb +44 -0
  43. data/lib/puma_http11.jar +0 -0
  44. data/lib/rack/handler/puma.rb +48 -0
  45. data/puma.gemspec +40 -0
  46. data/tasks/gem.rake +24 -0
  47. data/tasks/java.rake +12 -0
  48. data/tasks/native.rake +36 -0
  49. data/tasks/ragel.rake +24 -0
  50. data/test/lobster.ru +4 -0
  51. data/test/mime.yaml +3 -0
  52. data/test/test_cli.rb +19 -0
  53. data/test/test_http10.rb +27 -0
  54. data/test/test_http11.rb +151 -0
  55. data/test/test_persistent.rb +205 -0
  56. data/test/test_rack_handler.rb +10 -0
  57. data/test/test_rack_server.rb +122 -0
  58. data/test/test_thread_pool.rb +102 -0
  59. data/test/test_unix_socket.rb +37 -0
  60. data/test/test_ws.rb +97 -0
  61. data/test/testhelp.rb +41 -0
  62. data/tools/trickletest.rb +45 -0
  63. metadata +163 -0
@@ -0,0 +1,22 @@
1
+ require 'rack/commonlogger'
2
+
3
+ module Rack
4
+ # Patch CommonLogger to use after_reply
5
+ class CommonLogger
6
+ remove_method :call
7
+
8
+ def call(env)
9
+ began_at = Time.now
10
+ status, header, body = @app.call(env)
11
+ header = Utils::HeaderHash.new(header)
12
+
13
+ if ary = env['rack.after_reply']
14
+ ary << lambda { log(env, status, header, began_at) }
15
+ else
16
+ body = BodyProxy.new(body) { log(env, status, header, began_at) }
17
+ end
18
+
19
+ [status, header, body]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,429 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+ require 'stringio'
4
+
5
+ require 'puma/thread_pool'
6
+ require 'puma/const'
7
+ require 'puma/events'
8
+
9
+ require 'puma_http11'
10
+
11
+ require 'socket'
12
+
13
+ module Puma
14
+ class Server
15
+
16
+ include Puma::Const
17
+
18
+ attr_reader :thread
19
+ attr_reader :events
20
+ attr_accessor :app
21
+
22
+ attr_accessor :min_threads
23
+ attr_accessor :max_threads
24
+ attr_accessor :persistent_timeout
25
+
26
+ # Creates a working server on host:port (strange things happen if port
27
+ # isn't a Number).
28
+ #
29
+ # Use HttpServer#run to start the server and HttpServer#acceptor.join to
30
+ # join the thread that's processing incoming requests on the socket.
31
+ #
32
+ def initialize(app, events=Events::DEFAULT)
33
+ @app = app
34
+ @events = events
35
+
36
+ @check, @notify = IO.pipe
37
+ @ios = [@check]
38
+
39
+ @running = false
40
+
41
+ @min_threads = 0
42
+ @max_threads = 16
43
+
44
+ @thread = nil
45
+ @thread_pool = nil
46
+
47
+ @persistent_timeout = PERSISTENT_TIMEOUT
48
+
49
+ @proto_env = {
50
+ "rack.version".freeze => Rack::VERSION,
51
+ "rack.errors".freeze => events.stderr,
52
+ "rack.multithread".freeze => true,
53
+ "rack.multiprocess".freeze => false,
54
+ "rack.run_once".freeze => true,
55
+ "SCRIPT_NAME".freeze => "",
56
+ "CONTENT_TYPE".freeze => "",
57
+ "QUERY_STRING".freeze => "",
58
+ SERVER_PROTOCOL => HTTP_11,
59
+ SERVER_SOFTWARE => PUMA_VERSION,
60
+ GATEWAY_INTERFACE => CGI_VER
61
+ }
62
+ end
63
+
64
+ def add_tcp_listener(host, port)
65
+ @ios << TCPServer.new(host, port)
66
+ end
67
+
68
+ def add_unix_listener(path)
69
+ @ios << UNIXServer.new(path)
70
+ end
71
+
72
+ # Runs the server. It returns the thread used so you can "join" it.
73
+ # You can also access the HttpServer#acceptor attribute to get the
74
+ # thread later.
75
+ def run
76
+ BasicSocket.do_not_reverse_lookup = true
77
+
78
+ @running = true
79
+
80
+ @thread_pool = ThreadPool.new(@min_threads, @max_threads) do |client|
81
+ process_client(client)
82
+ end
83
+
84
+ @thread = Thread.new do
85
+ begin
86
+ check = @check
87
+ sockets = @ios
88
+ pool = @thread_pool
89
+
90
+ while @running
91
+ begin
92
+ ios = IO.select sockets
93
+ ios.first.each do |sock|
94
+ if sock == check
95
+ break if handle_check
96
+ else
97
+ pool << sock.accept
98
+ end
99
+ end
100
+ rescue Errno::ECONNABORTED
101
+ # client closed the socket even before accept
102
+ client.close rescue nil
103
+ rescue Object => e
104
+ @events.unknown_error self, env, e, "Listen loop"
105
+ end
106
+ end
107
+ graceful_shutdown
108
+ ensure
109
+ @ios.each { |i| i.close }
110
+ end
111
+ end
112
+
113
+ return @thread
114
+ end
115
+
116
+ def handle_check
117
+ cmd = @check.read(1)
118
+
119
+ case cmd
120
+ when STOP_COMMAND
121
+ @running = false
122
+ return true
123
+ end
124
+
125
+ return false
126
+ end
127
+
128
+ def process_client(client)
129
+ begin
130
+ while true
131
+ parser = HttpParser.new
132
+ env = @proto_env.dup
133
+ data = client.readpartial(CHUNK_SIZE)
134
+ nparsed = 0
135
+
136
+ # Assumption: nparsed will always be less since data will get filled
137
+ # with more after each parsing. If it doesn't get more then there was
138
+ # a problem with the read operation on the client socket.
139
+ # Effect is to stop processing when the socket can't fill the buffer
140
+ # for further parsing.
141
+ while nparsed < data.length
142
+ nparsed = parser.execute(env, data, nparsed)
143
+
144
+ if parser.finished?
145
+ cl = env[CONTENT_LENGTH]
146
+
147
+ return unless handle_request(env, client, parser.body, cl)
148
+
149
+ nparsed += parser.body.size if cl
150
+
151
+ if data.size > nparsed
152
+ data.slice!(0, nparsed)
153
+ parser = HttpParser.new
154
+ env = @proto_env.dup
155
+ nparsed = 0
156
+ else
157
+ unless IO.select([client], nil, nil, @persistent_timeout)
158
+ raise EOFError, "Timed out persistent connection"
159
+ end
160
+ end
161
+ else
162
+ # Parser is not done, queue up more data to read and continue parsing
163
+ chunk = client.readpartial(CHUNK_SIZE)
164
+ return if !chunk or chunk.length == 0 # read failed, stop processing
165
+
166
+ data << chunk
167
+ if data.length >= MAX_HEADER
168
+ raise HttpParserError,
169
+ "HEADER is longer than allowed, aborting client early."
170
+ end
171
+ end
172
+ end
173
+ end
174
+ rescue EOFError, SystemCallError
175
+ client.close rescue nil
176
+
177
+ rescue HttpParserError => e
178
+ @events.parse_error self, env, e
179
+
180
+ rescue StandardError => e
181
+ @events.unknown_error self, env, e, "Read"
182
+
183
+ ensure
184
+ begin
185
+ client.close
186
+ rescue IOError, SystemCallError
187
+ # Already closed
188
+ rescue StandardError => e
189
+ @events.unknown_error self, env, e, "Client"
190
+ end
191
+ end
192
+ end
193
+
194
+ def normalize_env(env, client)
195
+ if host = env[HTTP_HOST]
196
+ if colon = host.index(":")
197
+ env[SERVER_NAME] = host[0, colon]
198
+ env[SERVER_PORT] = host[colon+1, host.size]
199
+ else
200
+ env[SERVER_NAME] = host
201
+ env[SERVER_PORT] = PORT_80
202
+ end
203
+ end
204
+
205
+ unless env[REQUEST_PATH]
206
+ # it might be a dumbass full host request header
207
+ uri = URI.parse(env[REQUEST_URI])
208
+ env[REQUEST_PATH] = uri.path
209
+
210
+ raise "No REQUEST PATH" unless env[REQUEST_PATH]
211
+ end
212
+
213
+ env[PATH_INFO] = env[REQUEST_PATH]
214
+
215
+ # From http://www.ietf.org/rfc/rfc3875 :
216
+ # "Script authors should be aware that the REMOTE_ADDR and
217
+ # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
218
+ # may not identify the ultimate source of the request.
219
+ # They identify the client for the immediate request to the
220
+ # server; that client may be a proxy, gateway, or other
221
+ # intermediary acting on behalf of the actual source client."
222
+ #
223
+ env[REMOTE_ADDR] = client.peeraddr.last
224
+ end
225
+
226
+ EmptyBinary = ""
227
+ EmptyBinary.force_encoding("BINARY") if EmptyBinary.respond_to? :force_encoding
228
+
229
+ def handle_request(env, client, body, cl)
230
+ normalize_env env, client
231
+
232
+ if cl
233
+ body = read_body env, client, body, cl
234
+ return false unless body
235
+ else
236
+ body = StringIO.new(EmptyBinary)
237
+ end
238
+
239
+ env["rack.input"] = body
240
+ env["rack.url_scheme"] = env["HTTPS"] ? "https" : "http"
241
+
242
+ allow_chunked = false
243
+
244
+ if env['HTTP_VERSION'] == 'HTTP/1.1'
245
+ allow_chunked = true
246
+ http_version = "HTTP/1.1 "
247
+ keep_alive = env["HTTP_CONNECTION"] != "close"
248
+ else
249
+ http_version = "HTTP/1.0 "
250
+ keep_alive = env["HTTP_CONNECTION"] == "Keep-Alive"
251
+ end
252
+
253
+ chunked = false
254
+
255
+ after_reply = env['rack.after_reply'] = []
256
+
257
+ begin
258
+ begin
259
+ status, headers, res_body = @app.call(env)
260
+ rescue => e
261
+ status, headers, res_body = lowlevel_error(e)
262
+ end
263
+
264
+ content_length = nil
265
+
266
+ if res_body.kind_of? Array and res_body.size == 1
267
+ content_length = res_body[0].size
268
+ end
269
+
270
+ client.write http_version
271
+ client.write status.to_s
272
+ client.write " "
273
+ client.write HTTP_STATUS_CODES[status]
274
+ client.write "\r\n"
275
+
276
+ colon = ": "
277
+ line_ending = "\r\n"
278
+
279
+ headers.each do |k, vs|
280
+ case k
281
+ when "Content-Length"
282
+ content_length = vs
283
+ next
284
+ when "Transfer-Encoding"
285
+ allow_chunked = false
286
+ content_length = nil
287
+ end
288
+
289
+ vs.split("\n").each do |v|
290
+ client.write k
291
+ client.write colon
292
+ client.write v
293
+ client.write line_ending
294
+ end
295
+ end
296
+
297
+ client.write "Connection: close\r\n" unless keep_alive
298
+
299
+ if content_length
300
+ client.write "Content-Length: #{content_length}\r\n"
301
+ elsif allow_chunked
302
+ client.write "Transfer-Encoding: chunked\r\n"
303
+ chunked = true
304
+ end
305
+
306
+ client.write line_ending
307
+
308
+ res_body.each do |part|
309
+ if chunked
310
+ client.write part.size.to_s(16)
311
+ client.write line_ending
312
+ client.write part
313
+ client.write line_ending
314
+ else
315
+ client.write part
316
+ end
317
+
318
+ client.flush
319
+ end
320
+
321
+ if chunked
322
+ client.write "0"
323
+ client.write line_ending
324
+ client.write line_ending
325
+ client.flush
326
+ end
327
+
328
+ ensure
329
+ body.close
330
+ res_body.close if res_body.respond_to? :close
331
+
332
+ after_reply.each { |o| o.call }
333
+ end
334
+
335
+ return keep_alive
336
+ end
337
+
338
+ def read_body(env, client, body, cl)
339
+ content_length = cl.to_i
340
+
341
+ remain = content_length - body.size
342
+
343
+ return StringIO.new(body) if remain <= 0
344
+
345
+ # Use a Tempfile if there is a lot of data left
346
+ if remain > MAX_BODY
347
+ stream = Tempfile.new(Const::PUMA_TMP_BASE)
348
+ stream.binmode
349
+ else
350
+ stream = StringIO.new
351
+ end
352
+
353
+ stream.write body
354
+
355
+ # Read an odd sized chunk so we can read even sized ones
356
+ # after this
357
+ chunk = client.readpartial(remain % CHUNK_SIZE)
358
+
359
+ # No chunk means a closed socket
360
+ unless chunk
361
+ stream.close
362
+ return nil
363
+ end
364
+
365
+ remain -= stream.write(chunk)
366
+
367
+ # Raed the rest of the chunks
368
+ while remain > 0
369
+ chunk = client.readpartial(CHUNK_SIZE)
370
+ unless chunk
371
+ stream.close
372
+ return nil
373
+ end
374
+
375
+ remain -= stream.write(chunk)
376
+ end
377
+
378
+ stream.rewind
379
+
380
+ return stream
381
+ end
382
+
383
+ def lowlevel_error(e)
384
+ [500, {}, ["No application configured"]]
385
+ end
386
+
387
+ # Wait for all outstanding requests to finish.
388
+ def graceful_shutdown
389
+ @thread_pool.shutdown if @thread_pool
390
+ end
391
+
392
+ # Stops the acceptor thread and then causes the worker threads to finish
393
+ # off the request queue before finally exiting.
394
+ def stop(sync=false)
395
+ @notify << STOP_COMMAND
396
+
397
+ @thread.join if @thread && sync
398
+ end
399
+
400
+ def attempt_bonjour(name)
401
+ begin
402
+ require 'dnssd'
403
+ rescue LoadError
404
+ return false
405
+ end
406
+
407
+ @bonjour_registered = false
408
+ announced = false
409
+
410
+ @ios.each do |io|
411
+ if io.kind_of? TCPServer
412
+ fixed_name = name.gsub(/\./, "-")
413
+
414
+ DNSSD.announce io, "puma - #{fixed_name}", "http" do |r|
415
+ @bonjour_registered = true
416
+ end
417
+
418
+ announced = true
419
+ end
420
+ end
421
+
422
+ return announced
423
+ end
424
+
425
+ def bonjour_registered?
426
+ @bonjour_registered ||= false
427
+ end
428
+ end
429
+ end