puma 3.11.4 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1717 -432
  3. data/LICENSE +23 -20
  4. data/README.md +190 -64
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +31 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +95 -120
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +61 -3
  30. data/ext/puma_http11/http11_parser.c +106 -118
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +22 -38
  33. data/ext/puma_http11/http11_parser.rl +6 -4
  34. data/ext/puma_http11/http11_parser_common.rl +6 -6
  35. data/ext/puma_http11/mini_ssl.c +376 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +250 -88
  40. data/ext/puma_http11/puma_http11.c +49 -57
  41. data/lib/puma/app/status.rb +71 -49
  42. data/lib/puma/binder.rb +243 -148
  43. data/lib/puma/cli.rb +50 -36
  44. data/lib/puma/client.rb +373 -233
  45. data/lib/puma/cluster/worker.rb +175 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +268 -235
  48. data/lib/puma/commonlogger.rb +4 -2
  49. data/lib/puma/configuration.rb +116 -88
  50. data/lib/puma/const.rb +49 -30
  51. data/lib/puma/control_cli.rb +123 -76
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +685 -135
  54. data/lib/puma/error_logger.rb +112 -0
  55. data/lib/puma/events.rb +17 -111
  56. data/lib/puma/io_buffer.rb +44 -5
  57. data/lib/puma/jruby_restart.rb +4 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +196 -130
  61. data/lib/puma/log_writer.rb +137 -0
  62. data/lib/puma/minissl/context_builder.rb +92 -0
  63. data/lib/puma/minissl.rb +249 -69
  64. data/lib/puma/null_io.rb +20 -1
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +9 -13
  67. data/lib/puma/rack/builder.rb +8 -9
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +3 -1
  70. data/lib/puma/reactor.rb +90 -187
  71. data/lib/puma/request.rb +644 -0
  72. data/lib/puma/runner.rb +94 -71
  73. data/lib/puma/server.rb +337 -715
  74. data/lib/puma/single.rb +27 -72
  75. data/lib/puma/state_file.rb +46 -7
  76. data/lib/puma/systemd.rb +47 -0
  77. data/lib/puma/thread_pool.rb +184 -93
  78. data/lib/puma/util.rb +23 -10
  79. data/lib/puma.rb +60 -3
  80. data/lib/rack/handler/puma.rb +17 -15
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +53 -33
  84. data/ext/puma_http11/io_buffer.c +0 -155
  85. data/lib/puma/accept_nonblock.rb +0 -23
  86. data/lib/puma/compat.rb +0 -14
  87. data/lib/puma/convenient.rb +0 -23
  88. data/lib/puma/daemon_ext.rb +0 -31
  89. data/lib/puma/delegation.rb +0 -11
  90. data/lib/puma/java_io_buffer.rb +0 -45
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -39
  93. data/tools/jungle/README.md +0 -19
  94. data/tools/jungle/init.d/README.md +0 -61
  95. data/tools/jungle/init.d/puma +0 -421
  96. data/tools/jungle/init.d/run-puma +0 -18
  97. data/tools/jungle/upstart/README.md +0 -61
  98. data/tools/jungle/upstart/puma-manager.conf +0 -31
  99. data/tools/jungle/upstart/puma.conf +0 -69
@@ -10,6 +10,7 @@
10
10
  #include "ext_help.h"
11
11
  #include <assert.h>
12
12
  #include <string.h>
13
+ #include <ctype.h>
13
14
  #include "http11_parser.h"
14
15
 
15
16
  #ifndef MANAGED_STRINGS
@@ -35,11 +36,13 @@ static VALUE global_request_method;
35
36
  static VALUE global_request_uri;
36
37
  static VALUE global_fragment;
37
38
  static VALUE global_query_string;
38
- static VALUE global_http_version;
39
+ static VALUE global_server_protocol;
39
40
  static VALUE global_request_path;
40
41
 
41
42
  /** Defines common length and error messages for input length validation. */
