unicorn 2.0.1 → 3.0.0pre1

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/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v2.0.1.GIT
4
+ DEF_VER=v3.0.0pre1.GIT
5
5
 
6
6
  LF='
7
7
  '
data/TODO CHANGED
@@ -7,6 +7,3 @@
7
7
  * scalability to >= 1024 worker processes for crazy NUMA systems
8
8
 
9
9
  * Rack 2.x support (when Rack 2.x exists)
10
-
11
- * allow disabling "rack.input" rewindability for performance
12
- (but violate the Rack 1.x SPEC)
@@ -26,8 +26,6 @@ static VALUE g_http;
26
26
  static VALUE g_http_09;
27
27
  static VALUE g_http_10;
28
28
  static VALUE g_http_11;
29
- static VALUE g_GET;
30
- static VALUE g_HEAD;
31
29
 
32
30
  /** Defines common length and error messages for input length validation. */
33
31
  #define DEF_MAX_LENGTH(N, length) \
@@ -82,8 +80,6 @@ static void init_globals(void)
82
80
  DEF_GLOBAL(http_11, "HTTP/1.1");
83
81
  DEF_GLOBAL(http_10, "HTTP/1.0");
84
82
  DEF_GLOBAL(http_09, "HTTP/0.9");
85
- DEF_GLOBAL(GET, "GET");
86
- DEF_GLOBAL(HEAD, "HEAD");
87
83
  }
88
84
 
89
85
  #undef DEF_GLOBAL
@@ -18,12 +18,12 @@
18
18
  #define UH_FL_HASTRAILER 0x8
19
19
  #define UH_FL_INTRAILER 0x10
20
20
  #define UH_FL_INCHUNK 0x20
21
- #define UH_FL_KAMETHOD 0x40
21
+ #define UH_FL_REQEOF 0x40
22
22
  #define UH_FL_KAVERSION 0x80
23
23
  #define UH_FL_HASHEADER 0x100
24
24
 
25
- /* both of these flags need to be set for keepalive to be supported */
26
- #define UH_FL_KEEPALIVE (UH_FL_KAMETHOD | UH_FL_KAVERSION)
25
+ /* all of these flags need to be set for keepalive to be supported */
26
+ #define UH_FL_KEEPALIVE (UH_FL_KAVERSION | UH_FL_REQEOF | UH_FL_HASHEADER)
27
27
 
28
28
  /* keep this small for Rainbows! since every client has one */
