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.

@@ -83,7 +83,7 @@
83
83
  /** Data **/
84
84
  %% write data;
85
85
 
86
- int http_parser_init(http_parser *parser) {
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 http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off) {
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 http_parser_finish(http_parser *parser)
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 (http_parser_has_error(parser) ) {
142
+ if (thin_http_parser_has_error(parser) ) {
145
143
  return -1;
146
- } else if (http_parser_is_finished(parser) ) {
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 http_parser_has_error(http_parser *parser) {
151
+ int thin_http_parser_has_error(http_parser *parser) {
154
152
  return parser->cs == http_parser_error;
155
153
  }
156
154
 
157
- int http_parser_is_finished(http_parser *parser) {
155
+ int thin_http_parser_is_finished(http_parser *parser) {
158
156
  return parser->cs == http_parser_first_final;
159
157
  }
@@ -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 = ch + RSTRING_LEN(f); ch < end; ch++) {
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 HttpParser_free(void *data) {
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 HttpParser_alloc(VALUE klass)
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
- http_parser_init(hp);
240
+ thin_http_parser_init(hp);
241
241
 
242
- obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
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 HttpParser_init(VALUE self)
254
+ VALUE Thin_HttpParser_init(VALUE self)
255
255
  {
256
256
  http_parser *http = NULL;
257
257
  DATA_GET(self, http_parser, http);
258
- http_parser_init(http);
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 HttpParser_reset(VALUE self)
271
+ VALUE Thin_HttpParser_reset(VALUE self)
272
272
  {
273
273
  http_parser *http = NULL;
274
274
  DATA_GET(self, http_parser, http);
275
- http_parser_init(http);
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 HttpParser_finish(VALUE self)
288
+ VALUE Thin_HttpParser_finish(VALUE self)
289
289
  {
290
290
  http_parser *http = NULL;
291
291
  DATA_GET(self, http_parser, http);
292
- http_parser_finish(http);
292
+ thin_http_parser_finish(http);
293
293
 
294
- return http_parser_is_finished(http) ? Qtrue : Qfalse;
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 HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
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
- http_parser_execute(http, dptr, dlen, from);
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(http_parser_has_error(http)) {
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 HttpParser_has_error(VALUE self)
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 http_parser_has_error(http) ? Qtrue : Qfalse;
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 HttpParser_is_finished(VALUE self)
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 http_parser_is_finished(http) ? Qtrue : Qfalse;
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 HttpParser_nread(VALUE self)
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, HttpParser_alloc);
426
- rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
427
- rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
428
- rb_define_method(cHttpParser, "finish", HttpParser_finish,0);
429
- rb_define_method(cHttpParser, "execute", HttpParser_execute,3);
430
- rb_define_method(cHttpParser, "error?", HttpParser_has_error,0);
431
- rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0);
432
- rb_define_method(cHttpParser, "nread", HttpParser_nread,0);
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
  }
@@ -22,7 +22,13 @@ module Rack
22
22
 
23
23
  load_application
24
24
 
25
- @file_server = Rack::File.new(::File.join(RAILS_ROOT, "public"))
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
- ActionController::AbstractRequest.relative_url_root = @prefix if @prefix
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(@file_server.root, Utils.unescape(path))
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 serve_file(env)
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 serve_file(env)
64
+ return @file_app.call(env)
70
65
  end
71
66
  end
72
67
 
73
68
  # No static file, let Rails handle it
74
- serve_rails(env)
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)
@@ -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
@@ -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 << "--#{name}"
40
+ TrueClass then args << "--#{option_name}"
40
41
  when FalseClass
41
- when Array then value.each { |v| args << "--#{name}=#{v.inspect}" }
42
- else args << "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
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
@@ -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
- # Process the request calling the Rack adapter
63
- @app.call(@request.env)
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
- terminate_request
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
- @request.close rescue nil
110
- @response.close rescue nil
111
-
112
- # Prepare the connection for another request if the client
113
- # supports HTTP pipelining (persistent connection).
114
- post_init if persistent?
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