42
- #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length (was %d)"
43
+ #define QUOTE(s) #s
44
+ #define EXPAND_MAX_LENGTH_VALUE(s) QUOTE(s)
45
+ #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " EXPAND_MAX_LENGTH_VALUE(length) " allowed length (was %d)"
43
46
 
44
47
  /** Validates the max length of given input and throws an HttpParserError exception if over. */
45
48
  #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
@@ -49,12 +52,24 @@ static VALUE global_request_path;
49
52
 
50
53
 
51
54
  /* Defines the maximum allowed lengths for various input elements.*/
55
+ #ifndef PUMA_REQUEST_URI_MAX_LENGTH
56
+ #define PUMA_REQUEST_URI_MAX_LENGTH (1024 * 12)
57
+ #endif
58
+
59
+ #ifndef PUMA_REQUEST_PATH_MAX_LENGTH
60
+ #define PUMA_REQUEST_PATH_MAX_LENGTH (8192)
61
+ #endif
62
+
63
+ #ifndef PUMA_QUERY_STRING_MAX_LENGTH
64
+ #define PUMA_QUERY_STRING_MAX_LENGTH (1024 * 10)
65
+ #endif
66
+
52
67
  DEF_MAX_LENGTH(FIELD_NAME, 256);
53
68
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
54
- DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
69
+ DEF_MAX_LENGTH(REQUEST_URI, PUMA_REQUEST_URI_MAX_LENGTH);
55
70
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
56
- DEF_MAX_LENGTH(REQUEST_PATH, 2048);
57
- DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
71
+ DEF_MAX_LENGTH(REQUEST_PATH, PUMA_REQUEST_PATH_MAX_LENGTH);
72
+ DEF_MAX_LENGTH(QUERY_STRING, PUMA_QUERY_STRING_MAX_LENGTH);
58
73
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
59
74
 
60
75
  struct common_field {
@@ -111,21 +126,6 @@ static struct common_field common_http_fields[] = {
111
126
  # undef f
112
127
  };
113
128
 
114
- /*
115
- * qsort(3) and bsearch(3) improve average performance slightly, but may
116
- * not be worth it for lack of portability to certain platforms...
117
- */
118
- #if defined(HAVE_QSORT_BSEARCH)
119
- /* sort by length, then by name if there's a tie */
120
- static int common_field_cmp(const void *a, const void *b)
121
- {
122
- struct common_field *cfa = (struct common_field *)a;
123
- struct common_field *cfb = (struct common_field *)b;
124
- signed long diff = cfa->len - cfb->len;
125
- return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
126
- }
127
- #endif /* HAVE_QSORT_BSEARCH */
128
-
129
129
  static void init_common_fields(void)
130
130
  {
131
131
  unsigned i;
@@ -142,28 +142,10 @@ static void init_common_fields(void)
142
142
  }
143
143
  rb_global_variable(&cf->value);
144
144
  }
145
-
146
- #if defined(HAVE_QSORT_BSEARCH)
147
- qsort(common_http_fields,
148
- ARRAY_SIZE(common_http_fields),
149
- sizeof(struct common_field),
150
- common_field_cmp);
151
- #endif /* HAVE_QSORT_BSEARCH */
152
145
  }
153
146
 
154
147
  static VALUE find_common_field_value(const char *field, size_t flen)
155
148
  {
156
- #if defined(HAVE_QSORT_BSEARCH)
157
- struct common_field key;
158
- struct common_field *found;
159
- key.name = field;
160
- key.len = (signed long)flen;
161
- found = (struct common_field *)bsearch(&key, common_http_fields,
162
- ARRAY_SIZE(common_http_fields),
163
- sizeof(struct common_field),
164
- common_field_cmp);
165
- return found ? found->value : Qnil;
166
- #else /* !HAVE_QSORT_BSEARCH */
167
149
  unsigned i;
168
150
  struct common_field *cf = common_http_fields;
169
151
  for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
@@ -171,7 +153,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
171
153
  return cf->value;
172
154
  }
173
155
  return Qnil;
