diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/unicorn/configurator.rb | 25 | ||||
-rw-r--r-- | lib/unicorn/const.rb | 12 | ||||
-rw-r--r-- | lib/unicorn/http_request.rb | 19 | ||||
-rw-r--r-- | lib/unicorn/http_response.rb | 6 | ||||
-rw-r--r-- | lib/unicorn/http_server.rb | 23 |
5 files changed, 77 insertions, 8 deletions
diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb index 89cbf5c..9752cdd 100644 --- a/lib/unicorn/configurator.rb +++ b/lib/unicorn/configurator.rb @@ -45,6 +45,7 @@ class Unicorn::Configurator }, :pid => nil, :preload_app => false, + :check_client_connection => false, :rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1), :client_body_buffer_size => Unicorn::Const::MAX_BODY, :trust_x_forwarded => true, @@ -96,6 +97,18 @@ class Unicorn::Configurator if ready_pipe = RACKUP.delete(:ready_pipe) server.ready_pipe = ready_pipe end + if set[:check_client_connection] + set[:listeners].each do |address| + if set[:listener_opts][address][:tcp_nopush] == true + raise ArgumentError, + "check_client_connection is incompatible with tcp_nopush:true" + end + if set[:listener_opts][address][:tcp_nodelay] == true + raise ArgumentError, + "check_client_connection is incompatible with tcp_nodelay:true" + end + end + end set.each do |key, value| value == :unset and next skip.include?(key) and next @@ -454,6 +467,18 @@ class Unicorn::Configurator set_int(:client_body_buffer_size, bytes, 0) end + # When enabled, unicorn will check the client connection by writing + # the beginning of the HTTP headers before calling the application. + # + # This will prevent calling the application for clients who have + # disconnected while their connection was queued. + # + # This option cannot be used in conjunction with tcp_nodelay or + # tcp_nopush. + def check_client_connection(bool) + set_bool(:check_client_connection, bool) + end + # Allow redirecting $stderr to a given path. Unlike doing this from # the shell, this allows the unicorn process to know the path its # writing to and rotate the file if it is used for logging. The diff --git a/lib/unicorn/const.rb b/lib/unicorn/const.rb index b3d8d71..f0c4c12 100644 --- a/lib/unicorn/const.rb +++ b/lib/unicorn/const.rb @@ -29,12 +29,16 @@ module Unicorn::Const # :stopdoc: # common errors we'll send back - ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n" - ERROR_414_RESPONSE = "HTTP/1.1 414 Request-URI Too Long\r\n\r\n" - ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n" - ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n" + ERROR_400_RESPONSE = "400 Bad Request\r\n\r\n" + ERROR_414_RESPONSE = "414 Request-URI Too Long\r\n\r\n" + ERROR_413_RESPONSE = "413 Request Entity Too Large\r\n\r\n" + ERROR_500_RESPONSE = "500 Internal Server Error\r\n\r\n" + EXPECT_100_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n" + EXPECT_100_RESPONSE_SUFFIXED = "100 Continue\r\n\r\nHTTP/1.1 " + HTTP_RESPONSE_START = ['HTTP', '/1.1 '] HTTP_EXPECT = "HTTP_EXPECT" + # :startdoc: end diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb index a0435d6..79ead2e 100644 --- a/lib/unicorn/http_request.rb +++ b/lib/unicorn/http_request.rb @@ -22,11 +22,14 @@ class Unicorn::HttpParser NULL_IO = StringIO.new("") + attr_accessor :response_start_sent + # :stopdoc: # A frozen format for this is about 15% faster REMOTE_ADDR = 'REMOTE_ADDR'.freeze RACK_INPUT = 'rack.input'.freeze @@input_class = Unicorn::TeeInput + @@check_client_connection = false def self.input_class @@input_class @@ -35,6 +38,15 @@ class Unicorn::HttpParser def self.input_class=(klass) @@input_class = klass end + + def self.check_client_connection + @@check_client_connection + end + + def self.check_client_connection=(bool) + @@check_client_connection = bool + end + # :startdoc: # Does the majority of the IO processing. It has been written in @@ -70,6 +82,13 @@ class Unicorn::HttpParser # an Exception thrown from the parser will throw us out of the loop false until add_parse(socket.kgio_read!(16384)) end + + # detect if the socket is valid by writing a partial response: + if @@check_client_connection && headers? + @response_start_sent = true + Unicorn::Const::HTTP_RESPONSE_START.each { |c| socket.write(c) } + end + e[RACK_INPUT] = 0 == content_length ? NULL_IO : @@input_class.new(socket, self) e.merge!(DEFAULTS) diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb index b781e20..61563cd 100644 --- a/lib/unicorn/http_response.rb +++ b/lib/unicorn/http_response.rb @@ -18,11 +18,13 @@ module Unicorn::HttpResponse CRLF = "\r\n" # writes the rack_response to socket as an HTTP response - def http_response_write(socket, status, headers, body) + def http_response_write(socket, status, headers, body, + response_start_sent=false) status = CODES[status.to_i] || status + http_response_start = response_start_sent ? '' : 'HTTP/1.1 ' if headers - buf = "HTTP/1.1 #{status}\r\n" \ + buf = "#{http_response_start}#{status}\r\n" \ "Date: #{httpdate}\r\n" \ "Status: #{status}\r\n" \ "Connection: close\r\n" diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb index 13df55a..ef1ea58 100644 --- a/lib/unicorn/http_server.rb +++ b/lib/unicorn/http_server.rb @@ -17,6 +17,7 @@ class Unicorn::HttpServer :listener_opts, :preload_app, :reexec_pid, :orig_app, :init_listeners, :master_pid, :config, :ready_pipe, :user + attr_reader :pid, :logger include Unicorn::SocketHelper include Unicorn::HttpResponse @@ -355,6 +356,14 @@ class Unicorn::HttpServer Unicorn::HttpParser.trust_x_forwarded = bool end + def check_client_connection + Unicorn::HttpRequest.check_client_connection + end + + def check_client_connection=(bool) + Unicorn::HttpRequest.check_client_connection = bool + end + private # wait for a signal hander to wake us up and then consume the pipe @@ -524,23 +533,33 @@ class Unicorn::HttpServer Unicorn.log_error(@logger, "app error", e) Unicorn::Const::ERROR_500_RESPONSE end + msg = "HTTP/1.1 #{msg}" unless @request.response_start_sent client.kgio_trywrite(msg) client.close rescue end + def expect_100_response + if @request.response_start_sent + Unicorn::Const::EXPECT_100_RESPONSE_SUFFIXED + else + Unicorn::Const::EXPECT_100_RESPONSE + end + end + # once a client is accepted, it is processed in its entirety here # in 3 easy steps: read request, call app, write app response def process_client(client) status, headers, body = @app.call(env = @request.read(client)) if 100 == status.to_i - client.write(Unicorn::Const::EXPECT_100_RESPONSE) + client.write(expect_100_response) env.delete(Unicorn::Const::HTTP_EXPECT) status, headers, body = @app.call(env) end @request.headers? or headers = nil - http_response_write(client, status, headers, body) + http_response_write(client, status, headers, body, + @request.response_start_sent) client.shutdown # in case of fork() in Rack app client.close # flush and uncork socket immediately, no keepalive rescue => e |