thin 1.0.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of thin might be problematic. Click here for more details.
- data/CHANGELOG +26 -0
- data/example/async_app.ru +126 -0
- data/example/async_chat.ru +247 -0
- data/example/async_tailer.ru +100 -0
- data/example/config.ru +1 -1
- data/ext/thin_parser/parser.c +299 -1065
- data/ext/thin_parser/parser.rl +7 -9
- data/ext/thin_parser/thin.c +36 -36
- data/lib/rack/adapter/rails.rb +31 -23
- data/lib/thin.rb +2 -3
- data/lib/thin/command.rb +4 -3
- data/lib/thin/connection.rb +51 -15
- data/lib/thin/controllers/cluster.rb +7 -1
- data/lib/thin/controllers/controller.rb +1 -0
- data/lib/thin/request.rb +16 -2
- data/lib/thin/response.rb +4 -2
- data/lib/thin/runner.rb +1 -1
- data/lib/thin/version.rb +5 -5
- data/lib/thin_parser.bundle +0 -0
- data/spec/command_spec.rb +6 -1
- data/spec/controllers/cluster_spec.rb +33 -0
- data/spec/controllers/controller_spec.rb +11 -3
- data/spec/request/parser_spec.rb +28 -4
- data/spec/response_spec.rb +8 -0
- data/spec/runner_spec.rb +3 -2
- data/spec/server/pipelining_spec.rb +1 -0
- data/spec/server/stopping_spec.rb +13 -3
- data/spec/spec_helper.rb +2 -2
- data/tasks/gem.rake +9 -3
- metadata +9 -8
- data/COMMITTERS +0 -3
- data/lib/rack/handler/thin.rb +0 -18
data/ext/thin_parser/parser.rl
CHANGED
@@ -83,7 +83,7 @@
|
|
83
83
|
/** Data **/
|
84
84
|
%% write data;
|
85
85
|
|
86
|
-
int
|
86
|
+
int thin_http_parser_init(http_parser *parser) {
|
87
87
|
int cs = 0;
|
88
88
|
%% write init;
|
89
89
|
parser->cs = cs;
|
@@ -99,7 +99,7 @@ int http_parser_init(http_parser *parser) {
|
|
99
99
|
|
100
100
|
|
101
101
|
/** exec **/
|
102
|
-
size_t
|
102
|
+
size_t thin_http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off) {
|
103
103
|
const char *p, *pe;
|
104
104
|
int cs = parser->cs;
|
105
105
|
|
@@ -126,34 +126,32 @@ size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len,
|
|
126
126
|
|
127
127
|
if(parser->body_start) {
|
128
128
|
/* final \r\n combo encountered so stop right here */
|
129
|
-
%%write eof;
|
130
129
|
parser->nread++;
|
131
130
|
}
|
132
131
|
|
133
132
|
return(parser->nread);
|
134
133
|
}
|
135
134
|
|
136
|
-
int
|
135
|
+
int thin_http_parser_finish(http_parser *parser)
|
137
136
|
{
|
138
137
|
int cs = parser->cs;
|
139
138
|
|
140
|
-
%%write eof;
|
141
139
|
|
142
140
|
parser->cs = cs;
|
143
141
|
|
144
|
-
if (
|
142
|
+
if (thin_http_parser_has_error(parser) ) {
|
145
143
|
return -1;
|
146
|
-
} else if (
|
144
|
+
} else if (thin_http_parser_is_finished(parser) ) {
|
147
145
|
return 1;
|
148
146
|
} else {
|
149
147
|
return 0;
|
150
148
|
}
|
151
149
|
}
|
152
150
|
|
153
|
-
int
|
151
|
+
int thin_http_parser_has_error(http_parser *parser) {
|
154
152
|
return parser->cs == http_parser_error;
|
155
153
|
}
|
156
154
|
|
157
|
-
int
|
155
|
+
int thin_http_parser_is_finished(http_parser *parser) {
|
158
156
|
return parser->cs == http_parser_first_final;
|
159
157
|
}
|
data/ext/thin_parser/thin.c
CHANGED
@@ -72,7 +72,7 @@ DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
|
72
72
|
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
73
73
|
|
74
74
|
|
75
|
-
void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
|
75
|
+
static void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
|
76
76
|
{
|
77
77
|
char *ch, *end;
|
78
78
|
VALUE req = (VALUE)data;
|
@@ -86,7 +86,7 @@ void http_field(void *data, const char *field, size_t flen, const char *value, s
|
|
86
86
|
f = rb_str_dup(global_http_prefix);
|
87
87
|
f = rb_str_buf_cat(f, field, flen);
|
88
88
|
|
89
|
-
for(ch = RSTRING_PTR(f), end =
|
89
|
+
for(ch = RSTRING_PTR(f) + RSTRING_LEN(global_http_prefix), end = RSTRING_PTR(f) + RSTRING_LEN(f); ch < end; ch++) {
|
90
90
|
if(*ch == '-') {
|
91
91
|
*ch = '_';
|
92
92
|
} else {
|
@@ -97,7 +97,7 @@ void http_field(void *data, const char *field, size_t flen, const char *value, s
|
|
97
97
|
rb_hash_aset(req, f, v);
|
98
98
|
}
|
99
99
|
|
100
|
-
void request_method(void *data, const char *at, size_t length)
|
100
|
+
static void request_method(void *data, const char *at, size_t length)
|
101
101
|
{
|
102
102
|
VALUE req = (VALUE)data;
|
103
103
|
VALUE val = Qnil;
|
@@ -106,7 +106,7 @@ void request_method(void *data, const char *at, size_t length)
|
|
106
106
|
rb_hash_aset(req, global_request_method, val);
|
107
107
|
}
|
108
108
|
|
109
|
-
void request_uri(void *data, const char *at, size_t length)
|
109
|
+
static void request_uri(void *data, const char *at, size_t length)
|
110
110
|
{
|
111
111
|
VALUE req = (VALUE)data;
|
112
112
|
VALUE val = Qnil;
|
@@ -117,7 +117,7 @@ void request_uri(void *data, const char *at, size_t length)
|
|
117
117
|
rb_hash_aset(req, global_request_uri, val);
|
118
118
|
}
|
119
119
|
|
120
|
-
void fragment(void *data, const char *at, size_t length)
|
120
|
+
static void fragment(void *data, const char *at, size_t length)
|
121
121
|
{
|
122
122
|
VALUE req = (VALUE)data;
|
123
123
|
VALUE val = Qnil;
|
@@ -128,7 +128,7 @@ void fragment(void *data, const char *at, size_t length)
|
|
128
128
|
rb_hash_aset(req, global_fragment, val);
|
129
129
|
}
|
130
130
|
|
131
|
-
void request_path(void *data, const char *at, size_t length)
|
131
|
+
static void request_path(void *data, const char *at, size_t length)
|
132
132
|
{
|
133
133
|
VALUE req = (VALUE)data;
|
134
134
|
VALUE val = Qnil;
|
@@ -140,7 +140,7 @@ void request_path(void *data, const char *at, size_t length)
|
|
140
140
|
rb_hash_aset(req, global_path_info, val);
|
141
141
|
}
|
142
142
|
|
143
|
-
void query_string(void *data, const char *at, size_t length)
|
143
|
+
static void query_string(void *data, const char *at, size_t length)
|
144
144
|
{
|
145
145
|
VALUE req = (VALUE)data;
|
146
146
|
VALUE val = Qnil;
|
@@ -151,7 +151,7 @@ void query_string(void *data, const char *at, size_t length)
|
|
151
151
|
rb_hash_aset(req, global_query_string, val);
|
152
152
|
}
|
153
153
|
|
154
|
-
void http_version(void *data, const char *at, size_t length)
|
154
|
+
static void http_version(void *data, const char *at, size_t length)
|
155
155
|
{
|
156
156
|
VALUE req = (VALUE)data;
|
157
157
|
VALUE val = rb_str_new(at, length);
|
@@ -161,7 +161,7 @@ void http_version(void *data, const char *at, size_t length)
|
|
161
161
|
/** Finalizes the request header to have a bunch of stuff that's
|
162
162
|
needed. */
|
163
163
|
|
164
|
-
void header_done(void *data, const char *at, size_t length)
|
164
|
+
static void header_done(void *data, const char *at, size_t length)
|
165
165
|
{
|
166
166
|
VALUE req = (VALUE)data;
|
167
167
|
VALUE temp = Qnil;
|
@@ -215,7 +215,7 @@ void header_done(void *data, const char *at, size_t length)
|
|
215
215
|
}
|
216
216
|
|
217
217
|
|
218
|
-
void
|
218
|
+
void Thin_HttpParser_free(void *data) {
|
219
219
|
TRACE();
|
220
220
|
|
221
221
|
if(data) {
|
@@ -224,7 +224,7 @@ void HttpParser_free(void *data) {
|
|
224
224
|
}
|
225
225
|
|
226
226
|
|
227
|
-
VALUE
|
227
|
+
VALUE Thin_HttpParser_alloc(VALUE klass)
|
228
228
|
{
|
229
229
|
VALUE obj;
|
230
230
|
http_parser *hp = ALLOC_N(http_parser, 1);
|
@@ -237,9 +237,9 @@ VALUE HttpParser_alloc(VALUE klass)
|
|
237
237
|
hp->query_string = query_string;
|
238
238
|
hp->http_version = http_version;
|
239
239
|
hp->header_done = header_done;
|
240
|
-
|
240
|
+
thin_http_parser_init(hp);
|
241
241
|
|
242
|
-
obj = Data_Wrap_Struct(klass, NULL,
|
242
|
+
obj = Data_Wrap_Struct(klass, NULL, Thin_HttpParser_free, hp);
|
243
243
|
|
244
244
|
return obj;
|
245
245
|
}
|
@@ -251,11 +251,11 @@ VALUE HttpParser_alloc(VALUE klass)
|
|
251
251
|
*
|
252
252
|
* Creates a new parser.
|
253
253
|
*/
|
254
|
-
VALUE
|
254
|
+
VALUE Thin_HttpParser_init(VALUE self)
|
255
255
|
{
|
256
256
|
http_parser *http = NULL;
|
257
257
|
DATA_GET(self, http_parser, http);
|
258
|
-
|
258
|
+
thin_http_parser_init(http);
|
259
259
|
|
260
260
|
return self;
|
261
261
|
}
|
@@ -268,11 +268,11 @@ VALUE HttpParser_init(VALUE self)
|
|
268
268
|
* Resets the parser to it's initial state so that you can reuse it
|
269
269
|
* rather than making new ones.
|
270
270
|
*/
|
271
|
-
VALUE
|
271
|
+
VALUE Thin_HttpParser_reset(VALUE self)
|
272
272
|
{
|
273
273
|
http_parser *http = NULL;
|
274
274
|
DATA_GET(self, http_parser, http);
|
275
|
-
|
275
|
+
thin_http_parser_init(http);
|
276
276
|
|
277
277
|
return Qnil;
|
278
278
|
}
|
@@ -285,13 +285,13 @@ VALUE HttpParser_reset(VALUE self)
|
|
285
285
|
* Finishes a parser early which could put in a "good" or bad state.
|
286
286
|
* You should call reset after finish it or bad things will happen.
|
287
287
|
*/
|
288
|
-
VALUE
|
288
|
+
VALUE Thin_HttpParser_finish(VALUE self)
|
289
289
|
{
|
290
290
|
http_parser *http = NULL;
|
291
291
|
DATA_GET(self, http_parser, http);
|
292
|
-
|
292
|
+
thin_http_parser_finish(http);
|
293
293
|
|
294
|
-
return
|
294
|
+
return thin_http_parser_is_finished(http) ? Qtrue : Qfalse;
|
295
295
|
}
|
296
296
|
|
297
297
|
|
@@ -312,7 +312,7 @@ VALUE HttpParser_finish(VALUE self)
|
|
312
312
|
* the parsing from that position. It needs all of the original data as well
|
313
313
|
* so you have to append to the data buffer as you read.
|
314
314
|
*/
|
315
|
-
VALUE
|
315
|
+
VALUE Thin_HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
|
316
316
|
{
|
317
317
|
http_parser *http = NULL;
|
318
318
|
int from = 0;
|
@@ -329,11 +329,11 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
|
|
329
329
|
rb_raise(eHttpParserError, "Requested start is after data buffer end.");
|
330
330
|
} else {
|
331
331
|
http->data = (void *)req_hash;
|
332
|
-
|
332
|
+
thin_http_parser_execute(http, dptr, dlen, from);
|
333
333
|
|
334
334
|
VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
|
335
335
|
|
336
|
-
if(
|
336
|
+
if(thin_http_parser_has_error(http)) {
|
337
337
|
rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
|
338
338
|
} else {
|
339
339
|
return INT2FIX(http_parser_nread(http));
|
@@ -349,12 +349,12 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
|
|
349
349
|
*
|
350
350
|
* Tells you whether the parser is in an error state.
|
351
351
|
*/
|
352
|
-
VALUE
|
352
|
+
VALUE Thin_HttpParser_has_error(VALUE self)
|
353
353
|
{
|
354
354
|
http_parser *http = NULL;
|
355
355
|
DATA_GET(self, http_parser, http);
|
356
356
|
|
357
|
-
return
|
357
|
+
return thin_http_parser_has_error(http) ? Qtrue : Qfalse;
|
358
358
|
}
|
359
359
|
|
360
360
|
|
@@ -364,12 +364,12 @@ VALUE HttpParser_has_error(VALUE self)
|
|
364
364
|
*
|
365
365
|
* Tells you whether the parser is finished or not and in a good state.
|
366
366
|
*/
|
367
|
-
VALUE
|
367
|
+
VALUE Thin_HttpParser_is_finished(VALUE self)
|
368
368
|
{
|
369
369
|
http_parser *http = NULL;
|
370
370
|
DATA_GET(self, http_parser, http);
|
371
371
|
|
372
|
-
return
|
372
|
+
return thin_http_parser_is_finished(http) ? Qtrue : Qfalse;
|
373
373
|
}
|
374
374
|
|
375
375
|
|
@@ -380,7 +380,7 @@ VALUE HttpParser_is_finished(VALUE self)
|
|
380
380
|
* Returns the amount of data processed so far during this processing cycle. It is
|
381
381
|
* set to 0 on initialize or reset calls and is incremented each time execute is called.
|
382
382
|
*/
|
383
|
-
VALUE
|
383
|
+
VALUE Thin_HttpParser_nread(VALUE self)
|
384
384
|
{
|
385
385
|
http_parser *http = NULL;
|
386
386
|
DATA_GET(self, http_parser, http);
|
@@ -422,12 +422,12 @@ void Init_thin_parser()
|
|
422
422
|
eHttpParserError = rb_define_class_under(mThin, "InvalidRequest", rb_eIOError);
|
423
423
|
|
424
424
|
cHttpParser = rb_define_class_under(mThin, "HttpParser", rb_cObject);
|
425
|
-
rb_define_alloc_func(cHttpParser,
|
426
|
-
rb_define_method(cHttpParser, "initialize",
|
427
|
-
rb_define_method(cHttpParser, "reset",
|
428
|
-
rb_define_method(cHttpParser, "finish",
|
429
|
-
rb_define_method(cHttpParser, "execute",
|
430
|
-
rb_define_method(cHttpParser, "error?",
|
431
|
-
rb_define_method(cHttpParser, "finished?",
|
432
|
-
rb_define_method(cHttpParser, "nread",
|
425
|
+
rb_define_alloc_func(cHttpParser, Thin_HttpParser_alloc);
|
426
|
+
rb_define_method(cHttpParser, "initialize", Thin_HttpParser_init,0);
|
427
|
+
rb_define_method(cHttpParser, "reset", Thin_HttpParser_reset,0);
|
428
|
+
rb_define_method(cHttpParser, "finish", Thin_HttpParser_finish,0);
|
429
|
+
rb_define_method(cHttpParser, "execute", Thin_HttpParser_execute,3);
|
430
|
+
rb_define_method(cHttpParser, "error?", Thin_HttpParser_has_error,0);
|
431
|
+
rb_define_method(cHttpParser, "finished?", Thin_HttpParser_is_finished,0);
|
432
|
+
rb_define_method(cHttpParser, "nread", Thin_HttpParser_nread,0);
|
433
433
|
}
|
data/lib/rack/adapter/rails.rb
CHANGED
@@ -22,7 +22,13 @@ module Rack
|
|
22
22
|
|
23
23
|
load_application
|
24
24
|
|
25
|
-
@
|
25
|
+
@rails_app = if ActionController::Dispatcher.instance_methods.include?(:call)
|
26
|
+
ActionController::Dispatcher.new
|
27
|
+
else
|
28
|
+
CgiApp.new
|
29
|
+
end
|
30
|
+
|
31
|
+
@file_app = Rack::File.new(::File.join(RAILS_ROOT, "public"))
|
26
32
|
end
|
27
33
|
|
28
34
|
def load_application
|
@@ -31,31 +37,20 @@ module Rack
|
|
31
37
|
require "#{@root}/config/environment"
|
32
38
|
require 'dispatcher'
|
33
39
|
|
34
|
-
|
40
|
+
if @prefix
|
41
|
+
if ActionController::Base.respond_to?(:relative_url_root=)
|
42
|
+
ActionController::Base.relative_url_root = @prefix # Rails 2.1.1
|
43
|
+
else
|
44
|
+
ActionController::AbstractRequest.relative_url_root = @prefix
|
45
|
+
end
|
46
|
+
end
|
35
47
|
end
|
36
48
|
|
37
|
-
# TODO refactor this in File#can_serve?(path) ??
|
38
49
|
def file_exist?(path)
|
39
|
-
full_path = ::File.join(@
|
50
|
+
full_path = ::File.join(@file_app.root, Utils.unescape(path))
|
40
51
|
::File.file?(full_path) && ::File.readable_real?(full_path)
|
41
52
|
end
|
42
53
|
|
43
|
-
def serve_file(env)
|
44
|
-
@file_server.call(env)
|
45
|
-
end
|
46
|
-
|
47
|
-
def serve_rails(env)
|
48
|
-
request = Request.new(env)
|
49
|
-
response = Response.new
|
50
|
-
|
51
|
-
session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
|
52
|
-
cgi = CGIWrapper.new(request, response)
|
53
|
-
|
54
|
-
Dispatcher.dispatch(cgi, session_options, response)
|
55
|
-
|
56
|
-
response.finish
|
57
|
-
end
|
58
|
-
|
59
54
|
def call(env)
|
60
55
|
path = env['PATH_INFO'].chomp('/')
|
61
56
|
method = env['REQUEST_METHOD']
|
@@ -63,18 +58,31 @@ module Rack
|
|
63
58
|
|
64
59
|
if FILE_METHODS.include?(method)
|
65
60
|
if file_exist?(path) # Serve the file if it's there
|
66
|
-
return
|
61
|
+
return @file_app.call(env)
|
67
62
|
elsif file_exist?(cached_path) # Serve the page cache if it's there
|
68
63
|
env['PATH_INFO'] = cached_path
|
69
|
-
return
|
64
|
+
return @file_app.call(env)
|
70
65
|
end
|
71
66
|
end
|
72
67
|
|
73
68
|
# No static file, let Rails handle it
|
74
|
-
|
69
|
+
@rails_app.call(env)
|
75
70
|
end
|
76
71
|
|
77
72
|
protected
|
73
|
+
# For Rails pre Rack (2.3)
|
74
|
+
class CgiApp
|
75
|
+
def call(env)
|
76
|
+
request = Request.new(env)
|
77
|
+
response = Response.new
|
78
|
+
session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
|
79
|
+
cgi = CGIWrapper.new(request, response)
|
80
|
+
|
81
|
+
Dispatcher.dispatch(cgi, session_options, response)
|
82
|
+
|
83
|
+
response.finish
|
84
|
+
end
|
85
|
+
end
|
78
86
|
|
79
87
|
class CGIWrapper < ::CGI
|
80
88
|
def initialize(request, response, *args)
|
data/lib/thin.rb
CHANGED
@@ -4,8 +4,10 @@ require 'fileutils'
|
|
4
4
|
require 'timeout'
|
5
5
|
require 'stringio'
|
6
6
|
require 'time'
|
7
|
+
require 'forwardable'
|
7
8
|
|
8
9
|
require 'rubygems'
|
10
|
+
require 'openssl'
|
9
11
|
require 'eventmachine'
|
10
12
|
|
11
13
|
require 'thin/version'
|
@@ -41,9 +43,6 @@ require 'rack'
|
|
41
43
|
require 'rack/adapter/loader'
|
42
44
|
|
43
45
|
module Rack
|
44
|
-
module Handler
|
45
|
-
autoload :Thin, 'rack/handler/thin'
|
46
|
-
end
|
47
46
|
module Adapter
|
48
47
|
autoload :Rails, 'rack/adapter/rails'
|
49
48
|
end
|
data/lib/thin/command.rb
CHANGED
@@ -34,12 +34,13 @@ module Thin
|
|
34
34
|
# Turn into a runnable shell command
|
35
35
|
def shellify
|
36
36
|
shellified_options = @options.inject([]) do |args, (name, value)|
|
37
|
+
option_name = name.to_s.tr("_", "-")
|
37
38
|
case value
|
38
39
|
when NilClass,
|
39
|
-
TrueClass then args << "--#{
|
40
|
+
TrueClass then args << "--#{option_name}"
|
40
41
|
when FalseClass
|
41
|
-
when Array then value.each { |v| args << "--#{
|
42
|
-
else args << "--#{
|
42
|
+
when Array then value.each { |v| args << "--#{option_name}=#{v.inspect}" }
|
43
|
+
else args << "--#{option_name}=#{value.inspect}"
|
43
44
|
end
|
44
45
|
args
|
45
46
|
end
|
data/lib/thin/connection.rb
CHANGED
@@ -10,7 +10,10 @@ module Thin
|
|
10
10
|
CHUNKED_REGEXP = /\bchunked\b/i.freeze
|
11
11
|
|
12
12
|
include Logging
|
13
|
-
|
13
|
+
|
14
|
+
# This is a template async response. N.B. Can't use string for body on 1.9
|
15
|
+
AsyncResponse = [-1, {}, []].freeze
|
16
|
+
|
14
17
|
# Rack application (adapter) served by this connection.
|
15
18
|
attr_accessor :app
|
16
19
|
|
@@ -59,8 +62,20 @@ module Thin
|
|
59
62
|
# Add client info to the request env
|
60
63
|
@request.remote_address = remote_address
|
61
64
|
|
62
|
-
#
|
63
|
-
|
65
|
+
# Connection may be closed unless the App#call response was a [-1, ...]
|
66
|
+
# It should be noted that connection objects will linger until this
|
67
|
+
# callback is no longer referenced, so be tidy!
|
68
|
+
@request.async_callback = method(:post_process)
|
69
|
+
|
70
|
+
# When we're under a non-async framework like rails, we can still spawn
|
71
|
+
# off async responses using the callback info, so there's little point
|
72
|
+
# in removing this.
|
73
|
+
response = AsyncResponse
|
74
|
+
catch(:async) do
|
75
|
+
# Process the request calling the Rack adapter
|
76
|
+
response = @app.call(@request.env)
|
77
|
+
end
|
78
|
+
response
|
64
79
|
rescue Exception
|
65
80
|
handle_error
|
66
81
|
terminate_request
|
@@ -69,13 +84,18 @@ module Thin
|
|
69
84
|
|
70
85
|
def post_process(result)
|
71
86
|
return unless result
|
87
|
+
result = result.to_a
|
88
|
+
|
89
|
+
# Status code -1 indicates that we're going to respond later (async).
|
90
|
+
return if result.first == AsyncResponse.first
|
72
91
|
|
73
92
|
# Set the Content-Length header if possible
|
74
93
|
set_content_length(result) if need_content_length?(result)
|
75
|
-
|
76
|
-
@response.status, @response.headers, @response.body = result
|
94
|
+
|
95
|
+
@response.status, @response.headers, @response.body = *result
|
77
96
|
|
78
97
|
log "!! Rack application returned nil body. Probably you wanted it to be an empty string?" if @response.body.nil?
|
98
|
+
|
79
99
|
# Make the response persistent if requested by the client
|
80
100
|
@response.persistent! if @request.persistent?
|
81
101
|
|
@@ -85,13 +105,17 @@ module Thin
|
|
85
105
|
send_data chunk
|
86
106
|
end
|
87
107
|
|
88
|
-
# If no more request on that same connection, we close it.
|
89
|
-
close_connection_after_writing unless persistent?
|
90
|
-
|
91
108
|
rescue Exception
|
92
109
|
handle_error
|
93
110
|
ensure
|
94
|
-
|
111
|
+
# If the body is being deferred, then terminate afterward.
|
112
|
+
if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
|
113
|
+
@response.body.callback { terminate_request }
|
114
|
+
@response.body.errback { terminate_request }
|
115
|
+
else
|
116
|
+
# Don't terminate the response if we're going async.
|
117
|
+
terminate_request unless result && result.first == AsyncResponse.first
|
118
|
+
end
|
95
119
|
end
|
96
120
|
|
97
121
|
# Logs catched exception and closes the connection.
|
@@ -101,22 +125,33 @@ module Thin
|
|
101
125
|
close_connection rescue nil
|
102
126
|
end
|
103
127
|
|
128
|
+
def close_request_response
|
129
|
+
@request.async_close.succeed
|
130
|
+
@request.close rescue nil
|
131
|
+
@response.close rescue nil
|
132
|
+
end
|
133
|
+
|
104
134
|
# Does request and response cleanup (closes open IO streams and
|
105
135
|
# deletes created temporary files).
|
106
136
|
# Re-initializes response and request if client supports persistent
|
107
137
|
# connection.
|
108
138
|
def terminate_request
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
139
|
+
unless persistent?
|
140
|
+
close_connection_after_writing rescue nil
|
141
|
+
close_request_response
|
142
|
+
else
|
143
|
+
close_request_response
|
144
|
+
# Prepare the connection for another request if the client
|
145
|
+
# supports HTTP pipelining (persistent connection).
|
146
|
+
post_init
|
147
|
+
end
|
115
148
|
end
|
116
149
|
|
117
150
|
# Called when the connection is unbinded from the socket
|
118
151
|
# and can no longer be used to process requests.
|
119
152
|
def unbind
|
153
|
+
@request.async_close.succeed if @request.async_close
|
154
|
+
@response.body.fail if @response.body.respond_to?(:fail)
|
120
155
|
@backend.connection_finished(self)
|
121
156
|
end
|
122
157
|
|
@@ -161,6 +196,7 @@ module Thin
|
|
161
196
|
private
|
162
197
|
def need_content_length?(result)
|
163
198
|
status, headers, body = result
|
199
|
+
return false if status == -1
|
164
200
|
return false if headers.has_key?(CONTENT_LENGTH)
|
165
201
|
return false if (100..199).include?(status) || status == 204 || status == 304
|
166
202
|
return false if headers.has_key?(TRANSFER_ENCODING) && headers[TRANSFER_ENCODING] =~ CHUNKED_REGEXP
|