29
29
  struct http_parser {
@@ -79,46 +79,29 @@ static void parser_error(const char *msg)
79
79
  */
80
80
  static void hp_keepalive_connection(struct http_parser *hp, VALUE val)
81
81
  {
82
- /* REQUEST_METHOD is always set before any headers */
83
- if (HP_FL_TEST(hp, KAMETHOD)) {
84
- if (STR_CSTR_CASE_EQ(val, "keep-alive")) {
85
- /* basically have HTTP/1.0 masquerade as HTTP/1.1+ */
86
- HP_FL_SET(hp, KAVERSION);
87
- } else if (STR_CSTR_CASE_EQ(val, "close")) {
88
- /*
89
- * it doesn't matter what HTTP version or request method we have,
90
- * if a client says "Connection: close", we disable keepalive
91
- */
92
- HP_FL_UNSET(hp, KEEPALIVE);
93
- } else {
94
- /*
95
- * client could've sent anything, ignore it for now. Maybe
96
- * "HP_FL_UNSET(hp, KEEPALIVE);" just in case?
97
- * Raising an exception might be too mean...
98
- */
99
- }
82
+ if (STR_CSTR_CASE_EQ(val, "keep-alive")) {
83
+ /* basically have HTTP/1.0 masquerade as HTTP/1.1+ */
84
+ HP_FL_SET(hp, KAVERSION);
85
+ } else if (STR_CSTR_CASE_EQ(val, "close")) {
86
+ /*
87
+ * it doesn't matter what HTTP version or request method we have,
88
+ * if a client says "Connection: close", we disable keepalive
89
+ */
90
+ HP_FL_UNSET(hp, KAVERSION);
91
+ } else {
92
+ /*
93
+ * client could've sent anything, ignore it for now. Maybe
94
+ * "HP_FL_UNSET(hp, KAVERSION);" just in case?
95
+ * Raising an exception might be too mean...
96
+ */
100
97
  }
101
98
  }
102
99
 
103
100
  static void
104
101
  request_method(struct http_parser *hp, const char *ptr, size_t len)
105
102
  {
106
- VALUE v;
103
+ VALUE v = rb_str_new(ptr, len);
107
104
 
108
- /*
109
- * we only support keepalive for GET and HEAD requests for now other
110
- * methods are too rarely seen to be worth optimizing. POST is unsafe
111
- * since some clients send extra bytes after POST bodies.
112
- */
113
- if (CONST_MEM_EQ("GET", ptr, len)) {
114
- HP_FL_SET(hp, KAMETHOD);
115
- v = g_GET;
116
- } else if (CONST_MEM_EQ("HEAD", ptr, len)) {
117
- HP_FL_SET(hp, KAMETHOD);
118
- v = g_HEAD;
119
- } else {
120
- v = rb_str_new(ptr, len);
121
- }
122
105
  rb_hash_aset(hp->env, g_request_method, v);
123
106
  }
124
107
 
@@ -206,7 +189,8 @@ static void write_value(struct http_parser *hp,
206
189
  hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
207
190
  if (hp->len.content < 0)
208
191
  parser_error("invalid Content-Length");
209
- HP_FL_SET(hp, HASBODY);
192
+ if (hp->len.content != 0)
193
+ HP_FL_SET(hp, HASBODY);
210
194
  hp_invalid_if_trailer(hp);
211
195
  } else if (f == g_http_transfer_encoding) {
212
196
  if (STR_CSTR_CASE_EQ(v, "chunked")) {
@@ -305,6 +289,7 @@ static void write_value(struct http_parser *hp,
305
289
  if (HP_FL_TEST(hp, CHUNKED))
306
290
  cs = http_parser_en_ChunkedBody;
307
291
  } else {
292
+ HP_FL_SET(hp, REQEOF);
308
293
  assert(!HP_FL_TEST(hp, CHUNKED) && "chunked encoding without body!");
309
294
  }
310
295
  /*
@@ -559,6 +544,8 @@ static VALUE HttpParser_parse(VALUE self)
559
544
  hp->cs == http_parser_en_ChunkedBody) {
560
545
  advance_str(data, hp->offset + 1);
561
546
  hp->offset = 0;
547
+ if (HP_FL_TEST(hp, INTRAILER))
548
+ HP_FL_SET(hp, REQEOF);
562
549
 
563
550
  return hp->env;
564
551
  }
@@ -630,6 +617,25 @@ static VALUE HttpParser_keepalive(VALUE self)
630
617
  return HP_FL_ALL(hp, KEEPALIVE) ? Qtrue : Qfalse;
631
618
  }
632
619
 
620
+ /**
621
+ * call-seq:
622
+ * parser.next? => true or false
623
+ *
624
+ * Exactly like HttpParser#keepalive?, except it will reset the internal
625
+ * parser state if it returns true.
626
+ */
627
+ static VALUE HttpParser_next(VALUE self)
628
+ {
629
+ struct http_parser *hp = data_get(self);
630
+
631
+ if (HP_FL_ALL(hp, KEEPALIVE)) {
632
+ http_parser_init(hp);
633
+ rb_funcall(hp->env, id_clear, 0);
634
+ return Qtrue;
635
+ }
636
+ return Qfalse;
637
+ }
638
+
633
639
  /**
634
640
  * call-seq:
635
641
  * parser.headers? => true or false
@@ -708,10 +714,13 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
708
714
  if (hp->len.content > 0) {
709
715
  long nr = MIN(dlen, hp->len.content);
710
716
 
717
+ hp->buf = data;
711
718
  memcpy(RSTRING_PTR(buf), dptr, nr);
712
719
  hp->len.content -= nr;
713
- if (hp->len.content == 0)
720
+ if (hp->len.content == 0) {
721
+ HP_FL_SET(hp, REQEOF);
714
722
  hp->cs = http_parser_first_final;
723
+ }
715
724
  advance_str(data, nr);
716
725
  rb_str_set_len(buf, nr);
717
726
  data = Qnil;
@@ -747,6 +756,7 @@ void Init_unicorn_http(void)
747
756
  rb_define_method(cHttpParser, "body_eof?", HttpParser_body_eof, 0);
748
757
  rb_define_method(cHttpParser, "keepalive?", HttpParser_keepalive, 0);
749
758
  rb_define_method(cHttpParser, "headers?", HttpParser_has_headers, 0);
759
+ rb_define_method(cHttpParser, "next?", HttpParser_next, 0);
750
760
  rb_define_method(cHttpParser, "buf", HttpParser_buf, 0);
751
761
  rb_define_method(cHttpParser, "env", HttpParser_env, 0);
752
762
 
data/lib/unicorn.rb CHANGED
@@ -73,6 +73,7 @@ class Unicorn::ClientShutdown < EOFError; end
73
73
 
74
74
  require 'unicorn/const'
75
75
  require 'unicorn/socket_helper'
76
+ require 'unicorn/stream_input'
76
77
  require 'unicorn/tee_input'
77
78
  require 'unicorn/http_request'
78
79
  require 'unicorn/configurator'
@@ -39,6 +39,7 @@ class Unicorn::Configurator
39
39
  },
40
40
  :pid => nil,
41
41
  :preload_app => false,
42
+ :rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1),
42
43
  }
43
44
  #:startdoc:
44
45
 
@@ -373,12 +374,22 @@ class Unicorn::Configurator
373
374
  # cause the master process to exit with an error.
374
375
 
375
376
  def preload_app(bool)
376
- case bool
377
- when TrueClass, FalseClass
378
- set[:preload_app] = bool
379
- else
380
- raise ArgumentError, "preload_app=#{bool.inspect} not a boolean"
381
- end
377
+ set_bool(:preload_app, bool)
378
+ end
379
+
380
+ # Toggles making <code>env["rack.input"]</code> rewindable.
381
+ # Disabling rewindability can improve performance by lowering
382
+ # I/O and memory usage for applications that accept uploads.
383
+ # Keep in mind that the Rack 1.x spec requires
384
+ # <code>env["rack.input"]</code> to be rewindable, so this allows
385
+ # intentionally violating the current Rack 1.x spec.
386
+ #
387
+ # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
388
+ # Rack conformance. When Rack 2.x is finalized, this will most
389
+ # likely default to +false+ while still conforming to the newer
390
+ # (less demanding) spec.
391
+ def rewindable_input(bool)
392
+ set_bool(:rewindable_input, bool)
382
393
  end
383
394
 
384
395
  # Allow redirecting $stderr to a given path. Unlike doing this from
@@ -469,6 +480,15 @@ private
469
480
  end
470
481
  end
471
482
 
483
+ def set_bool(var, bool) #:nodoc:
484
+ case bool
485
+ when true, false
486
+ set[var] = bool
487
+ else
488
+ raise ArgumentError, "#{var}=#{bool.inspect} not a boolean"
489
+ end
490
+ end
491
+
472
492
  def set_hook(var, my_proc, req_arity = 2) #:nodoc:
473
493
  case my_proc
474
494
  when Proc
data/lib/unicorn/const.rb CHANGED
@@ -7,8 +7,8 @@
7
7
  # improve things much compared to constants.
8
8
  module Unicorn::Const
9
9
 
10
- # The current version of Unicorn, currently 2.0.1
11
- UNICORN_VERSION = "2.0.1"
10
+ # The current version of Unicorn, currently 3.0.0pre1
11
+ UNICORN_VERSION = "3.0.0pre1"
12
12
 
13
13
  # default TCP listen host address (0.0.0.0, all interfaces)
14
14
  DEFAULT_HOST = "0.0.0.0"
@@ -25,7 +25,15 @@ class Unicorn::HttpParser
25
25
  # A frozen format for this is about 15% faster
26
26
  REMOTE_ADDR = 'REMOTE_ADDR'.freeze
27
27
  RACK_INPUT = 'rack.input'.freeze
28
- TeeInput = Unicorn::TeeInput
28
+ @@input_class = Unicorn::TeeInput
29
+
30
+ def self.input_class
31
+ @@input_class
32
+ end
33
+
34
+ def self.input_class=(klass)
35
+ @@input_class = klass
36
+ end
29
37
  # :startdoc:
30
38
 
31
39
  # Does the majority of the IO processing. It has been written in
@@ -63,7 +71,8 @@ class Unicorn::HttpParser
63
71
  buf << socket.kgio_read!(16384)
64
72
  end while parse.nil?
65
73
  end
66
- e[RACK_INPUT] = 0 == content_length ? NULL_IO : TeeInput.new(socket, self)
74
+ e[RACK_INPUT] = 0 == content_length ?
75
+ NULL_IO : @@input_class.new(socket, self)
67
76
  e.merge!(DEFAULTS)
68
77
  end
69
78
  end
@@ -355,6 +355,15 @@ class Unicorn::HttpServer
355
355
  kill_each_worker(:KILL)
356
356
  end
357
357
 
358
+ def rewindable_input
359
+ Unicorn::HttpRequest.input_class.method_defined?(:rewind)
360
+ end
361
+
362
+ def rewindable_input=(bool)
363
+ Unicorn::HttpRequest.input_class = bool ?
364
+ Unicorn::TeeInput : Unicorn::StreamInput
365
+ end
366
+
358
367
  private
359
368
 
360
369
  # wait for a signal hander to wake us up and then consume the pipe
@@ -491,7 +500,7 @@ class Unicorn::HttpServer
491
500
  msg = case e
492
501
  when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
493
502
  Unicorn::Const::ERROR_500_RESPONSE
494
- when Unicorn::HttpParserError # try to tell the client they're bad
503
+ when HttpParserError # try to tell the client they're bad
495
504
  Unicorn::Const::ERROR_400_RESPONSE
496
505
  else
497
506
  logger.error "Read error: #{e.inspect}"
@@ -20,7 +20,7 @@ class PrereadInput
20
20
  def call(env)
21
21
  buf = ""
22
22
  input = env["rack.input"]
23
- if buf = input.read(16384)
23
+ if input.respond_to?(:rewind)
24
24
  true while input.read(16384, buf)
25
25
  input.rewind
26
26
  end
@@ -0,0 +1,156 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # When processing uploads, Unicorn may expose a StreamInput object under
4
+ # "rack.input" of the Rack (2.x) environment.
5
+ class Unicorn::StreamInput
6
+ # The I/O chunk size (in +bytes+) for I/O operations where
7
+ # the size cannot be user-specified when a method is called.
8
+ # The default is 16 kilobytes.
9
+ @@io_chunk_size = Unicorn::Const::CHUNK_SIZE
10
+
11
+ # Initializes a new StreamInput object. You normally do not have to call
12
+ # this unless you are writing an HTTP server.
13
+ def initialize(socket, request)
14
+ @chunked = request.content_length.nil?
15
+ @socket = socket
16
+ @parser = request
17
+ @buf = request.buf
18
+ @rbuf = ''
19
+ @bytes_read = 0
20
+ filter_body(@rbuf, @buf) unless @buf.empty?
21
+ end
22
+
23
+ # :call-seq:
24
+ # ios.read([length [, buffer ]]) => string, buffer, or nil
25
+ #
26
+ # Reads at most length bytes from the I/O stream, or to the end of
27
+ # file if length is omitted or is nil. length must be a non-negative
28
+ # integer or nil. If the optional buffer argument is present, it
29
+ # must reference a String, which will receive the data.
30
+ #
31
+ # At end of file, it returns nil or '' depend on length.
32
+ # ios.read() and ios.read(nil) returns ''.
33
+ # ios.read(length [, buffer]) returns nil.
34
+ #
35
+ # If the Content-Length of the HTTP request is known (as is the common
36
+ # case for POST requests), then ios.read(length [, buffer]) will block
37
+ # until the specified length is read (or it is the last chunk).
38
+ # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
39
+ # ios.read(length [, buffer]) will return immediately if there is
40
+ # any data and only block when nothing is available (providing
41
+ # IO#readpartial semantics).
42
+ def read(*args)
43
+ length = args.shift
44
+ rv = args.shift || ''
45
+ if length.nil?
46
+ read_all(rv)
47
+ else
48
+ if length <= @rbuf.size
49
+ rv.replace(@rbuf.slice(0, length))
50
+ @rbuf.replace(@rbuf.slice(length, @rbuf.size) || '')
51
+ else
52
+ rv.replace(@rbuf)
53
+ length -= @rbuf.size
54
+ @rbuf.replace('')
55
+ until length == 0 || eof? || (rv.size > 0 && @chunked)
56
+ @socket.kgio_read(length, @buf) or eof!
57
+ filter_body(@rbuf, @buf)
58
+ rv << @rbuf
59
+ length -= @rbuf.size
60
+ @rbuf.replace('')
61
+ end
62
+ end
63
+ rv = nil if rv.empty?
64
+ end
65
+ rv
66
+ end
67
+
68
+ # :call-seq:
69
+ # ios.gets => string or nil
70
+ #
71
+ # Reads the next ``line'' from the I/O stream; lines are separated
72
+ # by the global record separator ($/, typically "\n"). A global
73
+ # record separator of nil reads the entire unread contents of ios.
74
+ # Returns nil if called at the end of file.
75
+ # This takes zero arguments for strict Rack::Lint compatibility,
76
+ # unlike IO#gets.
77
+ def gets
78
+ sep = $/
79
+ if sep.nil?
80
+ read_all(rv = '')
81
+ return rv.empty? ? nil : rv
82
+ end
83
+ re = /\A(.*?#{Regexp.escape(sep)})/
84
+
85
+ begin
86
+ @rbuf.gsub!(re, '') and return $1
87
+ if eof?
88
+ if @rbuf.empty?
89
+ return nil
90
+ else
91
+ rv = @rbuf.dup
92
+ @rbuf.replace('')
93
+ return rv
94
+ end
95
+ end
96
+ @socket.kgio_read(@@io_chunk_size, @buf) or eof!
97
+ filter_body(once = '', @buf)
98
+ @rbuf << once
99
+ end while true
100
+ end
101
+
102
+ # :call-seq:
103
+ # ios.each { |line| block } => ios
104
+ #
105
+ # Executes the block for every ``line'' in *ios*, where lines are
106
+ # separated by the global record separator ($/, typically "\n").
107
+ def each(&block)
108
+ while line = gets
109
+ yield line
110
+ end
111
+
112
+ self # Rack does not specify what the return value is here
113
+ end
114
+
115
+ private
116
+
117
+ def eof?
118
+ if @parser.body_eof?
119
+ until @parser.parse
120
+ once = @socket.kgio_read(@@io_chunk_size) or eof!
121
+ @buf << once
122
+ end
123
+ @socket = nil
124
+ true
125
+ else
126
+ false
127
+ end
128
+ end
129
+
130
+ def filter_body(dst, src)
131
+ rv = @parser.filter_body(dst, src)
132
+ @bytes_read += dst.size
133
+ rv
134
+ end
135
+
136
+ def read_all(dst)
137
+ dst.replace(@rbuf)
138
+ @socket or return
139
+ until eof?
140
+ @socket.kgio_read(@@io_chunk_size, @buf) or eof!
141
+ filter_body(@rbuf, @buf)
142
+ dst << @rbuf
143
+ end
144
+ ensure
145
+ @rbuf.replace('')
146
+ end
147
+
148
+ def eof!
149
+ # in case client only did a premature shutdown(SHUT_WR)
150
+ # we do support clients that shutdown(SHUT_WR) after the
151
+ # _entire_ request has been sent, and those will not have
152
+ # raised EOFError on us.
153
+ @socket.close if @socket
154
+ raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", []
155
+ end
156
+ end
@@ -11,34 +11,18 @@
11
11
  #
12
12
  # When processing uploads, Unicorn exposes a TeeInput object under
13
13
  # "rack.input" of the Rack environment.
14
- class Unicorn::TeeInput
15
- attr_accessor :tmp, :socket, :parser, :env, :buf, :len, :buf2
16
-
14
+ class Unicorn::TeeInput < Unicorn::StreamInput
17
15
  # The maximum size (in +bytes+) to buffer in memory before
18
16
  # resorting to a temporary file. Default is 112 kilobytes.
19
17
  @@client_body_buffer_size = Unicorn::Const::MAX_BODY
20
18
 
21
- # The I/O chunk size (in +bytes+) for I/O operations where
22
- # the size cannot be user-specified when a method is called.
23
- # The default is 16 kilobytes.
24
- @@io_chunk_size = Unicorn::Const::CHUNK_SIZE
25
-
26
19
  # Initializes a new TeeInput object. You normally do not have to call
27
20
  # this unless you are writing an HTTP server.
28
21
  def initialize(socket, request)
29
- @socket = socket
30
- @parser = request
31
- @buf = request.buf
32
- @env = request.env
33
22
  @len = request.content_length
23
+ super
34
24
  @tmp = @len && @len < @@client_body_buffer_size ?
35
25
  StringIO.new("") : Unicorn::TmpIO.new
36
- @buf2 = ""
37
- if @buf.size > 0
38
- @parser.filter_body(@buf2, @buf) and finalize_input
39
- @tmp.write(@buf2)
40
- @tmp.rewind
41
- end
42
26
  end
43
27
 
44
28
  # :call-seq:
@@ -59,15 +43,10 @@ class Unicorn::TeeInput
59
43
  # specified +length+ in a loop until it returns +nil+.
60
44
  def size
61
45
  @len and return @len
62
-
63
- if socket
64
- pos = @tmp.pos
65
- while tee(@@io_chunk_size, @buf2)
66
- end
67
- @tmp.seek(pos)
68
- end
69
-
70
- @len = @tmp.size
46
+ pos = @bytes_read
47
+ consume!
48
+ @tmp.pos = pos
49
+ @len = @bytes_read
71
50
  end
72
51
 
73
52
  # :call-seq:
@@ -90,24 +69,7 @@ class Unicorn::TeeInput
90
69
  # any data and only block when nothing is available (providing
91
70
  # IO#readpartial semantics).
92
71
  def read(*args)
93
- @socket or return @tmp.read(*args)
94
-
95
- length = args.shift
96
- if nil == length
97
- rv = @tmp.read || ""
98
- while tee(@@io_chunk_size, @buf2)
99
- rv << @buf2
100
- end
101
- rv
102
- else
103
- rv = args.shift || ""
104
- diff = @tmp.size - @tmp.pos
105
- if 0 == diff
106
- ensure_length(tee(length, rv), length)
107
- else
108
- ensure_length(@tmp.read(diff > length ? length : diff, rv), length)
109
- end
110
- end
72
+ @socket ? tee(super) : @tmp.read(*args)
111
73
  end
112
74
 
113
75
  # :call-seq:
@@ -120,43 +82,7 @@ class Unicorn::TeeInput
120
82
  # This takes zero arguments for strict Rack::Lint compatibility,
121
83
  # unlike IO#gets.
122
84
  def gets
123
- @socket or return @tmp.gets
124
- sep = $/ or return read
125
-
126
- orig_size = @tmp.size
127
- if @tmp.pos == orig_size
128
- tee(@@io_chunk_size, @buf2) or return nil
129
- @tmp.seek(orig_size)
130
- end
131
-
132
- sep_size = Rack::Utils.bytesize(sep)
133
- line = @tmp.gets # cannot be nil here since size > pos
134
- sep == line[-sep_size, sep_size] and return line
135
-
136
- # unlikely, if we got here, then @tmp is at EOF
137
- begin
138
- orig_size = @tmp.pos
139
- tee(@@io_chunk_size, @buf2) or break
140
- @tmp.seek(orig_size)
141
- line << @tmp.gets
142
- sep == line[-sep_size, sep_size] and return line
143
- # @tmp is at EOF again here, retry the loop
144
- end while true
145
-
146
- line
147
- end
148
-
149
- # :call-seq:
150
- # ios.each { |line| block } => ios
151
- #
152
- # Executes the block for every ``line'' in *ios*, where lines are
153
- # separated by the global record separator ($/, typically "\n").
154
- def each(&block)
155
- while line = gets
156
- yield line
157
- end
158
-
159
- self # Rack does not specify what the return value is here
85
+ @socket ? tee(super) : @tmp.gets
160
86
  end
161
87
 
162
88
  # :call-seq:
@@ -166,59 +92,24 @@ class Unicorn::TeeInput
166
92
  # the offset (zero) of the +ios+ pointer. Subsequent reads will
167
93
  # start from the beginning of the previously-buffered input.
168
94
  def rewind
95
+ return 0 if @bytes_read == 0
96
+ consume! if @socket
169
97
  @tmp.rewind # Rack does not specify what the return value is here
170
98
  end
171
99
 
172
100
  private
173
101
 
174
- # tees off a +length+ chunk of data from the input into the IO
175
- # backing store as well as returning it. +dst+ must be specified.
176
- # returns nil if reading from the input returns nil
177
- def tee(length, dst)
178
- unless @parser.body_eof?
179
- r = @socket.kgio_read(length, @buf) or eof!
180
- unless @parser.filter_body(dst, @buf)
181
- @tmp.write(dst)
182
- @tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
183
- return dst
184
- end
185
- end
186
- finalize_input
102
+ # consumes the stream of the socket
103
+ def consume!
104
+ junk = ""
105
+ nil while read(@@io_chunk_size, junk)
187
106
  end
188
107
 
189
- def finalize_input
190
- while @parser.trailers(@env, @buf).nil?
191
- r = @socket.kgio_read(@@io_chunk_size) or eof!
192
- @buf << r
108
+ def tee(buffer)
109
+ if buffer && (n = buffer.size) > 0
110
+ @tmp.write(buffer)
111
+ @tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
193
112
  end
194
- @socket = nil
195
- end
196
-
197
- # tee()s into +dst+ until it is of +length+ bytes (or until
198
- # we've reached the Content-Length of the request body).
199
- # Returns +dst+ (the exact object, not a duplicate)
200
- # To continue supporting applications that need near-real-time
201
- # streaming input bodies, this is a no-op for
202
- # "Transfer-Encoding: chunked" requests.
203
- def ensure_length(dst, length)
204
- # len is nil for chunked bodies, so we can't ensure length for those
205
- # since they could be streaming bidirectionally and we don't want to
206
- # block the caller in that case.
207
- return dst if dst.nil? || @len.nil?
208
-
209
- while dst.size < length && tee(length - dst.size, @buf2)
210
- dst << @buf2
211
- end
212
-
213
- dst
214
- end
215
-
216
- def eof!
217
- # in case client only did a premature shutdown(SHUT_WR)
218
- # we do support clients that shutdown(SHUT_WR) after the
219
- # _entire_ request has been sent, and those will not have
220
- # raised EOFError on us.
221
- @socket.close if @socket
222
- raise Unicorn::ClientShutdown, "bytes_read=#{@tmp.size}", []
113
+ buffer
223
114
  end
224
115
  end
@@ -0,0 +1,24 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ t_plan 4 "rewindable_input toggled to false"
4
+
5
+ t_begin "setup and start" && {
6
+ unicorn_setup
7
+ echo rewindable_input false >> $unicorn_config
8
+ unicorn -D -c $unicorn_config t0013.ru
9
+ unicorn_wait_start
10
+ }
11
+
12
+ t_begin "ensure worker is started" && {
13
+ test xOK = x$(curl -T t0013.ru -H Expect: -vsSf http://$listen/)
14
+ }
15
+
16
+ t_begin "killing succeeds" && {
17
+ kill $unicorn_pid
18
+ }
19
+
20
+ t_begin "check stderr" && {
21
+ check_stderr
22
+ }
23
+
24
+ t_done
@@ -0,0 +1,24 @@
1
+ #!/bin/sh
2
+ . ./test-lib.sh
3
+ t_plan 4 "rewindable_input toggled to true"
4
+
5
+ t_begin "setup and start" && {
6
+ unicorn_setup
7
+ echo rewindable_input true >> $unicorn_config
8
+ unicorn -D -c $unicorn_config t0014.ru
9
+ unicorn_wait_start
10
+ }
11
+
12
+ t_begin "ensure worker is started" && {
13
+ test xOK = x$(curl -T t0014.ru -sSf http://$listen/)
14
+ }
15
+
16
+ t_begin "killing succeeds" && {
17
+ kill $unicorn_pid
18
+ }
19
+
20
+ t_begin "check stderr" && {
21
+ check_stderr
22
+ }
23
+
24
+ t_done
@@ -76,12 +76,22 @@ class HttpParserTest < Test::Unit::TestCase
76
76
  assert parser.keepalive?
77
77
  end
78
78
 
79
- def test_connection_keep_alive_ka_bad_method
79
+ def test_connection_keep_alive_no_body
80
80
  parser = HttpParser.new
81
81
  req = {}
82
82
  tmp = "POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
83
83
  assert_equal req.object_id, parser.headers(req, tmp).object_id
84
- assert ! parser.keepalive?
84
+ assert parser.keepalive?
85
+ end
86
+
87
+ def test_connection_keep_alive_no_body_empty
88
+ parser = HttpParser.new
89
+ req = {}
90
+ tmp = "POST / HTTP/1.1\r\n" \
91
+ "Content-Length: 0\r\n" \
92
+ "Connection: keep-alive\r\n\r\n"
93
+ assert_equal req.object_id, parser.headers(req, tmp).object_id
94
+ assert parser.keepalive?
85
95
  end
86
96
 
87
97
  def test_connection_keep_alive_ka_bad_version
@@ -461,7 +471,7 @@ class HttpParserTest < Test::Unit::TestCase
461
471
  assert_equal 'page=1', req['QUERY_STRING']
462
472
  assert_equal "", s
463
473
  assert_equal m, req['REQUEST_METHOD']
464
- assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
474
+ assert parser.keepalive? # TODO: read HTTP/1.2 when it's final
465
475
  }
466
476
  end
467
477
 
@@ -11,6 +11,19 @@ class HttpParserNgTest < Test::Unit::TestCase
11
11
  @parser = HttpParser.new
12
12
  end
13
13
 
14
+ def test_default_keepalive_is_off
15
+ assert ! @parser.keepalive?
16
+ assert ! @parser.next?
17
+ assert_nothing_raised do
18
+ @parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
19
+ @parser.parse
20
+ end
21
+ assert @parser.keepalive?
22
+ @parser.reset
23
+ assert ! @parser.keepalive?
24
+ assert ! @parser.next?
25
+ end
26
+
14
27
  def test_identity_byte_headers
15
28
  req = {}
16
29
  str = "PUT / HTTP/1.1\r\n"
@@ -27,6 +40,12 @@ class HttpParserNgTest < Test::Unit::TestCase
27
40
  assert ! @parser.keepalive?
28
41
  assert @parser.headers?
29
42
  assert_equal 123, @parser.content_length
43
+ dst = ""
44
+ buf = '.' * 123
45
+ @parser.filter_body(dst, buf)
46
+ assert_equal '.' * 123, dst
47
+ assert_equal "", buf
48
+ assert @parser.keepalive?
30
49
  end
31
50
 
32
51
  def test_identity_step_headers
@@ -41,6 +60,12 @@ class HttpParserNgTest < Test::Unit::TestCase
41
60
  assert_equal 0, str.size
42
61
  assert ! @parser.keepalive?
43
62
  assert @parser.headers?
63
+ dst = ""
64
+ buf = '.' * 123
65
+ @parser.filter_body(dst, buf)
66
+ assert_equal '.' * 123, dst
67
+ assert_equal "", buf
68
+ assert @parser.keepalive?
44
69
  end
45
70
 
46
71
  def test_identity_oneshot_header
@@ -50,6 +75,12 @@ class HttpParserNgTest < Test::Unit::TestCase
50
75
  assert_equal '123', req['CONTENT_LENGTH']
51
76
  assert_equal 0, str.size
52
77
  assert ! @parser.keepalive?
78
+ assert @parser.headers?
79
+ dst = ""
80
+ buf = '.' * 123
81
+ @parser.filter_body(dst, buf)
82
+ assert_equal '.' * 123, dst
83
+ assert_equal "", buf
53
84
  end
54
85
 
55
86
  def test_identity_oneshot_header_with_body
@@ -67,7 +98,7 @@ class HttpParserNgTest < Test::Unit::TestCase
67
98
  assert_equal 0, str.size
68
99
  assert_equal tmp, body
69
100
  assert_equal "", @parser.filter_body(tmp, str)
70
- assert ! @parser.keepalive?
101
+ assert @parser.keepalive?
71
102
  end
72
103
 
73
104
  def test_identity_oneshot_header_with_body_partial
@@ -85,7 +116,7 @@ class HttpParserNgTest < Test::Unit::TestCase
85
116
  assert_nil rv
86
117
  assert_equal "", str
87
118
  assert_equal str.object_id, @parser.filter_body(tmp, str).object_id
88
- assert ! @parser.keepalive?
119
+ assert @parser.keepalive?
89
120
  end
90
121
 
91
122
  def test_identity_oneshot_header_with_body_slop
@@ -99,7 +130,7 @@ class HttpParserNgTest < Test::Unit::TestCase
99
130
  assert_equal "G", @parser.filter_body(tmp, str)
100
131
  assert_equal 1, tmp.size
101
132
  assert_equal "a", tmp
102
- assert ! @parser.keepalive?
133
+ assert @parser.keepalive?
103
134
  end
104
135
 
105
136
  def test_chunked
@@ -122,6 +153,10 @@ class HttpParserNgTest < Test::Unit::TestCase
122
153
  assert_equal rv.object_id, @parser.filter_body(tmp, rv).object_id
123
154
  assert_equal "PUT", rv
124
155
  assert ! @parser.keepalive?
156
+ rv << "TY: FOO\r\n\r\n"
157
+ assert_equal req, @parser.trailers(req, rv)
158
+ assert_equal "FOO", req["HTTP_PUTTY"]
159
+ assert @parser.keepalive?
125
160
  end
126
161
 
127
162
  def test_two_chunks
@@ -177,7 +212,7 @@ class HttpParserNgTest < Test::Unit::TestCase
177
212
  assert_equal req, @parser.trailers(req, moo = "\r\n")
178
213
  assert_equal "", moo
179
214
  assert @parser.body_eof?
180
- assert ! @parser.keepalive?
215
+ assert @parser.keepalive?
181
216
  end
182
217
 
183
218
  def test_two_chunks_oneshot
@@ -237,7 +272,7 @@ class HttpParserNgTest < Test::Unit::TestCase
237
272
  assert_nil @parser.trailers(req, str << "\r")
238
273
  assert_equal req, @parser.trailers(req, str << "\nGET / ")
239
274
  assert_equal "GET / ", str
240
- assert ! @parser.keepalive?
275
+ assert @parser.keepalive?
241
276
  end
242
277
 
243
278
  def test_trailers_slowly
@@ -297,14 +332,12 @@ class HttpParserNgTest < Test::Unit::TestCase
297
332
  assert_equal req, @parser.headers(req, str)
298
333
  assert_nil @parser.content_length
299
334
  assert_raise(HttpParserError) { @parser.filter_body('', str) }
300
- assert ! @parser.keepalive?
301
335
  end
302
336
 
303
337
  def test_overflow_content_length
304
338
  n = HttpParser::LENGTH_MAX + 1
305
339
  str = "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
306
340
  assert_raise(HttpParserError) { @parser.headers({}, str) }
307
- assert ! @parser.keepalive?
308
341
  end
309
342
 
310
343
  def test_bad_chunk
@@ -315,13 +348,11 @@ class HttpParserNgTest < Test::Unit::TestCase
315
348
  assert_equal req, @parser.headers(req, str)
316
349
  assert_nil @parser.content_length
317
350
  assert_raise(HttpParserError) { @parser.filter_body('', str) }
318
- assert ! @parser.keepalive?
319
351
  end
320
352
 
321
353
  def test_bad_content_length
322
354
  str = "PUT / HTTP/1.1\r\nContent-Length: 7ff\r\n\r\n"
323
355
  assert_raise(HttpParserError) { @parser.headers({}, str) }
324
- assert ! @parser.keepalive?
325
356
  end
326
357
 
327
358
  def test_bad_trailers
@@ -338,7 +369,6 @@ class HttpParserNgTest < Test::Unit::TestCase
338
369
  assert_equal '', str
339
370
  str << "Transfer-Encoding: identity\r\n\r\n"
340
371
  assert_raise(HttpParserError) { @parser.trailers(req, str) }
341
- assert ! @parser.keepalive?
342
372
  end
343
373
 
344
374
  def test_repeat_headers
@@ -492,8 +522,9 @@ class HttpParserNgTest < Test::Unit::TestCase
492
522
  env1 = @parser.parse.dup
493
523
  assert_equal expect, env1
494
524
  assert_equal str, @parser.buf
495
- assert @parser.keepalive?
496
- @parser.reset
525
+ assert ! @parser.env.empty?
526
+ assert @parser.next?
527
+ assert @parser.env.empty?
497
528
  env2 = @parser.parse.dup
498
529
  assert_equal expect, env2
499
530
  assert_equal "", @parser.buf
@@ -0,0 +1,143 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'test/unit'
4
+ require 'digest/sha1'
5
+ require 'unicorn'
6
+
7
+ class TestStreamInput < Test::Unit::TestCase
8
+ def setup
9
+ @rs = $/
10
+ @env = {}
11
+ @rd, @wr = Kgio::UNIXSocket.pair
12
+ @rd.sync = @wr.sync = true
13
+ @start_pid = $$
14
+ end
15
+
16
+ def teardown
17
+ return if $$ != @start_pid
18
+ $/ = @rs
19
+ @rd.close rescue nil
20
+ @wr.close rescue nil
21
+ Process.waitall
22
+ end
23
+
24
+ def test_read_small
25
+ r = init_request('hello')
26
+ si = Unicorn::StreamInput.new(@rd, r)
27
+ assert_equal 'hello', si.read
28
+ assert_equal '', si.read
29
+ assert_nil si.read(5)
30
+ assert_nil si.gets
31
+ end
32
+
33
+ def test_gets_oneliner
34
+ r = init_request('hello')
35
+ si = Unicorn::StreamInput.new(@rd, r)
36
+ assert_equal 'hello', si.gets
37
+ assert_nil si.gets
38
+ end
39
+
40
+ def test_gets_multiline
41
+ r = init_request("a\nb\n\n")
42
+ si = Unicorn::StreamInput.new(@rd, r)
43
+ assert_equal "a\n", si.gets
44
+ assert_equal "b\n", si.gets
45
+ assert_equal "\n", si.gets
46
+ assert_nil si.gets
47
+ end
48
+
49
+ def test_gets_empty_rs
50
+ $/ = nil
51
+ r = init_request("a\nb\n\n")
52
+ si = Unicorn::StreamInput.new(@rd, r)
53
+ assert_equal "a\nb\n\n", si.gets
54
+ assert_nil si.gets
55
+ end
56
+
57
+ def test_read_with_equal_len
58
+ r = init_request("abcde")
59
+ si = Unicorn::StreamInput.new(@rd, r)
60
+ assert_equal "abcde", si.read(5)
61
+ assert_nil si.read(5)
62
+ end
63
+
64
+ def test_big_body_multi
65
+ r = init_request('.', Unicorn::Const::MAX_BODY + 1)
66
+ si = Unicorn::StreamInput.new(@rd, r)
67
+ assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
68
+ assert ! @parser.body_eof?
69
+ nr = Unicorn::Const::MAX_BODY / 4
70
+ pid = fork {
71
+ @rd.close
72
+ nr.times { @wr.write('....') }
73
+ @wr.close
74
+ }
75
+ @wr.close
76
+ assert_equal '.', si.read(1)
77
+ nr.times { |x|
78
+ assert_equal '....', si.read(4), "nr=#{x}"
79
+ }
80
+ assert_nil si.read(1)
81
+ status = nil
82
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
83
+ assert status.success?
84
+ end
85
+
86
+ def test_gets_long
87
+ r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
88
+ si = Unicorn::StreamInput.new(@rd, r)
89
+ status = line = nil
90
+ pid = fork {
91
+ @rd.close
92
+ 3.times { @wr.write("ffff" * 4096) }
93
+ @wr.write "#$/foo#$/"
94
+ @wr.close
95
+ }
96
+ @wr.close
97
+ assert_nothing_raised { line = si.gets }
98
+ assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
99
+ assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
100
+ assert_nothing_raised { line = si.gets }
101
+ assert_equal "foo#$/", line
102
+ assert_nil si.gets
103
+ assert_nothing_raised { pid, status = Process.waitpid2(pid) }
104
+ assert status.success?
105
+ end
106
+
107
+ def test_read_with_buffer
108
+ r = init_request('hello')
109
+ si = Unicorn::StreamInput.new(@rd, r)
110
+ buf = ''
111
+ rv = si.read(4, buf)
112
+ assert_equal 'hell', rv
113
+ assert_equal 'hell', buf
114
+ assert_equal rv.object_id, buf.object_id
115
+ assert_equal 'o', si.read
116
+ assert_equal nil, si.read(5, buf)
117
+ end
118
+
119
+ def test_read_with_buffer_clobbers
120
+ r = init_request('hello')
121
+ si = Unicorn::StreamInput.new(@rd, r)
122
+ buf = 'foo'
123
+ assert_equal 'hello', si.read(nil, buf)
124
+ assert_equal 'hello', buf
125
+ assert_equal '', si.read(nil, buf)
126
+ assert_equal '', buf
127
+ buf = 'asdf'
128
+ assert_nil si.read(5, buf)
129
+ assert_equal '', buf
130
+ end
131
+
132
+ def init_request(body, size = nil)
133
+ @parser = Unicorn::HttpParser.new
134
+ body = body.to_s.freeze
135
+ @buf = "POST / HTTP/1.1\r\n" \
136
+ "Host: localhost\r\n" \
137
+ "Content-Length: #{size || body.size}\r\n" \
138
+ "\r\n#{body}"
139
+ assert_equal @env, @parser.headers(@env, @buf)
140
+ assert_equal body, @buf
141
+ @parser
142
+ end
143
+ end
@@ -4,6 +4,10 @@ require 'test/unit'
4
4
  require 'digest/sha1'
5
5
  require 'unicorn'
6
6
 
7
+ class TeeInput < Unicorn::TeeInput
8
+ attr_accessor :tmp, :len
9
+ end
10
+
7
11
  class TestTeeInput < Test::Unit::TestCase
8
12
 
9
13
  def setup
@@ -28,7 +32,7 @@ class TestTeeInput < Test::Unit::TestCase
28
32
 
29
33
  def test_gets_long
30
34
  r = init_request("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
31
- ti = Unicorn::TeeInput.new(@rd, r)
35
+ ti = TeeInput.new(@rd, r)
32
36
  status = line = nil
33
37
  pid = fork {
34
38
  @rd.close
@@ -49,7 +53,7 @@ class TestTeeInput < Test::Unit::TestCase
49
53
 
50
54
  def test_gets_short
51
55
  r = init_request("hello", 5 + "#$/foo".size)
52
- ti = Unicorn::TeeInput.new(@rd, r)
56
+ ti = TeeInput.new(@rd, r)
53
57
  status = line = nil
54
58
  pid = fork {
55
59
  @rd.close
@@ -68,7 +72,7 @@ class TestTeeInput < Test::Unit::TestCase
68
72
 
69
73
  def test_small_body
70
74
  r = init_request('hello')
71
- ti = Unicorn::TeeInput.new(@rd, r)
75
+ ti = TeeInput.new(@rd, r)
72
76
  assert_equal 0, @parser.content_length
73
77
  assert @parser.body_eof?
74
78
  assert_equal StringIO, ti.tmp.class
@@ -77,11 +81,12 @@ class TestTeeInput < Test::Unit::TestCase
77
81
  assert_equal 'hello', ti.read
78
82
  assert_equal '', ti.read
79
83
  assert_nil ti.read(4096)
84
+ assert_equal 5, ti.size
80
85
  end
81
86
 
82
87
  def test_read_with_buffer
83
88
  r = init_request('hello')
84
- ti = Unicorn::TeeInput.new(@rd, r)
89
+ ti = TeeInput.new(@rd, r)
85
90
  buf = ''
86
91
  rv = ti.read(4, buf)
87
92
  assert_equal 'hell', rv
@@ -96,7 +101,7 @@ class TestTeeInput < Test::Unit::TestCase
96
101
 
97
102
  def test_big_body
98
103
  r = init_request('.' * Unicorn::Const::MAX_BODY << 'a')
99
- ti = Unicorn::TeeInput.new(@rd, r)
104
+ ti = TeeInput.new(@rd, r)
100
105
  assert_equal 0, @parser.content_length
101
106
  assert @parser.body_eof?
102
107
  assert_kind_of File, ti.tmp
@@ -108,7 +113,7 @@ class TestTeeInput < Test::Unit::TestCase
108
113
  a, b = 300, 3
109
114
  r = init_request('.' * b, 300)
110
115
  assert_equal 300, @parser.content_length
111
- ti = Unicorn::TeeInput.new(@rd, r)
116
+ ti = TeeInput.new(@rd, r)
112
117
  pid = fork {
113
118
  @wr.write('.' * 197)
114
119
  sleep 1 # still a *potential* race here that would make the test moot...
@@ -122,12 +127,11 @@ class TestTeeInput < Test::Unit::TestCase
122
127
 
123
128
  def test_big_body_multi
124
129
  r = init_request('.', Unicorn::Const::MAX_BODY + 1)
125
- ti = Unicorn::TeeInput.new(@rd, r)
130
+ ti = TeeInput.new(@rd, r)
126
131
  assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
127
132
  assert ! @parser.body_eof?
128
133
  assert_kind_of File, ti.tmp
129
134
  assert_equal 0, ti.tmp.pos
130
- assert_equal 1, ti.tmp.size
131
135
  assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
132
136
  nr = Unicorn::Const::MAX_BODY / 4
133
137
  pid = fork {
@@ -138,8 +142,8 @@ class TestTeeInput < Test::Unit::TestCase
138
142
  @wr.close
139
143
  assert_equal '.', ti.read(1)
140
144
  assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
141
- nr.times {
142
- assert_equal '....', ti.read(4)
145
+ nr.times { |x|
146
+ assert_equal '....', ti.read(4), "nr=#{x}"
143
147
  assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
144
148
  }
145
149
  assert_nil ti.read(1)
@@ -163,7 +167,7 @@ class TestTeeInput < Test::Unit::TestCase
163
167
  @wr.write("0\r\n\r\n")
164
168
  }
165
169
  @wr.close
166
- ti = Unicorn::TeeInput.new(@rd, @parser)
170
+ ti = TeeInput.new(@rd, @parser)
167
171
  assert_nil @parser.content_length
168
172
  assert_nil ti.len
169
173
  assert ! @parser.body_eof?
@@ -201,7 +205,7 @@ class TestTeeInput < Test::Unit::TestCase
201
205
  end
202
206
  @wr.write("0\r\n\r\n")
203
207
  }
204
- ti = Unicorn::TeeInput.new(@rd, @parser)
208
+ ti = TeeInput.new(@rd, @parser)
205
209
  assert_nil @parser.content_length
206
210
  assert_nil ti.len
207
211
  assert ! @parser.body_eof?
@@ -230,7 +234,7 @@ class TestTeeInput < Test::Unit::TestCase
230
234
  @wr.write("Hello: World\r\n\r\n")
231
235
  }
232
236
  @wr.close
233
- ti = Unicorn::TeeInput.new(@rd, @parser)
237
+ ti = TeeInput.new(@rd, @parser)
234
238
  assert_nil @parser.content_length
235
239
  assert_nil ti.len
236
240
  assert ! @parser.body_eof?
@@ -241,6 +245,28 @@ class TestTeeInput < Test::Unit::TestCase
241
245
  assert status.success?
242
246
  end
243
247
 
248
+ def test_chunked_and_size_slow
249
+ @parser = Unicorn::HttpParser.new
250
+ @buf = "POST / HTTP/1.1\r\n" \
251
+ "Host: localhost\r\n" \
252
+ "Trailer: Hello\r\n" \
253
+ "Transfer-Encoding: chunked\r\n" \
254
+ "\r\n"
255
+ assert_equal @env, @parser.headers(@env, @buf)
256
+ assert_equal "", @buf
257
+
258
+ @wr.write("9\r\nabcde")
259
+ ti = TeeInput.new(@rd, @parser)
260
+ assert_nil @parser.content_length
261
+ assert_equal "abcde", ti.read(9)
262
+ assert ! @parser.body_eof?
263
+ @wr.write("fghi\r\n0\r\nHello: World\r\n\r\n")
264
+ assert_equal 9, ti.size
265
+ assert_equal "fghi", ti.read(9)
266
+ assert_equal nil, ti.read(9)
267
+ assert_equal "World", @env['HTTP_HELLO']
268
+ end
269
+
244
270
  private
245
271
 
246
272
  def init_request(body, size = nil)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unicorn
3
3
  version: !ruby/object:Gem::Version
4
- hash: 13
5
- prerelease: false
4
+ hash: -766259879
5
+ prerelease: true
6
6
  segments:
7
- - 2
7
+ - 3
8
8
  - 0
9
- - 1
10
- version: 2.0.1
9
+ - 0pre1
10
+ version: 3.0.0pre1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Unicorn hackers
@@ -105,6 +105,7 @@ extra_rdoc_files:
105
105
  - lib/unicorn/oob_gc.rb
106
106
  - lib/unicorn/preread_input.rb
107
107
  - lib/unicorn/socket_helper.rb
108
+ - lib/unicorn/stream_input.rb
108
109
  - lib/unicorn/tee_input.rb
109
110
  - lib/unicorn/tmpio.rb
110
111
  - lib/unicorn/util.rb
@@ -176,6 +177,7 @@ files:
176
177
  - lib/unicorn/oob_gc.rb
177
178
  - lib/unicorn/preread_input.rb
178
179
  - lib/unicorn/socket_helper.rb
180
+ - lib/unicorn/stream_input.rb
179
181
  - lib/unicorn/tee_input.rb
180
182
  - lib/unicorn/tmpio.rb
181
183
  - lib/unicorn/util.rb
@@ -230,7 +232,6 @@ files:
230
232
  - t/t0000-http-basic.sh
231
233
  - t/t0001-reload-bad-config.sh
232
234
  - t/t0002-config-conflict.sh
233
- - t/t0002-parser-error.sh
234
235
  - t/t0003-working_directory.sh
235
236
  - t/t0004-working_directory_broken.sh
236
237
  - t/t0005-working_directory_app.rb.sh
@@ -242,6 +243,8 @@ files:
242
243
  - t/t0010-reap-logging.sh
243
244
  - t/t0011-active-unix-socket.sh
244
245
  - t/t0012-reload-empty-config.sh
246
+ - t/t0013-rewindable-input-false.sh
247
+ - t/t0014-rewindable-input-true.sh
245
248
  - t/t0300-rails3-basic.sh
246
249
  - t/t0301-rails3-missing-config-ru.sh
247
250
  - t/t0302-rails3-alt-working_directory.sh
@@ -341,6 +344,7 @@ files:
341
344
  - test/unit/test_server.rb
342
345
  - test/unit/test_signals.rb
343
346
  - test/unit/test_socket_helper.rb
347
+ - test/unit/test_stream_input.rb
344
348
  - test/unit/test_tee_input.rb
345
349
  - test/unit/test_upload.rb
346
350
  - test/unit/test_util.rb
@@ -368,12 +372,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
368
372
  required_rubygems_version: !ruby/object:Gem::Requirement
369
373
  none: false
370
374
  requirements:
371
- - - ">="
375
+ - - ">"
372
376
  - !ruby/object:Gem::Version
373
- hash: 3
377
+ hash: 25
374
378
  segments:
375
- - 0
376
- version: "0"
379
+ - 1
380
+ - 3
381
+ - 1
382
+ version: 1.3.1
377
383
  requirements: []
378
384
 
379
385
  rubyforge_project: mongrel
@@ -1,31 +0,0 @@
1
- #!/bin/sh
2
- . ./test-lib.sh
3
- t_plan 5 "parser error test"
4
-
5
- t_begin "setup and startup" && {
6
- unicorn_setup
7
- unicorn -D env.ru -c $unicorn_config
8
- unicorn_wait_start
9
- }
10
-
11
- t_begin "send a bad request" && {
12
- (
13
- printf 'GET / HTTP/1/1\r\nHost: example.com\r\n\r\n'
14
- cat $fifo > $tmp &
15
- wait
16
- echo ok > $ok
17
- ) | socat - TCP:$listen > $fifo
18
- test xok = x$(cat $ok)
19
- }
20
-
21
- dbgcat tmp
22
-
23
- t_begin "response should be a 400" && {
24
- grep -F 'HTTP/1.1 400 Bad Request' $tmp
25
- }
26
-
27
- t_begin "server stderr should be clean" && check_stderr
28
-
29
- t_begin "term signal sent" && kill $unicorn_pid
30
-
31
- t_done