unicorn 0.96.1 → 0.97.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|