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 +1 -1
- data/TODO +0 -3
- data/ext/unicorn_http/global_variables.h +0 -4
- data/ext/unicorn_http/unicorn_http.rl +48 -38
- data/lib/unicorn.rb +1 -0
- data/lib/unicorn/configurator.rb +26 -6
- data/lib/unicorn/const.rb +2 -2
- data/lib/unicorn/http_request.rb +11 -2
- data/lib/unicorn/http_server.rb +10 -1
- data/lib/unicorn/preread_input.rb +1 -1
- data/lib/unicorn/stream_input.rb +156 -0
- data/lib/unicorn/tee_input.rb +19 -128
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/test/unit/test_http_parser.rb +13 -3
- data/test/unit/test_http_parser_ng.rb +43 -12
- data/test/unit/test_stream_input.rb +143 -0
- data/test/unit/test_tee_input.rb +39 -13
- metadata +16 -10
- data/t/t0002-parser-error.sh +0 -31
    
        data/GIT-VERSION-GEN
    CHANGED
    
    
    
        data/TODO
    CHANGED
    
    
| @@ -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  | 
| 21 | 
            +
            #define UH_FL_REQEOF 0x40
         | 
| 22 22 | 
             
            #define UH_FL_KAVERSION 0x80
         | 
| 23 23 | 
             
            #define UH_FL_HASHEADER 0x100
         | 
| 24 24 |  | 
| 25 | 
            -
            /*  | 
| 26 | 
            -
            #define UH_FL_KEEPALIVE ( | 
| 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 | 
            -
               | 
| 83 | 
            -
             | 
| 84 | 
            -
                 | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 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 | 
            -
                 | 
| 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
    
    
    
        data/lib/unicorn/configurator.rb
    CHANGED
    
    | @@ -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 | 
            -
                 | 
| 377 | 
            -
             | 
| 378 | 
            -
             | 
| 379 | 
            -
             | 
| 380 | 
            -
             | 
| 381 | 
            -
             | 
| 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  | 
| 11 | 
            -
              UNICORN_VERSION = " | 
| 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"
         | 
    
        data/lib/unicorn/http_request.rb
    CHANGED
    
    | @@ -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 | 
            -
               | 
| 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 ? | 
| 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
         | 
    
        data/lib/unicorn/http_server.rb
    CHANGED
    
    | @@ -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  | 
| 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}"
         | 
| @@ -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
         | 
    
        data/lib/unicorn/tee_input.rb
    CHANGED
    
    | @@ -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 | 
            -
                 | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 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  | 
| 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  | 
| 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 | 
            -
              #  | 
| 175 | 
            -
               | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 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  | 
| 190 | 
            -
                 | 
| 191 | 
            -
                   | 
| 192 | 
            -
                  @ | 
| 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 | 
            -
                 | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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  | 
| 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. | 
| 496 | 
            -
                @parser. | 
| 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
         | 
    
        data/test/unit/test_tee_input.rb
    CHANGED
    
    | @@ -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 =  | 
| 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 =  | 
| 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 =  | 
| 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 =  | 
| 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 =  | 
| 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 =  | 
| 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 =  | 
| 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 =  | 
| 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 =  | 
| 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 =  | 
| 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:  | 
| 5 | 
            -
              prerelease:  | 
| 4 | 
            +
              hash: -766259879
         | 
| 5 | 
            +
              prerelease: true
         | 
| 6 6 | 
             
              segments: 
         | 
| 7 | 
            -
              -  | 
| 7 | 
            +
              - 3
         | 
| 8 8 | 
             
              - 0
         | 
| 9 | 
            -
              -  | 
| 10 | 
            -
              version:  | 
| 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:  | 
| 377 | 
            +
                  hash: 25
         | 
| 374 378 | 
             
                  segments: 
         | 
| 375 | 
            -
                  -  | 
| 376 | 
            -
                   | 
| 379 | 
            +
                  - 1
         | 
| 380 | 
            +
                  - 3
         | 
| 381 | 
            +
                  - 1
         | 
| 382 | 
            +
                  version: 1.3.1
         | 
| 377 383 | 
             
            requirements: []
         | 
| 378 384 |  | 
| 379 385 | 
             
            rubyforge_project: mongrel
         | 
    
        data/t/t0002-parser-error.sh
    DELETED
    
    | @@ -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
         |