174
- #endif /* !HAVE_QSORT_BSEARCH */
175
156
  }
176
157
 
177
158
  void http_field(puma_parser* hp, const char *field, size_t flen,
@@ -200,6 +181,8 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
200
181
  f = rb_str_new(hp->buf, new_size);
201
182
  }
202
183
 
184
+ while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
185
+
203
186
  /* check for duplicate header */
204
187
  v = rb_hash_aref(hp->request, f);
205
188
 
@@ -261,10 +244,10 @@ void query_string(puma_parser* hp, const char *at, size_t length)
261
244
  rb_hash_aset(hp->request, global_query_string, val);
262
245
  }
263
246
 
264
- void http_version(puma_parser* hp, const char *at, size_t length)
247
+ void server_protocol(puma_parser* hp, const char *at, size_t length)
265
248
  {
266
249
  VALUE val = rb_str_new(at, length);
267
- rb_hash_aset(hp->request, global_http_version, val);
250
+ rb_hash_aset(hp->request, global_server_protocol, val);
268
251
  }
269
252
 
270
253
  /** Finalizes the request header to have a bunch of stuff that's
@@ -284,11 +267,18 @@ void HttpParser_free(void *data) {
284
267
  }
285
268
  }
286
269
 
287
- void HttpParser_mark(puma_parser* hp) {
270
+ void HttpParser_mark(void *ptr) {
271
+ puma_parser *hp = ptr;
288
272
  if(hp->request) rb_gc_mark(hp->request);
289
273
  if(hp->body) rb_gc_mark(hp->body);
290
274
  }
291
275
 
276
+ const rb_data_type_t HttpParser_data_type = {
277
+ "HttpParser",
278
+ { HttpParser_mark, HttpParser_free, 0 },
279
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
280
+ };
281
+
292
282
  VALUE HttpParser_alloc(VALUE klass)
293
283
  {
294
284
  puma_parser *hp = ALLOC_N(puma_parser, 1);
@@ -299,13 +289,13 @@ VALUE HttpParser_alloc(VALUE klass)
299
289
  hp->fragment = fragment;
300
290
  hp->request_path = request_path;
301
291
  hp->query_string = query_string;
302
- hp->http_version = http_version;
292
+ hp->server_protocol = server_protocol;
303
293
  hp->header_done = header_done;
304
294
  hp->request = Qnil;
305
295
 
306
296
  puma_parser_init(hp);
307
297
 
308
- return Data_Wrap_Struct(klass, HttpParser_mark, HttpParser_free, hp);
298
+ return TypedData_Wrap_Struct(klass, &HttpParser_data_type, hp);
309
299
  }
310
300
 
311
301
  /**
@@ -317,7 +307,7 @@ VALUE HttpParser_alloc(VALUE klass)
317
307
  VALUE HttpParser_init(VALUE self)
318
308
  {
319
309
  puma_parser *http = NULL;
320
- DATA_GET(self, puma_parser, http);
310
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
321
311
  puma_parser_init(http);
322
312
 
323
313
  return self;
@@ -334,7 +324,7 @@ VALUE HttpParser_init(VALUE self)
334
324
  VALUE HttpParser_reset(VALUE self)
335
325
  {
336
326
  puma_parser *http = NULL;
337
- DATA_GET(self, puma_parser, http);
327
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
338
328
  puma_parser_init(http);
339
329
 
340
330
  return Qnil;
@@ -351,7 +341,7 @@ VALUE HttpParser_reset(VALUE self)
351
341
  VALUE HttpParser_finish(VALUE self)
352
342
  {
353
343
  puma_parser *http = NULL;
354
- DATA_GET(self, puma_parser, http);
344
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
355
345
  puma_parser_finish(http);
356
346
 
357
347
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
@@ -382,7 +372,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
382
372
  char *dptr = NULL;
383
373
  long dlen = 0;
384
374
 
385
- DATA_GET(self, puma_parser, http);
375
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
386
376
 
387
377
  from = FIX2INT(start);
388
378
  dptr = rb_extract_chars(data, &dlen);
@@ -398,7 +388,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
398
388
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
399
389
 
400
390
  if(puma_parser_has_error(http)) {
401
- rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
391
+ rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
402
392
  } else {
403
393
  return INT2FIX(puma_parser_nread(http));
404
394
  }
@@ -416,7 +406,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
416
406
  VALUE HttpParser_has_error(VALUE self)
417
407
  {
418
408
  puma_parser *http = NULL;
419
- DATA_GET(self, puma_parser, http);
409
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
420
410
 
421
411
  return puma_parser_has_error(http) ? Qtrue : Qfalse;
422
412
  }
@@ -431,7 +421,7 @@ VALUE HttpParser_has_error(VALUE self)
431
421
  VALUE HttpParser_is_finished(VALUE self)
432
422
  {
433
423
  puma_parser *http = NULL;
434
- DATA_GET(self, puma_parser, http);
424
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
435
425
 
436
426
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
437
427
  }
@@ -447,7 +437,7 @@ VALUE HttpParser_is_finished(VALUE self)
447
437
  VALUE HttpParser_nread(VALUE self)
448
438
  {
449
439
  puma_parser *http = NULL;
450
- DATA_GET(self, puma_parser, http);
440
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
451
441
 
452
442
  return INT2FIX(http->nread);
453
443
  }
@@ -460,15 +450,16 @@ VALUE HttpParser_nread(VALUE self)
460
450
  */
