unicorn 0.96.1 → 0.97.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.
- data/.gitignore +1 -0
- data/Documentation/unicorn_rails.1.txt +6 -1
- data/FAQ +7 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +6 -8
- data/KNOWN_ISSUES +8 -0
- data/Rakefile +1 -4
- data/TODO +1 -6
- data/bin/unicorn +4 -42
- data/bin/unicorn_rails +65 -68
- data/examples/init.sh +7 -2
- data/examples/logger_mp_safe.rb +25 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +13 -0
- data/ext/unicorn_http/common_field_optimization.h +3 -3
- data/ext/unicorn_http/ext_help.h +2 -2
- data/ext/unicorn_http/global_variables.h +1 -3
- data/ext/unicorn_http/unicorn_http.rl +15 -13
- data/lib/unicorn.rb +64 -12
- data/lib/unicorn/configurator.rb +19 -1
- data/lib/unicorn/const.rb +4 -7
- data/lib/unicorn/http_request.rb +1 -1
- data/lib/unicorn/tee_input.rb +39 -39
- data/lib/unicorn/util.rb +1 -2
- data/t/.gitignore +2 -0
- data/t/GNUmakefile +67 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +23 -0
- data/t/bin/unused_listen +40 -0
- data/t/bin/utee +12 -0
- data/t/env.ru +3 -0
- data/t/my-tap-lib.sh +200 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +52 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/test-lib.sh +100 -0
- data/test/rails/test_rails.rb +3 -3
- data/test/test_helper.rb +5 -0
- data/test/unit/test_http_parser_ng.rb +0 -1
- data/test/unit/test_server.rb +1 -0
- data/test/unit/test_signals.rb +5 -1
- data/test/unit/test_tee_input.rb +15 -15
- data/test/unit/test_upload.rb +1 -0
- metadata +17 -2
data/ext/unicorn_http/c_util.h
CHANGED
@@ -29,6 +29,19 @@
|
|
29
29
|
# error off_t size unknown for this platform!
|
30
30
|
#endif /* SIZEOF_OFF_T check */
|
31
31
|
|
32
|
+
/*
|
33
|
+
* ragel enforces fpc as a const, and merely casting can make picky
|
34
|
+
* compilers unhappy, so we have this little helper do our dirty work
|
35
|
+
*/
|
36
|
+
static inline void *deconst(const void *in)
|
37
|
+
{
|
38
|
+
union { const void *in; void *out; } tmp;
|
39
|
+
|
40
|
+
tmp.in = in;
|
41
|
+
|
42
|
+
return tmp.out;
|
43
|
+
}
|
44
|
+
|
32
45
|
/*
|
33
46
|
* capitalizes all lower-case ASCII characters and converts dashes
|
34
47
|
* to underscores for HTTP headers. Locale-agnostic.
|
@@ -67,7 +67,7 @@ static void init_common_fields(void)
|
|
67
67
|
char tmp[64];
|
68
68
|
memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
|
69
69
|
|
70
|
-
for(i =
|
70
|
+
for(i = ARRAY_SIZE(common_http_fields); --i >= 0; cf++) {
|
71
71
|
/* Rack doesn't like certain headers prefixed with "HTTP_" */
|
72
72
|
if (!strcmp("CONTENT_LENGTH", cf->name) ||
|
73
73
|
!strcmp("CONTENT_TYPE", cf->name)) {
|
@@ -87,8 +87,8 @@ static VALUE find_common_field(const char *field, size_t flen)
|
|
87
87
|
int i;
|
88
88
|
struct common_field *cf = common_http_fields;
|
89
89
|
|
90
|
-
for(i =
|
91
|
-
if (cf->len == flen && !memcmp(cf->name, field, flen))
|
90
|
+
for(i = ARRAY_SIZE(common_http_fields); --i >= 0; cf++) {
|
91
|
+
if (cf->len == (long)flen && !memcmp(cf->name, field, flen))
|
92
92
|
return cf->value;
|
93
93
|
}
|
94
94
|
return Qnil;
|
data/ext/unicorn_http/ext_help.h
CHANGED
@@ -47,7 +47,7 @@ static void rb_18_str_set_len(VALUE str, long len)
|
|
47
47
|
# define rb_str_modify(x) do {} while (0)
|
48
48
|
#endif /* ! defined(HAVE_RB_STR_MODIFY) */
|
49
49
|
|
50
|
-
static inline int str_cstr_eq(VALUE val, const char *ptr,
|
50
|
+
static inline int str_cstr_eq(VALUE val, const char *ptr, long len)
|
51
51
|
{
|
52
52
|
return (RSTRING_LEN(val) == len && !memcmp(ptr, RSTRING_PTR(val), len));
|
53
53
|
}
|
@@ -56,7 +56,7 @@ static inline int str_cstr_eq(VALUE val, const char *ptr, size_t len)
|
|
56
56
|
str_cstr_eq(val, const_str, sizeof(const_str) - 1)
|
57
57
|
|
58
58
|
/* strcasecmp isn't locale independent */
|
59
|
-
static int str_cstr_case_eq(VALUE val, const char *ptr,
|
59
|
+
static int str_cstr_case_eq(VALUE val, const char *ptr, long len)
|
60
60
|
{
|
61
61
|
if (RSTRING_LEN(val) == len) {
|
62
62
|
const char *v = RSTRING_PTR(val);
|
@@ -1,7 +1,5 @@
|
|
1
1
|
#ifndef global_variables_h
|
2
2
|
#define global_variables_h
|
3
|
-
static VALUE mUnicorn;
|
4
|
-
static VALUE cHttpParser;
|
5
3
|
static VALUE eHttpParserError;
|
6
4
|
|
7
5
|
static VALUE g_rack_url_scheme;
|
@@ -61,7 +59,7 @@ DEF_MAX_LENGTH(REQUEST_PATH, 1024);
|
|
61
59
|
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
62
60
|
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
63
61
|
|
64
|
-
void init_globals(void)
|
62
|
+
static void init_globals(void)
|
65
63
|
{
|
66
64
|
DEF_GLOBAL(rack_url_scheme, "rack.url_scheme");
|
67
65
|
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
@@ -136,7 +136,7 @@ static inline void hp_invalid_if_trailer(struct http_parser *hp)
|
|
136
136
|
}
|
137
137
|
|
138
138
|
static void write_cont_value(struct http_parser *hp,
|
139
|
-
|
139
|
+
char *buffer, const char *p)
|
140
140
|
{
|
141
141
|
char *vptr;
|
142
142
|
|
@@ -154,7 +154,7 @@ static void write_cont_value(struct http_parser *hp,
|
|
154
154
|
if (RSTRING_LEN(hp->cont) > 0)
|
155
155
|
--hp->mark;
|
156
156
|
|
157
|
-
vptr =
|
157
|
+
vptr = PTR_TO(mark);
|
158
158
|
|
159
159
|
if (RSTRING_LEN(hp->cont) > 0) {
|
160
160
|
assert((' ' == *vptr || '\t' == *vptr) && "invalid leading white space");
|
@@ -220,8 +220,8 @@ static void write_value(VALUE req, struct http_parser *hp,
|
|
220
220
|
action mark {MARK(mark, fpc); }
|
221
221
|
|
222
222
|
action start_field { MARK(start.field, fpc); }
|
223
|
-
action snake_upcase_field { snake_upcase_char((
|
224
|
-
action downcase_char { downcase_char((
|
223
|
+
action snake_upcase_field { snake_upcase_char(deconst(fpc)); }
|
224
|
+
action downcase_char { downcase_char(deconst(fpc)); }
|
225
225
|
action write_field { hp->s.field_len = LEN(start.field, fpc); }
|
226
226
|
action start_value { MARK(mark, fpc); }
|
227
227
|
action write_value { write_value(req, hp, buffer, fpc); }
|
@@ -236,10 +236,9 @@ static void write_value(VALUE req, struct http_parser *hp,
|
|
236
236
|
rb_hash_aset(req, g_http_host, STR_NEW(mark, fpc));
|
237
237
|
}
|
238
238
|
action request_uri {
|
239
|
-
size_t len = LEN(mark, fpc);
|
240
239
|
VALUE str;
|
241
240
|
|
242
|
-
VALIDATE_MAX_LENGTH(
|
241
|
+
VALIDATE_MAX_LENGTH(LEN(mark, fpc), REQUEST_URI);
|
243
242
|
str = rb_hash_aset(req, g_request_uri, STR_NEW(mark, fpc));
|
244
243
|
/*
|
245
244
|
* "OPTIONS * HTTP/1.1\r\n" is a valid request, but we can't have '*'
|
@@ -263,9 +262,8 @@ static void write_value(VALUE req, struct http_parser *hp,
|
|
263
262
|
action http_version { http_version(hp, req, PTR_TO(mark), LEN(mark, fpc)); }
|
264
263
|
action request_path {
|
265
264
|
VALUE val;
|
266
|
-
size_t len = LEN(mark, fpc);
|
267
265
|
|
268
|
-
VALIDATE_MAX_LENGTH(
|
266
|
+
VALIDATE_MAX_LENGTH(LEN(mark, fpc), REQUEST_PATH);
|
269
267
|
val = rb_hash_aset(req, g_request_path, STR_NEW(mark, fpc));
|
270
268
|
|
271
269
|
/* rack says PATH_INFO must start with "/" or be empty */
|
@@ -314,13 +312,13 @@ static void write_value(VALUE req, struct http_parser *hp,
|
|
314
312
|
|
315
313
|
action skip_chunk_data {
|
316
314
|
skip_chunk_data_hack: {
|
317
|
-
size_t nr = MIN(hp->len.chunk, REMAINING);
|
315
|
+
size_t nr = MIN((size_t)hp->len.chunk, REMAINING);
|
318
316
|
memcpy(RSTRING_PTR(req) + hp->s.dest_offset, fpc, nr);
|
319
317
|
hp->s.dest_offset += nr;
|
320
318
|
hp->len.chunk -= nr;
|
321
319
|
p += nr;
|
322
320
|
assert(hp->len.chunk >= 0 && "negative chunk length");
|
323
|
-
if (hp->len.chunk > REMAINING) {
|
321
|
+
if ((size_t)hp->len.chunk > REMAINING) {
|
324
322
|
HP_FL_SET(hp, INCHUNK);
|
325
323
|
goto post_exec;
|
326
324
|
} else {
|
@@ -346,7 +344,7 @@ static void http_parser_init(struct http_parser *hp)
|
|
346
344
|
|
347
345
|
/** exec **/
|
348
346
|
static void http_parser_execute(struct http_parser *hp,
|
349
|
-
VALUE req,
|
347
|
+
VALUE req, char *buffer, size_t len)
|
350
348
|
{
|
351
349
|
const char *p, *pe;
|
352
350
|
int cs = hp->cs;
|
@@ -360,7 +358,8 @@ static void http_parser_execute(struct http_parser *hp,
|
|
360
358
|
p = buffer+off;
|
361
359
|
pe = buffer+len;
|
362
360
|
|
363
|
-
assert(pe - p == len - off &&
|
361
|
+
assert((void *)(pe - p) == (void *)(len - off) &&
|
362
|
+
"pointers aren't same distance");
|
364
363
|
|
365
364
|
if (HP_FL_TEST(hp, INCHUNK)) {
|
366
365
|
HP_FL_UNSET(hp, INCHUNK);
|
@@ -675,10 +674,13 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
|
|
675
674
|
|
676
675
|
void Init_unicorn_http(void)
|
677
676
|
{
|
677
|
+
VALUE mUnicorn, cHttpParser;
|
678
|
+
|
678
679
|
mUnicorn = rb_define_module("Unicorn");
|
680
|
+
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
679
681
|
eHttpParserError =
|
680
682
|
rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError);
|
681
|
-
|
683
|
+
|
682
684
|
init_globals();
|
683
685
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
684
686
|
rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
|
data/lib/unicorn.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'fcntl'
|
4
4
|
require 'unicorn/socket_helper'
|
5
|
+
require 'etc'
|
5
6
|
autoload :Rack, 'rack'
|
6
7
|
|
7
8
|
# Unicorn module containing all of the classes (include C extensions) for running
|
@@ -27,6 +28,49 @@ module Unicorn
|
|
27
28
|
def run(app, options = {})
|
28
29
|
HttpServer.new(app, options).start.join
|
29
30
|
end
|
31
|
+
|
32
|
+
# This returns a lambda to pass in as the app, this does not "build" the
|
33
|
+
# app (which we defer based on the outcome of "preload_app" in the
|
34
|
+
# Unicorn config). The returned lambda will be called when it is
|
35
|
+
# time to build the app.
|
36
|
+
def builder(ru, opts)
|
37
|
+
if ru =~ /\.ru\z/
|
38
|
+
# parse embedded command-line options in config.ru comments
|
39
|
+
/^#\\(.*)/ =~ File.read(ru) and opts.parse!($1.split(/\s+/))
|
40
|
+
end
|
41
|
+
|
42
|
+
lambda do ||
|
43
|
+
inner_app = case ru
|
44
|
+
when /\.ru$/
|
45
|
+
raw = File.read(ru)
|
46
|
+
raw.sub!(/^__END__\n.*/, '')
|
47
|
+
eval("Rack::Builder.new {(#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
|
48
|
+
else
|
49
|
+
require ru
|
50
|
+
Object.const_get(File.basename(ru, '.rb').capitalize)
|
51
|
+
end
|
52
|
+
|
53
|
+
pp({ :inner_app => inner_app }) if $DEBUG
|
54
|
+
|
55
|
+
# return value, matches rackup defaults based on env
|
56
|
+
case ENV["RACK_ENV"]
|
57
|
+
when "development"
|
58
|
+
Rack::Builder.new do
|
59
|
+
use Rack::CommonLogger, $stderr
|
60
|
+
use Rack::ShowExceptions
|
61
|
+
use Rack::Lint
|
62
|
+
run inner_app
|
63
|
+
end.to_app
|
64
|
+
when "deployment"
|
65
|
+
Rack::Builder.new do
|
66
|
+
use Rack::CommonLogger, $stderr
|
67
|
+
run inner_app
|
68
|
+
end.to_app
|
69
|
+
else
|
70
|
+
inner_app
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
30
74
|
end
|
31
75
|
|
32
76
|
# This is the process manager of Unicorn. This manages worker
|
@@ -34,11 +78,11 @@ module Unicorn
|
|
34
78
|
# Listener sockets are started in the master process and shared with
|
35
79
|
# forked worker children.
|
36
80
|
|
37
|
-
class HttpServer < Struct.new(:
|
81
|
+
class HttpServer < Struct.new(:app, :timeout, :worker_processes,
|
38
82
|
:before_fork, :after_fork, :before_exec,
|
39
|
-
:logger, :pid, :
|
83
|
+
:logger, :pid, :listener_opts, :preload_app,
|
40
84
|
:reexec_pid, :orig_app, :init_listeners,
|
41
|
-
:master_pid, :config, :ready_pipe)
|
85
|
+
:master_pid, :config, :ready_pipe, :user)
|
42
86
|
include ::Unicorn::SocketHelper
|
43
87
|
|
44
88
|
# prevents IO objects in here from being GC-ed
|
@@ -119,9 +163,7 @@ module Unicorn
|
|
119
163
|
# releases of Unicorn. You may need to access it in the
|
120
164
|
# before_fork/after_fork hooks. See the Unicorn::Configurator RDoc
|
121
165
|
# for examples.
|
122
|
-
class Worker < Struct.new(:nr, :tmp)
|
123
|
-
|
124
|
-
autoload :Etc, 'etc'
|
166
|
+
class Worker < Struct.new(:nr, :tmp, :switched)
|
125
167
|
|
126
168
|
# worker objects may be compared to just plain numbers
|
127
169
|
def ==(other_nr)
|
@@ -151,6 +193,7 @@ module Unicorn
|
|
151
193
|
Process::GID.change_privilege(gid)
|
152
194
|
end
|
153
195
|
Process.euid != uid and Process::UID.change_privilege(uid)
|
196
|
+
self.switched = true
|
154
197
|
end
|
155
198
|
|
156
199
|
end
|
@@ -210,7 +253,18 @@ module Unicorn
|
|
210
253
|
end
|
211
254
|
config_listeners.each { |addr| listen(addr) }
|
212
255
|
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
256
|
+
|
257
|
+
# this pipe is used to wake us up from select(2) in #join when signals
|
258
|
+
# are trapped. See trap_deferred.
|
259
|
+
init_self_pipe!
|
260
|
+
|
261
|
+
# setup signal handlers before writing pid file in case people get
|
262
|
+
# trigger happy and send signals as soon as the pid file exists.
|
263
|
+
# Note that signals don't actually get handled until the #join method
|
264
|
+
QUEUE_SIGS.each { |sig| trap_deferred(sig) }
|
265
|
+
trap(:CHLD) { |_| awaken_master }
|
213
266
|
self.pid = config[:pid]
|
267
|
+
|
214
268
|
self.master_pid = $$
|
215
269
|
build_app! if preload_app
|
216
270
|
maintain_worker_count
|
@@ -322,14 +376,9 @@ module Unicorn
|
|
322
376
|
# one-at-a-time time and we'll happily drop signals in case somebody
|
323
377
|
# is signalling us too often.
|
324
378
|
def join
|
325
|
-
# this pipe is used to wake us up from select(2) in #join when signals
|
326
|
-
# are trapped. See trap_deferred
|
327
|
-
init_self_pipe!
|
328
379
|
respawn = true
|
329
380
|
last_check = Time.now
|
330
381
|
|
331
|
-
QUEUE_SIGS.each { |sig| trap_deferred(sig) }
|
332
|
-
trap(:CHLD) { |sig_nr| awaken_master }
|
333
382
|
proc_name 'master'
|
334
383
|
logger.info "master process ready" # test_exec.rb relies on this message
|
335
384
|
if ready_pipe
|
@@ -610,6 +659,7 @@ module Unicorn
|
|
610
659
|
LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
611
660
|
worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
612
661
|
after_fork.call(self, worker) # can drop perms
|
662
|
+
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
613
663
|
self.timeout /= 2.0 # halve it for select()
|
614
664
|
build_app! unless preload_app
|
615
665
|
end
|
@@ -725,6 +775,7 @@ module Unicorn
|
|
725
775
|
end
|
726
776
|
|
727
777
|
def load_config!
|
778
|
+
loaded_app = app
|
728
779
|
begin
|
729
780
|
logger.info "reloading config_file=#{config.config_file}"
|
730
781
|
config[:listeners].replace(init_listeners)
|
@@ -735,9 +786,10 @@ module Unicorn
|
|
735
786
|
self.app = orig_app
|
736
787
|
build_app! if preload_app
|
737
788
|
logger.info "done reloading config_file=#{config.config_file}"
|
738
|
-
rescue => e
|
789
|
+
rescue StandardError, LoadError, SyntaxError => e
|
739
790
|
logger.error "error reloading config_file=#{config.config_file}: " \
|
740
791
|
"#{e.class} #{e.message}"
|
792
|
+
self.app = loaded_app
|
741
793
|
end
|
742
794
|
end
|
743
795
|
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -10,7 +10,7 @@ module Unicorn
|
|
10
10
|
# See http://unicorn.bogomips.org/examples/unicorn.conf.rb for an
|
11
11
|
# example config file. An example config file for use with nginx is
|
12
12
|
# also available at http://unicorn.bogomips.org/examples/nginx.conf
|
13
|
-
class Configurator < Struct.new(:set, :config_file)
|
13
|
+
class Configurator < Struct.new(:set, :config_file, :after_reload)
|
14
14
|
|
15
15
|
# Default settings for Unicorn
|
16
16
|
DEFAULTS = {
|
@@ -34,6 +34,10 @@ module Unicorn
|
|
34
34
|
self.set = Hash.new(:unset)
|
35
35
|
use_defaults = defaults.delete(:use_defaults)
|
36
36
|
self.config_file = defaults.delete(:config_file)
|
37
|
+
|
38
|
+
# after_reload is only used by unicorn_rails, unsupported otherwise
|
39
|
+
self.after_reload = defaults.delete(:after_reload)
|
40
|
+
|
37
41
|
set.merge!(DEFAULTS) if use_defaults
|
38
42
|
defaults.each { |key, value| self.send(key, value) }
|
39
43
|
Hash === set[:listener_opts] or
|
@@ -53,6 +57,9 @@ module Unicorn
|
|
53
57
|
test(?w, path) || test(?w, File.dirname(path)) or \
|
54
58
|
raise ArgumentError, "directory for #{var}=#{path} not writable"
|
55
59
|
end
|
60
|
+
|
61
|
+
# unicorn_rails creates dirs here after working_directory is bound
|
62
|
+
after_reload.call if after_reload
|
56
63
|
end
|
57
64
|
|
58
65
|
def commit!(server, options = {}) #:nodoc:
|
@@ -329,6 +336,17 @@ module Unicorn
|
|
329
336
|
HttpServer::START_CTX[:cwd] = ENV["PWD"] = path
|
330
337
|
end
|
331
338
|
|
339
|
+
# Runs worker processes as the specified +user+ and +group+.
|
340
|
+
# The master process always stays running as the user who started it.
|
341
|
+
# This switch will occur after calling the after_fork hook, and only
|
342
|
+
# if the Worker#user method is not called in the after_fork hook
|
343
|
+
def user(user, group = nil)
|
344
|
+
# raises ArgumentError on invalid user/group
|
345
|
+
Etc.getpwnam(user)
|
346
|
+
Etc.getgrnam(group) if group
|
347
|
+
set[:user] = [ user, group ]
|
348
|
+
end
|
349
|
+
|
332
350
|
# expands "unix:path/to/foo" to a socket relative to the current path
|
333
351
|
# expands pathnames of sockets if relative to "~" or "~username"
|
334
352
|
# expands "*:port and ":port" to "0.0.0.0:port"
|
data/lib/unicorn/const.rb
CHANGED
@@ -7,7 +7,7 @@ module Unicorn
|
|
7
7
|
# gave about a 3% to 10% performance improvement over using the strings directly.
|
8
8
|
# Symbols did not really improve things much compared to constants.
|
9
9
|
module Const
|
10
|
-
UNICORN_VERSION="0.
|
10
|
+
UNICORN_VERSION="0.97.0"
|
11
11
|
|
12
12
|
DEFAULT_HOST = "0.0.0.0" # default TCP listen host address
|
13
13
|
DEFAULT_PORT = 8080 # default TCP listen port
|
@@ -16,12 +16,9 @@ module Unicorn
|
|
16
16
|
# The basic max request size we'll try to read.
|
17
17
|
CHUNK_SIZE=(16 * 1024)
|
18
18
|
|
19
|
-
#
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
# Maximum request body size before it is moved out of memory and into a tempfile for reading.
|
24
|
-
MAX_BODY=MAX_HEADER
|
19
|
+
# Maximum request body size before it is moved out of memory and into a
|
20
|
+
# temporary file for reading (112 kilobytes).
|
21
|
+
MAX_BODY=1024 * 112
|
25
22
|
|
26
23
|
# common errors we'll send back
|
27
24
|
ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n"
|
data/lib/unicorn/http_request.rb
CHANGED
data/lib/unicorn/tee_input.rb
CHANGED
@@ -13,19 +13,19 @@ module Unicorn
|
|
13
13
|
#
|
14
14
|
# When processing uploads, Unicorn exposes a TeeInput object under
|
15
15
|
# "rack.input" of the Rack environment.
|
16
|
-
class TeeInput < Struct.new(:socket, :req, :parser, :buf)
|
16
|
+
class TeeInput < Struct.new(:socket, :req, :parser, :buf, :len, :tmp, :buf2)
|
17
17
|
|
18
18
|
# Initializes a new TeeInput object. You normally do not have to call
|
19
19
|
# this unless you are writing an HTTP server.
|
20
20
|
def initialize(*args)
|
21
21
|
super(*args)
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
self.len = parser.content_length
|
23
|
+
self.tmp = len && len < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
|
24
|
+
self.buf2 = ""
|
25
25
|
if buf.size > 0
|
26
|
-
parser.filter_body(
|
27
|
-
|
28
|
-
|
26
|
+
parser.filter_body(buf2, buf) and finalize_input
|
27
|
+
tmp.write(buf2)
|
28
|
+
tmp.seek(0)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -40,16 +40,16 @@ module Unicorn
|
|
40
40
|
# all of the input stream before returning since there's no other
|
41
41
|
# way to determine the size of the request body beforehand.
|
42
42
|
def size
|
43
|
-
|
43
|
+
len and return len
|
44
44
|
|
45
45
|
if socket
|
46
|
-
pos =
|
47
|
-
while tee(Const::CHUNK_SIZE,
|
46
|
+
pos = tmp.pos
|
47
|
+
while tee(Const::CHUNK_SIZE, buf2)
|
48
48
|
end
|
49
|
-
|
49
|
+
tmp.seek(pos)
|
50
50
|
end
|
51
51
|
|
52
|
-
|
52
|
+
self.len = tmp.size
|
53
53
|
end
|
54
54
|
|
55
55
|
# :call-seq:
|
@@ -72,22 +72,22 @@ module Unicorn
|
|
72
72
|
# any data and only block when nothing is available (providing
|
73
73
|
# IO#readpartial semantics).
|
74
74
|
def read(*args)
|
75
|
-
socket or return
|
75
|
+
socket or return tmp.read(*args)
|
76
76
|
|
77
77
|
length = args.shift
|
78
78
|
if nil == length
|
79
|
-
rv =
|
80
|
-
while tee(Const::CHUNK_SIZE,
|
81
|
-
rv <<
|
79
|
+
rv = tmp.read || ""
|
80
|
+
while tee(Const::CHUNK_SIZE, buf2)
|
81
|
+
rv << buf2
|
82
82
|
end
|
83
83
|
rv
|
84
84
|
else
|
85
|
-
rv = args.shift ||
|
86
|
-
diff =
|
85
|
+
rv = args.shift || ""
|
86
|
+
diff = tmp.size - tmp.pos
|
87
87
|
if 0 == diff
|
88
88
|
ensure_length(tee(length, rv), length)
|
89
89
|
else
|
90
|
-
ensure_length(
|
90
|
+
ensure_length(tmp.read(diff > length ? length : diff, rv), length)
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
@@ -102,26 +102,26 @@ module Unicorn
|
|
102
102
|
# This takes zero arguments for strict Rack::Lint compatibility,
|
103
103
|
# unlike IO#gets.
|
104
104
|
def gets
|
105
|
-
socket or return
|
105
|
+
socket or return tmp.gets
|
106
106
|
nil == $/ and return read
|
107
107
|
|
108
|
-
orig_size =
|
109
|
-
if
|
110
|
-
tee(Const::CHUNK_SIZE,
|
111
|
-
|
108
|
+
orig_size = tmp.size
|
109
|
+
if tmp.pos == orig_size
|
110
|
+
tee(Const::CHUNK_SIZE, buf2) or return nil
|
111
|
+
tmp.seek(orig_size)
|
112
112
|
end
|
113
113
|
|
114
|
-
line =
|
114
|
+
line = tmp.gets # cannot be nil here since size > pos
|
115
115
|
$/ == line[-$/.size, $/.size] and return line
|
116
116
|
|
117
|
-
# unlikely, if we got here, then
|
117
|
+
# unlikely, if we got here, then tmp is at EOF
|
118
118
|
begin
|
119
|
-
orig_size =
|
120
|
-
tee(Const::CHUNK_SIZE,
|
121
|
-
|
122
|
-
line <<
|
119
|
+
orig_size = tmp.pos
|
120
|
+
tee(Const::CHUNK_SIZE, buf2) or break
|
121
|
+
tmp.seek(orig_size)
|
122
|
+
line << tmp.gets
|
123
123
|
$/ == line[-$/.size, $/.size] and return line
|
124
|
-
#
|
124
|
+
# tmp is at EOF again here, retry the loop
|
125
125
|
end while true
|
126
126
|
|
127
127
|
line
|
@@ -147,7 +147,7 @@ module Unicorn
|
|
147
147
|
# the offset (zero) of the +ios+ pointer. Subsequent reads will
|
148
148
|
# start from the beginning of the previously-buffered input.
|
149
149
|
def rewind
|
150
|
-
|
150
|
+
tmp.rewind # Rack does not specify what the return value is here
|
151
151
|
end
|
152
152
|
|
153
153
|
private
|
@@ -160,7 +160,7 @@ module Unicorn
|
|
160
160
|
# _entire_ request has been sent, and those will not have
|
161
161
|
# raised EOFError on us.
|
162
162
|
socket.close if socket
|
163
|
-
raise ClientShutdown, "bytes_read=#{
|
163
|
+
raise ClientShutdown, "bytes_read=#{tmp.size}", []
|
164
164
|
when HttpParserError
|
165
165
|
e.set_backtrace([])
|
166
166
|
end
|
@@ -173,8 +173,8 @@ module Unicorn
|
|
173
173
|
def tee(length, dst)
|
174
174
|
unless parser.body_eof?
|
175
175
|
if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
|
176
|
-
|
177
|
-
|
176
|
+
tmp.write(dst)
|
177
|
+
tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
|
178
178
|
return dst
|
179
179
|
end
|
180
180
|
end
|
@@ -201,13 +201,13 @@ module Unicorn
|
|
201
201
|
# streaming input bodies, this is a no-op for
|
202
202
|
# "Transfer-Encoding: chunked" requests.
|
203
203
|
def ensure_length(dst, length)
|
204
|
-
#
|
204
|
+
# len is nil for chunked bodies, so we can't ensure length for those
|
205
205
|
# since they could be streaming bidirectionally and we don't want to
|
206
206
|
# block the caller in that case.
|
207
|
-
return dst if dst.nil? ||
|
207
|
+
return dst if dst.nil? || len.nil?
|
208
208
|
|
209
|
-
while dst.size < length && tee(length - dst.size,
|
210
|
-
dst <<
|
209
|
+
while dst.size < length && tee(length - dst.size, buf2)
|
210
|
+
dst << buf2
|
211
211
|
end
|
212
212
|
|
213
213
|
dst
|