unicorn 0.8.4 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -0
- data/CHANGELOG +1 -3
- data/COPYING +339 -0
- data/GNUmakefile +12 -8
- data/LICENSE +3 -3
- data/Manifest +9 -0
- data/README +20 -8
- data/TODO +5 -13
- data/examples/echo.ru +32 -0
- data/examples/git.ru +13 -0
- data/ext/unicorn/http11/http11.c +9 -2
- data/lib/unicorn.rb +35 -17
- data/lib/unicorn/app/exec_cgi.rb +10 -7
- data/lib/unicorn/app/inetd.rb +108 -0
- data/lib/unicorn/chunked_reader.rb +94 -0
- data/lib/unicorn/configurator.rb +1 -1
- data/lib/unicorn/const.rb +5 -1
- data/lib/unicorn/http_request.rb +16 -60
- data/lib/unicorn/http_response.rb +2 -3
- data/lib/unicorn/tee_input.rb +135 -0
- data/lib/unicorn/trailer_parser.rb +52 -0
- data/lib/unicorn/util.rb +0 -17
- data/local.mk.sample +3 -3
- data/test/rails/test_rails.rb +18 -12
- data/test/test_helper.rb +26 -0
- data/test/unit/test_chunked_reader.rb +180 -0
- data/test/unit/test_configurator.rb +1 -1
- data/test/unit/test_http_parser.rb +30 -0
- data/test/unit/test_request.rb +6 -1
- data/test/unit/test_server.rb +12 -1
- data/test/unit/test_signals.rb +2 -0
- data/test/unit/test_trailer_parser.rb +52 -0
- data/test/unit/test_upload.rb +130 -104
- data/test/unit/test_util.rb +28 -30
- data/unicorn.gemspec +7 -6
- metadata +19 -3
data/README
CHANGED
@@ -131,16 +131,28 @@ regarding this.
|
|
131
131
|
|
132
132
|
== Known Issues
|
133
133
|
|
134
|
-
* WONTFIX: code reloading with Sinatra 0.3.
|
134
|
+
* WONTFIX: code reloading and restarts with Sinatra 0.3.x (and likely older
|
135
135
|
versions) apps is broken. The workaround is to force production
|
136
|
-
mode to disable code reloading in your
|
136
|
+
mode to disable code reloading as well as disabling "run" in your
|
137
|
+
Sinatra application:
|
137
138
|
set :env, :production
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
139
|
+
set :run, false
|
140
|
+
Since this is no longer an issue with Sinatra 0.9.x apps, this will not be
|
141
|
+
fixed on our end. Since Unicorn is itself the application launcher, the
|
142
|
+
at_exit handler used in old Sinatra always caused Mongrel to be launched
|
143
|
+
whenever a Unicorn worker was about to exit.
|
144
|
+
|
145
|
+
Also remember we're capable of replacing the running binary without dropping
|
146
|
+
any connections regardless of framework :)
|
142
147
|
|
143
148
|
== Contact
|
144
149
|
|
145
|
-
|
146
|
-
|
150
|
+
All feedback (bug reports, user/development dicussion, patches, pull
|
151
|
+
requests) go to the mailing list. Patches must be sent inline
|
152
|
+
(git format-patch -M + git send-email). No subscription is necessary
|
153
|
+
to post on the mailing list. No top posting. Address replies +To:+ (or
|
154
|
+
+Cc:+) the original sender and +Cc:+ the mailing list.
|
155
|
+
|
156
|
+
* email: mongrel-unicorn@rubyforge.org
|
157
|
+
* archives: http://rubyforge.org/pipermail/mongrel-unicorn/
|
158
|
+
* subscribe: http://rubyforge.org/mailman/listinfo/mongrel-unicorn/
|
data/TODO
CHANGED
@@ -1,17 +1,9 @@
|
|
1
|
-
|
1
|
+
* integration tests with nginx including bad client handling
|
2
2
|
|
3
|
-
|
3
|
+
* manpages (why do so few Ruby executables come with proper manpages?)
|
4
4
|
|
5
|
-
|
5
|
+
* code cleanups (launchers)
|
6
6
|
|
7
|
-
|
7
|
+
* Pure Ruby HTTP parser
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
curl -T- http://host:port/path < file_from_stdin
|
12
|
-
|
13
|
-
* code cleanups (launchers)
|
14
|
-
|
15
|
-
* Pure Ruby HTTP parser
|
16
|
-
|
17
|
-
* Rubinius support?
|
9
|
+
* Rubinius support
|
data/examples/echo.ru
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#\-E none
|
2
|
+
#
|
3
|
+
# Example application that echoes read data back to the HTTP client.
|
4
|
+
# This emulates the old echo protocol people used to run.
|
5
|
+
#
|
6
|
+
# An example of using this in a client would be to run:
|
7
|
+
# curl --no-buffer -T- http://host:port/
|
8
|
+
#
|
9
|
+
# Then type random stuff in your terminal to watch it get echoed back!
|
10
|
+
|
11
|
+
class EchoBody
|
12
|
+
def initialize(input)
|
13
|
+
@input = input
|
14
|
+
end
|
15
|
+
|
16
|
+
def each(&block)
|
17
|
+
while buf = @input.read(4096)
|
18
|
+
yield buf
|
19
|
+
end
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def close
|
24
|
+
@input = nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
use Rack::Chunked
|
29
|
+
run lambda { |env|
|
30
|
+
[ 200, { 'Content-Type' => 'application/octet-stream' },
|
31
|
+
EchoBody.new(env['rack.input']) ]
|
32
|
+
}
|
data/examples/git.ru
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#\-E none
|
2
|
+
|
3
|
+
# See http://thread.gmane.org/gmane.comp.web.curl.general/10473/raw on
|
4
|
+
# how to setup git for this. A better version of the above patch was
|
5
|
+
# accepted and committed on June 15, 2009, so you can pull the latest
|
6
|
+
# curl CVS snapshot to try this out.
|
7
|
+
require 'unicorn/app/inetd'
|
8
|
+
|
9
|
+
use Rack::Lint
|
10
|
+
use Rack::Chunked # important!
|
11
|
+
run Unicorn::App::Inetd.new(
|
12
|
+
*%w(git daemon --verbose --inetd --export-all --base-path=/home/ew/unicorn)
|
13
|
+
)
|
data/ext/unicorn/http11/http11.c
CHANGED
@@ -324,10 +324,17 @@ static void header_done(void *data, const char *at, size_t length)
|
|
324
324
|
}
|
325
325
|
rb_hash_aset(req, global_server_name, server_name);
|
326
326
|
rb_hash_aset(req, global_server_port, server_port);
|
327
|
+
rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
|
327
328
|
|
328
329
|
/* grab the initial body and stuff it into the hash */
|
329
|
-
|
330
|
-
|
330
|
+
temp = rb_hash_aref(req, global_request_method);
|
331
|
+
if (temp != Qnil) {
|
332
|
+
long len = RSTRING_LEN(temp);
|
333
|
+
char *ptr = RSTRING_PTR(temp);
|
334
|
+
|
335
|
+
if (memcmp(ptr, "HEAD", len) && memcmp(ptr, "GET", len))
|
336
|
+
rb_hash_aset(req, sym_http_body, rb_str_new(at, length));
|
337
|
+
}
|
331
338
|
}
|
332
339
|
|
333
340
|
static void HttpParser_free(void *data) {
|
data/lib/unicorn.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fcntl'
|
2
|
+
require 'tempfile'
|
2
3
|
require 'unicorn/socket_helper'
|
3
4
|
autoload :Rack, 'rack'
|
4
5
|
|
@@ -10,8 +11,15 @@ module Unicorn
|
|
10
11
|
autoload :HttpRequest, 'unicorn/http_request'
|
11
12
|
autoload :HttpResponse, 'unicorn/http_response'
|
12
13
|
autoload :Configurator, 'unicorn/configurator'
|
14
|
+
autoload :TeeInput, 'unicorn/tee_input'
|
15
|
+
autoload :ChunkedReader, 'unicorn/chunked_reader'
|
16
|
+
autoload :TrailerParser, 'unicorn/trailer_parser'
|
13
17
|
autoload :Util, 'unicorn/util'
|
14
18
|
|
19
|
+
Z = '' # the stock empty string we use everywhere...
|
20
|
+
Z.force_encoding(Encoding::BINARY) if Z.respond_to?(:force_encoding)
|
21
|
+
Z.freeze
|
22
|
+
|
15
23
|
class << self
|
16
24
|
def run(app, options = {})
|
17
25
|
HttpServer.new(app, options).start.join
|
@@ -51,10 +59,11 @@ module Unicorn
|
|
51
59
|
# don't rely on Dir.pwd here since it's not symlink-aware, and
|
52
60
|
# symlink dirs are the default with Capistrano...
|
53
61
|
:cwd => `/bin/sh -c pwd`.chomp("\n"),
|
54
|
-
|
62
|
+
:zero => $0.dup,
|
55
63
|
}
|
56
64
|
|
57
|
-
|
65
|
+
Worker = Struct.new(:nr, :tempfile) unless defined?(Worker)
|
66
|
+
class Worker
|
58
67
|
# worker objects may be compared to just plain numbers
|
59
68
|
def ==(other_nr)
|
60
69
|
self.nr == other_nr
|
@@ -322,7 +331,7 @@ module Unicorn
|
|
322
331
|
self.pid = @pid.chomp('.oldbin') if @pid
|
323
332
|
proc_name 'master'
|
324
333
|
else
|
325
|
-
worker = WORKERS.delete(pid) and worker.
|
334
|
+
worker = WORKERS.delete(pid) and worker.tempfile.close rescue nil
|
326
335
|
logger.info "reaped #{status.inspect} " \
|
327
336
|
"worker=#{worker.nr rescue 'unknown'}"
|
328
337
|
end
|
@@ -362,7 +371,7 @@ module Unicorn
|
|
362
371
|
listener_fds = LISTENERS.map { |sock| sock.fileno }
|
363
372
|
ENV['UNICORN_FD'] = listener_fds.join(',')
|
364
373
|
Dir.chdir(START_CTX[:cwd])
|
365
|
-
cmd = [ START_CTX[
|
374
|
+
cmd = [ START_CTX[:zero] ] + START_CTX[:argv]
|
366
375
|
|
367
376
|
# avoid leaking FDs we don't know about, but let before_exec
|
368
377
|
# unset FD_CLOEXEC, if anything else in the app eventually
|
@@ -382,16 +391,16 @@ module Unicorn
|
|
382
391
|
end
|
383
392
|
|
384
393
|
# forcibly terminate all workers that haven't checked in in @timeout
|
385
|
-
# seconds. The timeout is implemented using an unlinked
|
394
|
+
# seconds. The timeout is implemented using an unlinked tempfile
|
386
395
|
# shared between the parent process and each worker. The worker
|
387
|
-
# runs File#chmod to modify the ctime of the
|
396
|
+
# runs File#chmod to modify the ctime of the tempfile. If the ctime
|
388
397
|
# is stale for >@timeout seconds, then we'll kill the corresponding
|
389
398
|
# worker.
|
390
399
|
def murder_lazy_workers
|
391
400
|
diff = stat = nil
|
392
401
|
WORKERS.dup.each_pair do |pid, worker|
|
393
402
|
stat = begin
|
394
|
-
worker.
|
403
|
+
worker.tempfile.stat
|
395
404
|
rescue => e
|
396
405
|
logger.warn "worker=#{worker.nr} PID:#{pid} stat error: #{e.inspect}"
|
397
406
|
kill_worker(:QUIT, pid)
|
@@ -415,7 +424,9 @@ module Unicorn
|
|
415
424
|
SIG_QUEUE << :QUIT # forcibly emulate SIGQUIT
|
416
425
|
return
|
417
426
|
end
|
418
|
-
|
427
|
+
tempfile = Tempfile.new(nil) # as short as possible to save dir space
|
428
|
+
tempfile.unlink # don't allow other processes to find or see it
|
429
|
+
worker = Worker.new(worker_nr, tempfile)
|
419
430
|
@before_fork.call(self, worker)
|
420
431
|
pid = fork { worker_loop(worker) }
|
421
432
|
WORKERS[pid] = worker
|
@@ -433,8 +444,15 @@ module Unicorn
|
|
433
444
|
# once a client is accepted, it is processed in its entirety here
|
434
445
|
# in 3 easy steps: read request, call app, write app response
|
435
446
|
def process_client(app, client)
|
436
|
-
|
437
|
-
|
447
|
+
response = app.call(env = REQUEST.read(client))
|
448
|
+
|
449
|
+
if 100 == response.first.to_i
|
450
|
+
client.write(Const::EXPECT_100_RESPONSE)
|
451
|
+
env.delete(Const::HTTP_EXPECT)
|
452
|
+
response = app.call(env)
|
453
|
+
end
|
454
|
+
|
455
|
+
HttpResponse.write(client, response)
|
438
456
|
# if we get any error, try to write something back to the client
|
439
457
|
# assuming we haven't closed the socket, but don't get hung up
|
440
458
|
# if the socket is already closed or broken. We'll always ensure
|
@@ -457,16 +475,16 @@ module Unicorn
|
|
457
475
|
# traps for USR1, USR2, and HUP may be set in the @after_fork Proc
|
458
476
|
# by the user.
|
459
477
|
def init_worker_process(worker)
|
460
|
-
QUEUE_SIGS.each { |sig| trap(sig,
|
478
|
+
QUEUE_SIGS.each { |sig| trap(sig, 'IGNORE') }
|
461
479
|
trap(:CHLD, 'DEFAULT')
|
462
480
|
SIG_QUEUE.clear
|
463
481
|
proc_name "worker[#{worker.nr}]"
|
464
482
|
START_CTX.clear
|
465
483
|
init_self_pipe!
|
466
|
-
WORKERS.values.each { |other| other.
|
484
|
+
WORKERS.values.each { |other| other.tempfile.close! rescue nil }
|
467
485
|
WORKERS.clear
|
468
486
|
LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
469
|
-
worker.
|
487
|
+
worker.tempfile.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
470
488
|
@after_fork.call(self, worker) # can drop perms
|
471
489
|
@timeout /= 2.0 # halve it for select()
|
472
490
|
build_app! unless @preload_app
|
@@ -486,7 +504,7 @@ module Unicorn
|
|
486
504
|
master_pid = Process.ppid # slightly racy, but less memory usage
|
487
505
|
init_worker_process(worker)
|
488
506
|
nr = 0 # this becomes negative if we need to reopen logs
|
489
|
-
alive = worker.
|
507
|
+
alive = worker.tempfile # tempfile is our lifeline to the master process
|
490
508
|
ready = LISTENERS
|
491
509
|
t = ti = 0
|
492
510
|
|
@@ -552,7 +570,7 @@ module Unicorn
|
|
552
570
|
begin
|
553
571
|
Process.kill(signal, pid)
|
554
572
|
rescue Errno::ESRCH
|
555
|
-
worker = WORKERS.delete(pid) and worker.
|
573
|
+
worker = WORKERS.delete(pid) and worker.tempfile.close rescue nil
|
556
574
|
end
|
557
575
|
end
|
558
576
|
|
@@ -613,8 +631,8 @@ module Unicorn
|
|
613
631
|
end
|
614
632
|
|
615
633
|
def proc_name(tag)
|
616
|
-
$0 = ([ File.basename(START_CTX[
|
617
|
-
|
634
|
+
$0 = ([ File.basename(START_CTX[:zero]), tag ] +
|
635
|
+
START_CTX[:argv]).join(' ')
|
618
636
|
end
|
619
637
|
|
620
638
|
def redirect_io(io, path)
|
data/lib/unicorn/app/exec_cgi.rb
CHANGED
@@ -42,8 +42,11 @@ module Unicorn::App
|
|
42
42
|
|
43
43
|
# Calls the app
|
44
44
|
def call(env)
|
45
|
-
out, err =
|
45
|
+
out, err = Tempfile.new(nil), Tempfile.new(nil)
|
46
|
+
out.unlink
|
47
|
+
err.unlink
|
46
48
|
inp = force_file_input(env)
|
49
|
+
out.sync = err.sync = true
|
47
50
|
pid = fork { run_child(inp, out, err, env) }
|
48
51
|
inp.close
|
49
52
|
pid, status = Process.waitpid2(pid)
|
@@ -118,15 +121,15 @@ module Unicorn::App
|
|
118
121
|
# ensures rack.input is a file handle that we can redirect stdin to
|
119
122
|
def force_file_input(env)
|
120
123
|
inp = env['rack.input']
|
121
|
-
if inp.
|
122
|
-
inp
|
123
|
-
elsif inp.size == 0 # inp could be a StringIO or StringIO-like object
|
124
|
+
if inp.size == 0 # inp could be a StringIO or StringIO-like object
|
124
125
|
::File.open('/dev/null', 'rb')
|
125
126
|
else
|
126
|
-
tmp =
|
127
|
+
tmp = Tempfile.new(nil)
|
128
|
+
tmp.unlink
|
129
|
+
tmp.binmode
|
130
|
+
tmp.sync = true
|
127
131
|
|
128
|
-
|
129
|
-
buf = Unicorn::Z.dup
|
132
|
+
buf = Z.dup
|
130
133
|
while inp.read(CHUNK_SIZE, buf)
|
131
134
|
tmp.syswrite(buf)
|
132
135
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Copyright (c) 2009 Eric Wong
|
2
|
+
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
|
+
|
4
|
+
# this class *must* be used with Rack::Chunked
|
5
|
+
|
6
|
+
module Unicorn::App
|
7
|
+
class Inetd
|
8
|
+
|
9
|
+
class CatBody
|
10
|
+
def initialize(env, cmd)
|
11
|
+
@cmd = cmd
|
12
|
+
@input, @errors = env['rack.input'], env['rack.errors']
|
13
|
+
in_rd, in_wr = IO.pipe
|
14
|
+
@err_rd, err_wr = IO.pipe
|
15
|
+
@out_rd, out_wr = IO.pipe
|
16
|
+
|
17
|
+
@cmd_pid = fork {
|
18
|
+
inp, out, err = (0..2).map { |i| IO.new(i) }
|
19
|
+
inp.reopen(in_rd)
|
20
|
+
out.reopen(out_wr)
|
21
|
+
err.reopen(err_wr)
|
22
|
+
[ in_rd, in_wr, @err_rd, err_wr, @out_rd, out_wr ].each { |io|
|
23
|
+
io.close
|
24
|
+
}
|
25
|
+
exec(*cmd)
|
26
|
+
}
|
27
|
+
[ in_rd, err_wr, out_wr ].each { |io| io.close }
|
28
|
+
[ in_wr, @err_rd, @out_rd ].each { |io| io.binmode }
|
29
|
+
in_wr.sync = true
|
30
|
+
|
31
|
+
# Unfortunately, input here must be processed inside a seperate
|
32
|
+
# thread/process using blocking I/O since env['rack.input'] is not
|
33
|
+
# IO.select-able and attempting to make it so would trip Rack::Lint
|
34
|
+
@inp_pid = fork {
|
35
|
+
[ @err_rd, @out_rd ].each { |io| io.close }
|
36
|
+
buf = Unicorn::Z.dup
|
37
|
+
|
38
|
+
# this is dependent on @input.read having readpartial semantics:
|
39
|
+
while @input.read(16384, buf)
|
40
|
+
in_wr.write(buf)
|
41
|
+
end
|
42
|
+
in_wr.close
|
43
|
+
}
|
44
|
+
in_wr.close
|
45
|
+
end
|
46
|
+
|
47
|
+
def each(&block)
|
48
|
+
buf = Unicorn::Z.dup
|
49
|
+
begin
|
50
|
+
rd, = IO.select([@err_rd, @out_rd])
|
51
|
+
rd && rd.first or next
|
52
|
+
|
53
|
+
if rd.include?(@err_rd)
|
54
|
+
begin
|
55
|
+
@errors.write(@err_rd.read_nonblock(16384, buf))
|
56
|
+
rescue Errno::EINTR
|
57
|
+
rescue Errno::EAGAIN
|
58
|
+
break
|
59
|
+
end while true
|
60
|
+
end
|
61
|
+
|
62
|
+
rd.include?(@out_rd) or next
|
63
|
+
|
64
|
+
begin
|
65
|
+
yield @out_rd.read_nonblock(16384, buf)
|
66
|
+
rescue Errno::EINTR
|
67
|
+
rescue Errno::EAGAIN
|
68
|
+
break
|
69
|
+
end while true
|
70
|
+
rescue EOFError,Errno::EPIPE,Errno::EBADF,Errno::EINVAL
|
71
|
+
break
|
72
|
+
end while true
|
73
|
+
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def close
|
78
|
+
@input = nil
|
79
|
+
[ [ @cmd.inspect, @cmd_pid ], [ 'input streamer', @inp_pid ]
|
80
|
+
].each { |str, pid|
|
81
|
+
begin
|
82
|
+
pid, status = Process.waitpid2(pid)
|
83
|
+
status.success? or
|
84
|
+
@errors.write("#{str}: #{status.inspect} (PID:#{pid})\n")
|
85
|
+
rescue Errno::ECHILD
|
86
|
+
@errors.write("Failed to reap #{str} (PID:#{pid})\n")
|
87
|
+
end
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize(*cmd)
|
94
|
+
@cmd = cmd
|
95
|
+
end
|
96
|
+
|
97
|
+
def call(env)
|
98
|
+
expect = env[Unicorn::Const::HTTP_EXPECT] and
|
99
|
+
/\A100-continue\z/i =~ expect and
|
100
|
+
return [ 100, {} , [] ]
|
101
|
+
|
102
|
+
[ 200, { 'Content-Type' => 'application/octet-stream' },
|
103
|
+
CatBody.new(env, @cmd) ]
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# Copyright (c) 2009 Eric Wong
|
2
|
+
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
|
+
|
4
|
+
require 'unicorn'
|
5
|
+
require 'unicorn/http11'
|
6
|
+
|
7
|
+
module Unicorn
|
8
|
+
class ChunkedReader
|
9
|
+
|
10
|
+
def initialize(env, input, buf)
|
11
|
+
@env, @input, @buf = env, input, buf
|
12
|
+
@chunk_left = 0
|
13
|
+
parse_chunk_header
|
14
|
+
end
|
15
|
+
|
16
|
+
def readpartial(max, buf = Z.dup)
|
17
|
+
while @input && @chunk_left <= 0 && ! parse_chunk_header
|
18
|
+
@buf << @input.readpartial(Const::CHUNK_SIZE, buf)
|
19
|
+
end
|
20
|
+
|
21
|
+
if @input
|
22
|
+
begin
|
23
|
+
@buf << @input.read_nonblock(Const::CHUNK_SIZE, buf)
|
24
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
max = @chunk_left if max > @chunk_left
|
29
|
+
buf.replace(last_block(max) || Z)
|
30
|
+
@chunk_left -= buf.size
|
31
|
+
(0 == buf.size && @input.nil?) and raise EOFError
|
32
|
+
buf
|
33
|
+
end
|
34
|
+
|
35
|
+
def gets
|
36
|
+
line = nil
|
37
|
+
begin
|
38
|
+
line = readpartial(Const::CHUNK_SIZE)
|
39
|
+
begin
|
40
|
+
if line.sub!(%r{\A(.*?#{$/})}, Z)
|
41
|
+
@chunk_left += line.size
|
42
|
+
@buf = @buf ? (line << @buf) : line
|
43
|
+
return $1.dup
|
44
|
+
end
|
45
|
+
line << readpartial(Const::CHUNK_SIZE)
|
46
|
+
end while true
|
47
|
+
rescue EOFError
|
48
|
+
return line
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def last_block(max = nil)
|
55
|
+
rv = @buf
|
56
|
+
if max && rv && max < rv.size
|
57
|
+
@buf = rv[max - rv.size, rv.size - max]
|
58
|
+
return rv[0, max]
|
59
|
+
end
|
60
|
+
@buf = Z.dup
|
61
|
+
rv
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_chunk_header
|
65
|
+
buf = @buf
|
66
|
+
# ignoring chunk-extension info for now, I haven't seen any use for it
|
67
|
+
# (or any users, and TE:chunked sent by clients is rare already)
|
68
|
+
# if there was not enough data in buffer to parse length of the chunk
|
69
|
+
# then just return
|
70
|
+
if buf.sub!(/\A(?:\r\n)?([a-fA-F0-9]{1,8})[^\r]*?\r\n/, Z)
|
71
|
+
@chunk_left = $1.to_i(16)
|
72
|
+
if 0 == @chunk_left # EOF
|
73
|
+
buf.sub!(/\A\r\n(?:\r\n)?/, Z) # cleanup for future requests
|
74
|
+
if trailer = @env[Const::HTTP_TRAILER]
|
75
|
+
tp = TrailerParser.new(trailer)
|
76
|
+
while ! tp.execute!(@env, buf)
|
77
|
+
buf << @input.readpartial(Const::CHUNK_SIZE)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@input = nil
|
81
|
+
end
|
82
|
+
return @chunk_left
|
83
|
+
end
|
84
|
+
|
85
|
+
buf.size > 256 and
|
86
|
+
raise HttpParserError,
|
87
|
+
"malformed chunk, chunk-length not found in buffer: " \
|
88
|
+
"#{buf.inspect}"
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|