461
451
  VALUE HttpParser_body(VALUE self) {
462
452
  puma_parser *http = NULL;
463
- DATA_GET(self, puma_parser, http);
453
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
464
454
 
465
455
  return http->body;
466
456
  }
467
457
 
468
- void Init_io_buffer(VALUE puma);
458
+ #ifdef HAVE_OPENSSL_BIO_H
469
459
  void Init_mini_ssl(VALUE mod);
460
+ #endif
470
461
 
471
- void Init_puma_http11()
462
+ void Init_puma_http11(void)
472
463
  {
473
464
 
474
465
  VALUE mPuma = rb_define_module("Puma");
@@ -478,7 +469,7 @@ void Init_puma_http11()
478
469
  DEF_GLOBAL(request_uri, "REQUEST_URI");
479
470
  DEF_GLOBAL(fragment, "FRAGMENT");
480
471
  DEF_GLOBAL(query_string, "QUERY_STRING");
481
- DEF_GLOBAL(http_version, "HTTP_VERSION");
472
+ DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
482
473
  DEF_GLOBAL(request_path, "REQUEST_PATH");
483
474
 
484
475
  eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
@@ -495,6 +486,7 @@ void Init_puma_http11()
495
486
  rb_define_method(cHttpParser, "body", HttpParser_body, 0);
496
487
  init_common_fields();
497
488
 
498
- Init_io_buffer(mPuma);
489
+ #ifdef HAVE_OPENSSL_BIO_H
499
490
  Init_mini_ssl(mPuma);
491
+ #endif
500
492
  }
@@ -1,73 +1,95 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../json_serialization'
3
+
1
4
  module Puma
2
5
  module App
6
+ # Check out {#call}'s source code to see what actions this web application
7
+ # can respond to.
3
8
  class Status
4
- def initialize(cli)
5
- @cli = cli
6
- @auth_token = nil
7
- end
8
9
  OK_STATUS = '{ "status": "ok" }'.freeze
9
10
 
10
- attr_accessor :auth_token
11
-
12
- def authenticate(env)
13
- return true unless @auth_token
14
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
15
- end
16
-
17
- def rack_response(status, body, content_type='application/json')
18
- headers = {
19
- 'Content-Type' => content_type,
20
- 'Content-Length' => body.bytesize.to_s
21
- }
22
-
23
- [status, headers, [body]]
11
+ # @param launcher [::Puma::Launcher]
12
+ # @param token [String, nil] the token used for authentication
13
+ #
14
+ def initialize(launcher, token = nil)
15
+ @launcher = launcher
16
+ @auth_token = token
24
17
  end
