Date | Commit message (Collapse) |
|
|
|
|
|
The overhead of the temporary array is too little to
be worth worrying about...
|
|
From RFC 2616, section 14.20:
> Comparison of expectation values is case-insensitive for
> unquoted tokens (including the 100-continue token), and is
> case-sensitive for quoted-string expectation-extensions.
|
|
The Revactor::TCP::Socket#read method without any arguments is
more or less equivalent to TCPSocket#readpartial without an
explicit buffer. Since we rely on the buffer being
modified/replaced, monkey patch that object it to replace the
buffer with our own since we rely on modifying the existing
buffer passed to us.
|
|
* Documented Unicorn::HttpParser API methods
* Keep GPL2 (COPYING) as-is without RDoc formatting.
* The auto-generated index.html is stupid, replace it with
README which looks saner.
|
|
|
|
Since Rack permits body objects to have #close called on
them, we can safely close our pipe readers immediately
instead of waiting on the GC to close them (like we do for
TeeInput tempfiles).
|
|
Rack is autoload-based and so are we.
|
|
I've noticed rackup has been __END__-aware as of
7b6046b764eafd332b3b2d9d93b3915c425fae54 in Rack upstream
|
|
The code I was _about_ to commit to support them was too ugly
and the performance benefit in real applications/clients is
unproven.
Support for these things also had the inevitable side effect of
adding overhead to non-persistent requests. Worst of all,
interaction with people tweaking TCP_CORK/TCP_NOPUSH suddenly
becomes an order of magnitude more complex because of the
_need_ to flush the socket out in between requests (for
most apps).
Aggressive pipelining can actually help a lot with a direct
connection to Unicorn, not via proxy. But the applications (and
clients) that would benefit from it are very few and moving
those applications away from HTTP entirely would be an even
bigger benefit.
The C/Ragel parser will maintain keepalive support for now since
I've always intended for that to be used in more general-purpose
HTTP servers than Unicorn.
For documentation purposes, the keep-alive/pipelining patch is
included below in its entirety. I don't have the
TCP_CORK/TCP_NOPUSH parts in there because that's when I finally
gave up and decided it would not be worth supporting.
diff --git a/lib/unicorn.rb b/lib/unicorn.rb
index b185b25..cc58997 100644
--- a/lib/unicorn.rb
+++ b/lib/unicorn.rb
@@ -439,22 +439,24 @@ module Unicorn
# 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)
+ response = nil
client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
- response = app.call(env = REQUEST.read(client))
-
- if 100 == response.first.to_i
- client.write(Const::EXPECT_100_RESPONSE)
- env.delete(Const::HTTP_EXPECT)
- response = app.call(env)
- end
+ begin
+ response = app.call(env = REQUEST.read(client))
- HttpResponse.write(client, response)
+ if 100 == response.first.to_i
+ client.write(Const::EXPECT_100_RESPONSE)
+ env.delete(Const::HTTP_EXPECT)
+ response = app.call(env)
+ end
+ end while HttpResponse.write(client, response, HttpRequest::PARSER.keepalive?)
+ client.close
# if we get any error, try to write something back to the client
# assuming we haven't closed the socket, but don't get hung up
# if the socket is already closed or broken. We'll always ensure
# the socket is closed at the end of this function
- rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
- client.write_nonblock(Const::ERROR_500_RESPONSE) rescue nil
+ rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
+ Errno::EBADF,Errno::ENOTCONN
client.close rescue nil
rescue HttpParserError # try to tell the client they're bad
client.write_nonblock(Const::ERROR_400_RESPONSE) rescue nil
diff --git a/lib/unicorn/http_request.rb b/lib/unicorn/http_request.rb
index 1358ccc..f73bdd0 100644
--- a/lib/unicorn/http_request.rb
+++ b/lib/unicorn/http_request.rb
@@ -42,6 +42,7 @@ module Unicorn
# to handle any socket errors (e.g. user aborted upload).
def read(socket)
REQ.clear
+ pipelined = PARSER.keepalive? && 0 < BUF.size
PARSER.reset
# From http://www.ietf.org/rfc/rfc3875:
@@ -55,7 +56,9 @@ module Unicorn
TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
# short circuit the common case with small GET requests first
- PARSER.headers(REQ, socket.readpartial(Const::CHUNK_SIZE, BUF)) and
+ PARSER.headers(REQ,
+ pipelined ? BUF :
+ socket.readpartial(Const::CHUNK_SIZE, BUF)) and
return handle_body(socket)
data = BUF.dup # socket.readpartial will clobber data
diff --git a/lib/unicorn/http_response.rb b/lib/unicorn/http_response.rb
index 5602a43..e22b5f9 100644
--- a/lib/unicorn/http_response.rb
+++ b/lib/unicorn/http_response.rb
@@ -20,12 +20,15 @@ module Unicorn
# to most clients since they can close their connection immediately.
class HttpResponse
+ include Unicorn::SocketHelper
# Every standard HTTP code mapped to the appropriate message.
CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
hash[code] = "#{code} #{msg}"
hash
}
+ CLOSE = "close".freeze # :nodoc
+ KEEPALIVE = "keep-alive".freeze # :nodoc
# Rack does not set/require a Date: header. We always override the
# Connection: and Date: headers no matter what (if anything) our
@@ -34,7 +37,7 @@ module Unicorn
OUT = [] # :nodoc
# writes the rack_response to socket as an HTTP response
- def self.write(socket, rack_response)
+ def self.write(socket, rack_response, keepalive = false)
status, headers, body = rack_response
status = CODES[status.to_i] || status
OUT.clear
@@ -50,6 +53,10 @@ module Unicorn
end
end
+ if keepalive && (0 == HttpRequest::BUF.size)
+ keepalive = IO.select([socket], nil, nil, 0.0)
+ end
+
# Rack should enforce Content-Length or chunked transfer encoding,
# so don't worry or care about them.
# Date is required by HTTP/1.1 as long as our clock can be trusted.
@@ -57,10 +64,10 @@ module Unicorn
socket.write("HTTP/1.1 #{status}\r\n" \
"Date: #{Time.now.httpdate}\r\n" \
"Status: #{status}\r\n" \
- "Connection: close\r\n" \
+ "Connection: #{keepalive ? KEEPALIVE : CLOSE}\r\n" \
"#{OUT.join(Z)}\r\n")
body.each { |chunk| socket.write(chunk) }
- socket.close # flushes and uncorks the socket immediately
+ keepalive
ensure
body.respond_to?(:close) and body.close rescue nil
end
|
|
ab still sends this with HTTP/1.0 requests, which is
unfortunate, but synthetic benchmarks are good for marketing
purposes!
|
|
|
|
|
|
This lets clients can pass through newly-invented status codes
that Rack does not know about.
|
|
|
|
|
|
We need to declare constants for 64-bit off_t explicitly with
the "LL" suffix on 32-bit machines.
|
|
It's not worth the effort to keep the internal API consistent
between non-bugfix versions.
|
|
TeeInput being needed is now (once again) an uncommon code path
so there's no point in relying on global constants. While we're
at it, allow StringIO to be used in the presence of small
inputs; too.
|
|
This makes a noticeable difference on light GET/HEAD requests.
Heck, even the tests run a few seconds faster.
|
|
|
|
Otherwise we'll be creating an extra garbage string because
rb_hash_aset can't tell the string isn't being used elsewhere
and creates a new frozen one.
|
|
This should be used to detect if a request can really handle
keepalives and pipelining. Currently, the rules are:
1. MUST be a GET or HEAD request
2. MUST be HTTP/1.1
3. MUST NOT have "Connection: close" set
This also reduces the amount of garbage we create by
globalizing constants and using them whenever possible.
|
|
For comparing a raw memory space against a constant
|
|
This method is strictly a filter, it does no I/O so "read"
is not an appropriate name to give it.
|
|
The normal at_exit handlers can't work here
|
|
|
|
Otherwise Ruby could get confused and not be able to reap the
process correctly (and thus wait a long time for timeout).
|
|
Otherwise they might be picked up by the GC during the
other tests (exposed by Ruby 1.9.1-p243).
|
|
Since Rack requires a Hash object, this is joined in in
accordance with rfc2616, section 4.2[1]. Of course, it's up to
the framework or application to handle such requests.
I could optimize this to avoid creating a single garbage
String object, but I don't think it's common enough to
worry about...
[1] - http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
|
Just in case clients decide to get cute.
|
|
We're bound by the maximum value of off_t when handling
input bodies (we need to buffer to disk). Also ensure
we stop bad clients that send us unparseable lengths.
|
|
This should be more robust, faster and easier to deal
with than the ugly proof-of-concept regexp-based ones.
|
|
Our current TrailerParser is liberal and does not require it,
but the to-be-activated Ragel one is not.
|
|
|
|
Explicitly track if our request will need Content-Length
or chunked body decoding.
|
|
The macro version lets us painlessly compare Ruby strings
against constant C strings. It wraps a C version of the
function which is necessary to avoid double evaluation while
preventing the user from having to type sizeof or strlen.
|
|
We'll need to keep track of a few more things to account
for content/chunk length and the temporary file descriptor
we'll need to use.
|
|
|
|
It's easy enough to just check a single equality without having
extra functions obscuring what's actually going on.
|
|
We'll need to be defining g_http_transfer_encoding and
g_content_length in the near future.
|
|
Just extra noise we don't care about. This also allows us to
undef the DEF_GLOBAL macro to avoid polluting the main
namespace.
|
|
It's mostly uninteresting code.
|
|
Create a Ruby string object before jumping into the function
to reduce the number of parameters passed and to take advantage
of our STR_NEW macro to reduce noise.
|
|
|
|
It doesn't conflict with any any common variables, tokens or
documentation pieces in the code.
|
|
Eliminate unnecessary jumping around in the source code to find
actions that lead to other actions and making it harder to
notice side effects when dealing with data we're sharing anyways.
|
|
There's also no point in validating field hits if our field is a
common field; so only do the validation for field length if we
need to allocate memory for a new field.
|
|
The "_value" suffix was used to denote the return type, which is
redundandant as it is already known at compile time (being that
this is C and functions have explicit return types). Furthurmore,
the "_value" is confusing since we're actually returning a "key"
when "value" is used in the context of "key-value" pairs.
|