unicorn 2.0.1 → 3.0.0pre1

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