25
18
 
19
+ # most commands call methods in `::Puma::Launcher` based on command in
20
+ # `env['PATH_INFO']`
26
21
  def call(env)
27
22
  unless authenticate(env)
28
23
  return rack_response(403, 'Invalid auth token', 'text/plain')
29
24
  end
30
25
 
31
- case env['PATH_INFO']
32
- when /\/stop$/
33
- @cli.stop
34
- return rack_response(200, OK_STATUS)
26
+ # resp_type is processed by following case statement, return
27
+ # is a number (status) or a string used as the body of a 200 response
28
+ resp_type =
29
+ case env['PATH_INFO'][/\/([^\/]+)$/, 1]
30
+ when 'stop'
31
+ @launcher.stop ; 200
35
32
 
36
- when /\/halt$/
37
- @cli.halt
38
- return rack_response(200, OK_STATUS)
33
+ when 'halt'
34
+ @launcher.halt ; 200
39
35
 
40
- when /\/restart$/
41
- @cli.restart
42
- return rack_response(200, OK_STATUS)
36
+ when 'restart'
37
+ @launcher.restart ; 200
43
38
 
44
- when /\/phased-restart$/
45
- if !@cli.phased_restart
46
- return rack_response(404, '{ "error": "phased restart not available" }')
47
- else
48
- return rack_response(200, OK_STATUS)
49
- end
39
+ when 'phased-restart'
40
+ @launcher.phased_restart ? 200 : 404
41
+
42
+ when 'refork'
43
+ @launcher.refork ? 200 : 404
44
+
45
+ when 'reload-worker-directory'
46
+ @launcher.send(:reload_worker_directory) ? 200 : 404
47
+
48
+ when 'gc'
49
+ GC.start ; 200
50
+
51
+ when 'gc-stats'
52
+ Puma::JSONSerialization.generate GC.stat
53
+
54
+ when 'stats'
55
+ Puma::JSONSerialization.generate @launcher.stats
56
+
57
+ when 'thread-backtraces'
58
+ backtraces = []
59
+ @launcher.thread_status do |name, backtrace|
60
+ backtraces << { name: name, backtrace: backtrace }
61
+ end
62
+ Puma::JSONSerialization.generate backtraces
50
63
 
51
- when /\/reload-worker-directory$/
52
- if !@cli.send(:reload_worker_directory)
53
- return rack_response(404, '{ "error": "reload_worker_directory not available" }')
54
64
  else
55
- return rack_response(200, OK_STATUS)
65
+ return rack_response(404, "Unsupported action", 'text/plain')
56
66
  end
57
67
 
58
- when /\/gc$/
59
- GC.start
60
- return rack_response(200, OK_STATUS)
68
+ case resp_type
69
+ when String
70
+ rack_response 200, resp_type
71
+ when 200
72
+ rack_response 200, OK_STATUS
73
+ when 404
74
+ str = env['PATH_INFO'][/\/(\S+)/, 1].tr '-', '_'
75
+ rack_response 404, "{ \"error\": \"#{str} not available\" }"
76
+ end
77
+ end
61
78
 
62
- when /\/gc-stats$/
63
- json = "{" + GC.stat.map { |k, v| "\"#{k}\": #{v}" }.join(",") + "}"
64
- return rack_response(200, json)
79
+ private
65
80
 
66
- when /\/stats$/
67
- return rack_response(200, @cli.stats)
68
- else
69
- rack_response 404, "Unsupported action", 'text/plain'
70
- end
81
+ def authenticate(env)
82
+ return true unless @auth_token
83
+ env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
84
+ end
85
+
86
+ def rack_response(status, body, content_type='application/json')
87
+ headers = {
88
+ 'content-type' => content_type,
89
+ 'content-length' => body.bytesize.to_s
90
+ }
91
+
92
+ [status, headers, [body]]
71
93
  end
72
94
  end
73
95
  end