return;
}
- if (requestBodySource->exhausted())
+ if (!requestBodySource->exhausted())
+ sendMoreRequestBody();
+ else if (receivedWholeRequestBody)
doneSendingRequestBody();
else
- sendMoreRequestBody();
+ debugs(9,3, HERE << "waiting for body production end or abort");
}
+#if 0
bool
ServerStateData::canSend(int fd) const
{
char *skipLeadingSpace(char *aString);
static void connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount);
- static int connKeepReadingIncompleteRequest(ConnStateData * conn);
- static void connCancelIncompleteRequests(ConnStateData * conn);
-static ConnStateData *connStateCreate(const Ip::Address &peer, const Ip::Address &me, int fd, http_port_list *port);
+static ConnStateData *connStateCreate(const Comm::ConnectionPointer &details, http_port_list *port);
+// TODO make this return the conn for use instead.
int
ClientSocketContext::fd() const
{
void
ConnStateData::clientAfterReadingRequests(int do_next_read)
{
- /*
- * If (1) we are reading a message body, (2) and the connection
- * is half-closed, and (3) we didn't get the entire HTTP request
- * yet, then close this connection.
- */
-
- if (fd_table[clientConn->fd].flags.socket_eof) {
- if ((int64_t)in.notYetUsed < bodySizeLeft()) {
- /* Partial request received. Abort client connection! */
- debugs(33, 3, "clientAfterReadingRequests: FD " << clientConn->fd << " aborted, partial request");
- clientConn->close();
- return;
- }
+ // Were we expecting to read more request body from half-closed connection?
+ if (mayNeedToReadMoreBody() && commIsHalfClosed(fd)) {
- debugs(33, 3, HERE << "truncated body: closing half-closed FD " << fd);
- comm_close(fd);
++ debugs(33, 3, HERE << "truncated body: closing half-closed FD " << clientConn);
++ clientConn->close();
+ return;
}
clientMaybeReadData (do_next_read);
HttpVersion http_ver;
HttpParser hp;
- debugs(33, 5, "clientParseRequest: FD " << conn->fd << ": attempting to parse");
+ debugs(33, 5, HERE << conn->clientConn << ": attempting to parse");
- while (conn->in.notYetUsed > 0 && conn->bodySizeLeft() == 0) {
+ // Loop while we have read bytes that are not needed for producing the body
+ // On errors, bodyPipe may become nil, but readMoreRequests will be cleared
+ while (conn->in.notYetUsed > 0 && !conn->bodyPipe &&
+ conn->flags.readMoreRequests) {
connStripBufferWhitespace (conn);
/* Don't try to parse if the buffer is empty */
size_t putSize = 0;
- #if FUTURE_CODE_TO_SUPPORT_CHUNKED_REQUESTS
- // The code below works, in principle, but we cannot do dechunking
- // on-the-fly because that would mean sending chunked requests to
- // the next hop. Squid lacks logic to determine which servers can
- // receive chunk requests. Squid v3.0 code cannot even handle chunked
- // responses which we may encourage by sending chunked requests.
- // The error generation code probably needs more work.
- if (in.bodyParser) { // chunked body
- debugs(33,5, HERE << "handling chunked request body for FD " << clientConn->fd);
- bool malformedChunks = false;
-
- MemBuf raw; // ChunkedCodingParser only works with MemBufs
- raw.init(in.notYetUsed, in.notYetUsed);
- raw.append(in.buf, in.notYetUsed);
- try { // the parser will throw on errors
- const mb_size_t wasContentSize = raw.contentSize();
- BodyPipeCheckout bpc(*bodyPipe);
- const bool parsed = in.bodyParser->parse(&raw, &bpc.buf);
- bpc.checkIn();
- putSize = wasContentSize - raw.contentSize();
-
- if (parsed) {
- stopProducingFor(bodyPipe, true); // this makes bodySize known
- } else {
- // parser needy state must imply body pipe needy state
- if (in.bodyParser->needsMoreData() &&
- !bodyPipe->mayNeedMoreData())
- malformedChunks = true;
- // XXX: if bodyParser->needsMoreSpace, how can we guarantee it?
- }
- } catch (...) { // XXX: be more specific
- malformedChunks = true;
- }
-
- if (malformedChunks) {
- if (bodyPipe != NULL)
- stopProducingFor(bodyPipe, false);
-
- ClientSocketContext::Pointer context = getCurrentContext();
- if (!context->http->out.offset) {
- clientStreamNode *node = context->getClientReplyContext();
- clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
- assert (repContext);
- repContext->setReplyToError(ERR_INVALID_REQ, HTTP_BAD_REQUEST,
- METHOD_NONE, NULL, &peer.sin_addr,
- NULL, NULL, NULL);
- context->pullData();
- }
- flags.readMoreRequests = false;
- return; // XXX: is that sufficient to generate an error?
+ if (in.bodyParser) { // chunked encoding
+ if (const err_type error = handleChunkedRequestBody(putSize)) {
+ abortChunkedRequestBody(error);
+ return false;
}
- } else // identity encoding
- #endif
- {
- debugs(33,5, HERE << "handling plain request body for FD " << clientConn->fd);
+ } else { // identity encoding
- debugs(33,5, HERE << "handling plain request body for FD " << fd);
++ debugs(33,5, HERE << "handling plain request body for " << clientConn);
putSize = bodyPipe->putMoreData(in.buf, in.notYetUsed);
if (!bodyPipe->mayNeedMoreData()) {
// BodyPipe will clear us automagically when we produced everything
connNoteUseOfBuffer(this, putSize);
if (!bodyPipe) {
- debugs(33,5, HERE << "produced entire request body for FD " << clientConn->fd);
- debugs(33,5, HERE << "produced entire request body for FD " << fd);
++ debugs(33,5, HERE << "produced entire request body for " << clientConn);
if (closing()) {
/* we've finished reading like good clients,
* now do the close that initiateClose initiated.
- *
- * XXX: do we have to close? why not check keepalive et.
- *
- * XXX: To support chunked requests safely, we need to handle
- * the case of an endless request. This if-statement does not,
- * because mayNeedMoreData is true if request size is not known.
*/
- comm_close(fd);
+ clientConn->close();
+ return false;
}
}
- debugs(33,7, HERE << "chunked from FD " << fd << ": " << in.notYetUsed);
+
+ return true;
+ }
+
+ /// parses available chunked encoded body bytes, checks size, returns errors
+ err_type
+ ConnStateData::handleChunkedRequestBody(size_t &putSize)
+ {
- comm_reset_close(fd);
++ debugs(33,7, HERE << "chunked from " << clientConn << ": " << in.notYetUsed);
+
+ try { // the parser will throw on errors
+
+ if (!in.notYetUsed) // nothing to do (MemBuf::init requires this check)
+ return ERR_NONE;
+
+ MemBuf raw; // ChunkedCodingParser only works with MemBufs
+ // add one because MemBuf will assert if it cannot 0-terminate
+ raw.init(in.notYetUsed, in.notYetUsed+1);
+ raw.append(in.buf, in.notYetUsed);
+
+ const mb_size_t wasContentSize = raw.contentSize();
+ BodyPipeCheckout bpc(*bodyPipe);
+ const bool parsed = in.bodyParser->parse(&raw, &bpc.buf);
+ bpc.checkIn();
+ putSize = wasContentSize - raw.contentSize();
+
+ // dechunk then check: the size limit applies to _dechunked_ content
+ if (clientIsRequestBodyTooLargeForPolicy(bodyPipe->producedSize()))
+ return ERR_TOO_BIG;
+
+ if (parsed) {
+ finishDechunkingRequest(true);
+ Must(!bodyPipe);
+ return ERR_NONE; // nil bodyPipe implies body end for the caller
+ }
+
+ // if chunk parser needs data, then the body pipe must need it too
+ Must(!in.bodyParser->needsMoreData() || bodyPipe->mayNeedMoreData());
+
+ // if parser needs more space and we can consume nothing, we will stall
+ Must(!in.bodyParser->needsMoreSpace() || bodyPipe->buf().hasContent());
+ } catch (...) { // TODO: be more specific
+ debugs(33, 3, HERE << "malformed chunks" << bodyPipe->status());
+ return ERR_INVALID_REQ;
+ }
+
+ debugs(33, 7, HERE << "need more chunked data" << *bodyPipe->status());
+ return ERR_NONE;
+ }
+
+ /// quit on errors related to chunked request body handling
+ void
+ ConnStateData::abortChunkedRequestBody(const err_type error)
+ {
+ finishDechunkingRequest(false);
+
+ // XXX: The code below works if we fail during initial request parsing,
+ // but if we fail when the server-side works already, the server may send
+ // us its response too, causing various assertions. How to prevent that?
+ #if WE_KNOW_HOW_TO_SEND_ERRORS
+ ClientSocketContext::Pointer context = getCurrentContext();
+ if (context != NULL && !context->http->out.offset) { // output nothing yet
+ clientStreamNode *node = context->getClientReplyContext();
+ clientReplyContext *repContext = dynamic_cast<clientReplyContext*>(node->data.getRaw());
+ assert(repContext);
+ const http_status scode = (error == ERR_TOO_BIG) ?
+ HTTP_REQUEST_ENTITY_TOO_LARGE : HTTP_BAD_REQUEST;
+ repContext->setReplyToError(error, scode,
+ repContext->http->request->method,
+ repContext->http->uri,
+ peer,
+ repContext->http->request,
+ in.buf, NULL);
+ context->pullData();
+ } else {
+ // close or otherwise we may get stuck as nobody will notice the error?
- comm_reset_close(fd);
++ comm_reset_close(clientConn);
+ }
+ #else
+ debugs(33, 3, HERE << "aborting chunked request without error " << error);
++ comm_reset_close(clientConn);
+ #endif
+ flags.readMoreRequests = false;
}
void
debugs(33, 1, "WARNING: Closing client " << " connection due to lifetime timeout");
debugs(33, 1, "\t" << http->uri);
http->al.http.timedout = true;
-- comm_close(fd);
++ comm_close(fd); // XXX: this breaks ConnStateData::clientConn.
}
ConnStateData *
};
-class ConnectionDetail;
-
/** A connection to a socket */
- class ConnStateData : public BodyProducer/*, public RefCountable*/
+ class ConnStateData : public BodyProducer, public HttpControlMsgSink
{
public:
void addContextToQueue(ClientSocketContext * context);
int getConcurrentRequestCount() const;
bool isOpen() const;
+ void checkHeaderLimits();
+
+ // HttpControlMsgSink API
+ virtual void sendControlMsg(HttpControlMsg msg);
- int fd;
+ // Client TCP connection details from comm layer.
+ Comm::ConnectionPointer clientConn;
- /// chunk buffering and parsing algorithm state
- typedef enum { chunkUnknown, chunkNone, chunkParsing, chunkReady, chunkError } DechunkingState;
-
struct In {
In();
~In();
if (http->flags.internal)
r->protocol = PROTO_INTERNAL;
+ r->clientConnection = http->getConn();
+
/** Start forwarding to get the new object from network */
- FwdState::fwdStart(http->getConn() != NULL ? http->getConn()->fd : -1,
- http->storeEntry(),
- r);
+ Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConn : NULL;
+ FwdState::fwdStart(conn, http->storeEntry(), r);
}
}
void
FwdState::retryOrBail()
{
- if (!self) { // we have aborted before the server called us back
- debugs(17, 5, HERE << "not retrying because of earlier abort");
- // we will be destroyed when the server clears its Pointer to us
- return;
- }
-
if (checkRetry()) {
- int originserver = (servers->_peer == NULL);
- debugs(17, 3, "fwdServerClosed: re-forwarding (" << n_tries << " tries, " << (squid_curtime - start_t) << " secs)");
-
- if (servers->next) {
- /* use next, or cycle if origin server isn't last */
- FwdServer *fs = servers;
- FwdServer **T, *T2 = NULL;
- servers = fs->next;
-
- for (T = &servers; *T; T2 = *T, T = &(*T)->next);
- if (T2 && T2->_peer) {
- /* cycle */
- *T = fs;
- fs->next = NULL;
- } else {
- /* Use next. The last "direct" entry is retried multiple times */
- servers = fs->next;
- fwdServerFree(fs);
- originserver = 0;
+ debugs(17, 3, HERE << "re-forwarding (" << n_tries << " tries, " << (squid_curtime - start_t) << " secs)");
+
+ serverDestinations.shift(); // last one failed. try another.
+
+ if (serverDestinations.size() > 0) {
+ /* Ditch error page if it was created before.
+ * A new one will be created if there's another problem */
+ if (err) {
+ errorStateFree(err);
+ err = NULL;
}
- }
- /* Ditch error page if it was created before.
- * A new one will be created if there's another problem */
- if (err) {
- errorStateFree(err);
- err = NULL;
+ connectStart();
+ return;
}
+ // else bail. no more serverDestinations possible to try.
- /* use eventAdd to break potential call sequence loops and to slow things down a little */
- eventAdd("fwdConnectStart", fwdConnectStartWrapper, this, originserver ? 0.05 : 0.005, 0);
-
- return;
+ // AYJ: cannot-forward error ??
- ErrorState *anErr = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
- errorAppendEntry(entry, anErr);
++// is this hack needed since we now have doneWithRetries() below?
++// ErrorState *anErr = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
++// errorAppendEntry(entry, anErr);
}
- if (!err && shutting_down) {
+ // TODO: should we call completed() here and move doneWithRetries there?
+ doneWithRetries();
+
+ if (self != NULL && !err && shutting_down) {
- errorCon(ERR_SHUTTING_DOWN, HTTP_SERVICE_UNAVAILABLE, request);
+ ErrorState *anErr = errorCon(ERR_SHUTTING_DOWN, HTTP_SERVICE_UNAVAILABLE, request);
+ errorAppendEntry(entry, anErr);
}
self = NULL; // refcounted
#include "acl/FilledChecklist.h"
#include "auth/UserRequest.h"
+ #include "base/AsyncJobCalls.h"
#include "base/TextException.h"
+#include "comm/Connection.h"
#if DELAY_POOLS
#include "DelayPools.h"
#endif
HTTPMSGUNLOCK(orig_request);
- debugs(11,5, HERE << "HttpStateData " << this << " destroyed; FD " << fd);
+ cbdataReferenceDone(_peer);
+
- debugs(11,5, HERE << "HttpStateData " << this << " destroyed; FD " << (serverConnection!=NULL?serverConnection->fd:-1) );
++ debugs(11,5, HERE << "HttpStateData " << this << " destroyed; " << serverConnection);
}
-int
+const Comm::ConnectionPointer &
HttpStateData::dataDescriptor() const
{
- return fd;
+ return serverConnection;
}
+
/*
static void
httpStateFree(int fd, void *data)
void
HttpStateData::httpTimeout(const CommTimeoutCbParams ¶ms)
{
- debugs(11, 4, "httpTimeout: FD " << serverConnection->fd << ": '" << entry->url() << "'" );
- debugs(11, 4, "httpTimeout: FD " << fd << ": '" << entry->url() << "'" );
++ debugs(11, 4, HERE << serverConnection << ": '" << entry->url() << "'" );
if (entry->store_status == STORE_PENDING) {
fwd->fail(errorCon(ERR_READ_TIMEOUT, HTTP_GATEWAY_TIMEOUT, fwd->request));
HttpStateData::ConnectionStatus
HttpStateData::persistentConnStatus() const
{
- debugs(11, 3, "persistentConnStatus: FD " << serverConnection->fd << " eof=" << eof);
- debugs(11, 3, "persistentConnStatus: FD " << fd << " eof=" << eof);
++ debugs(11, 3, HERE << serverConnection << " eof=" << eof);
const HttpReply *vrep = virginReply();
debugs(11, 5, "persistentConnStatus: content_length=" << vrep->content_length);
int clen;
int len = io.size;
- assert(serverConnection->fd == io.fd);
- assert(fd == io.fd);
++// assert(serverConnection->fd == io.fd); // XXX: false when closing. serverConnection-> will already be -1.
flags.do_next_read = 0;
const int read_size = replyBodySpace(*readBuf, minRead);
debugs(11,9, HERE << (flags.do_next_read ? "may" : "wont") <<
- " read up to " << read_size << " bytes from FD " << serverConnection->fd);
- " read up to " << read_size << " bytes from FD " << fd);
++ " read up to " << read_size << " bytes from " << serverConnection);
/*
* why <2? Because delayAwareRead() won't actually read if
}
}
- /*
- * This will be called when request write is complete.
- */
+ /// called after writing the very last request byte (body, last-chunk, etc)
void
- HttpStateData::sendComplete(const CommIoCbParams &io)
+ HttpStateData::wroteLast(const CommIoCbParams &io)
{
- debugs(11, 5, "httpSendComplete: FD " << serverConnection->fd << ": size " << io.size << ": errflag " << io.flag << ".");
- debugs(11, 5, HERE << "FD " << fd << ": size " << io.size << ": errflag " << io.flag << ".");
++ debugs(11, 5, HERE << serverConnection << ": size " << io.size << ": errflag " << io.flag << ".");
#if URL_CHECKSUM_DEBUG
entry->mem_obj->checkUrlChecksum();
void
HttpStateData::closeServer()
{
- debugs(11,5, HERE << "closing HTTP server FD " << serverConnection->fd << " this " << this);
- debugs(11,5, HERE << "closing HTTP server FD " << fd << " this " << this);
++ debugs(11,5, HERE << "closing HTTP server " << serverConnection << " this " << this);
- if (serverConnection->isOpen()) {
- if (fd >= 0) {
- fwd->unregister(fd);
- comm_remove_close_handler(fd, closeHandler);
++ if (Comm::IsConnOpen(serverConnection)) {
+ fwd->unregister(serverConnection);
+ comm_remove_close_handler(serverConnection->fd, closeHandler);
closeHandler = NULL;
- comm_close(fd);
- fd = -1;
+ serverConnection->close();
}
}
bool
HttpStateData::doneWithServer() const
{
- return serverConnection == NULL || !serverConnection->isOpen();
- return fd < 0;
++ return !Comm::IsConnOpen(serverConnection);
}
-
/*
* Fixup authentication request headers for special cases
*/
{
MemBuf mb;
- debugs(11, 5, "httpSendRequest: FD " << serverConnection->fd << ", request " << request << ", this " << this << ".");
- debugs(11, 5, "httpSendRequest: FD " << fd << ", request " << request << ", this " << this << ".");
++ debugs(11, 5, HERE << serverConnection << ", request " << request << ", this " << this << ".");
- if (!canSend(fd)) {
- debugs(11,3, HERE << "cannot send request to closing FD " << fd);
+ if (!Comm::IsConnOpen(serverConnection)) {
- debugs(11,3, HERE << "cannot send request to closing FD " << serverConnection->fd);
++ debugs(11,3, HERE << "cannot send request to closing " << serverConnection);
assert(closeHandler != NULL);
return false;
}
mb.init();
request->peer_host=_peer?_peer->host:NULL;
buildRequestPrefix(request, orig_request, entry, &mb, flags);
- debugs(11, 6, "httpSendRequest: FD " << serverConnection->fd << ":\n" << mb.buf);
- debugs(11, 6, "httpSendRequest: FD " << fd << ":\n" << mb.buf);
- comm_write_mbuf(fd, &mb, requestSender);
++ debugs(11, 6, HERE << serverConnection << ":\n" << mb.buf);
+ comm_write_mbuf(serverConnection->fd, &mb, requestSender);
return true;
}
*/
}
- void
- HttpStateData::doneSendingRequestBody()
+ /// if broken posts are enabled for the request, try to fix and return true
+ bool
+ HttpStateData::finishingBrokenPost()
{
- debugs(11,5, HERE << "doneSendingRequestBody: FD " << serverConnection->fd);
-
#if USE_HTTP_VIOLATIONS
- if (Config.accessList.brokenPosts) {
- ACLFilledChecklist ch(Config.accessList.brokenPosts, request, NULL);
- if (!ch.fastCheck()) {
- debugs(11, 5, "doneSendingRequestBody: didn't match brokenPosts");
- CommIoCbParams io(NULL);
- io.fd = serverConnection->fd;
- io.flag = COMM_OK;
- sendComplete(io);
- } else {
- debugs(11, 2, "doneSendingRequestBody: matched brokenPosts");
+ if (!Config.accessList.brokenPosts) {
+ debugs(11, 5, HERE << "No brokenPosts list");
+ return false;
+ }
- if (!Comm::IsConnOpen(serverConnection)) {
- debugs(11,2, HERE << "cannot send CRLF to closing FD");
- assert(closeHandler != NULL);
- return;
- }
+ ACLFilledChecklist ch(Config.accessList.brokenPosts, request, NULL);
+ if (!ch.fastCheck()) {
+ debugs(11, 5, HERE << "didn't match brokenPosts");
+ return false;
+ }
- typedef CommCbMemFunT<HttpStateData, CommIoCbParams> Dialer;
- AsyncCall::Pointer call = JobCallback(11, 5, Dialer, this, HttpStateData::sendComplete);
- comm_write(serverConnection->fd, "\r\n", 2, call);
- }
- return;
- if (!canSend(fd)) {
- debugs(11,2, HERE << "ignoring broken POST for closing FD " << fd);
++ if (!Comm::IsConnOpen(serverConnection)) {
++ debugs(11,2, HERE << "ignoring broken POST for closed " << serverConnection);
+ assert(closeHandler != NULL);
+ return true; // prevent caller from proceeding as if nothing happened
}
- debugs(11, 5, "doneSendingRequestBody: No brokenPosts list");
+
+ debugs(11, 2, "finishingBrokenPost: fixing broken POST");
+ typedef CommCbMemFunT<HttpStateData, CommIoCbParams> Dialer;
+ requestSender = JobCallback(11,5,
+ Dialer, this, HttpStateData::wroteLast);
- comm_write(fd, "\r\n", 2, requestSender);
++ comm_write(serverConnection->fd, "\r\n", 2, requestSender);
+ return true;
+ #else
+ return false;
#endif /* USE_HTTP_VIOLATIONS */
- comm_write(fd, "0\r\n\r\n", 5, requestSender);
+ }
+
+ /// if needed, write last-chunk to end the request body and return true
+ bool
+ HttpStateData::finishingChunkedRequest()
+ {
+ if (flags.sentLastChunk) {
+ debugs(11, 5, HERE << "already sent last-chunk");
+ return false;
+ }
+
+ Must(receivedWholeRequestBody); // or we should not be sending last-chunk
+ flags.sentLastChunk = true;
+
+ typedef CommCbMemFunT<HttpStateData, CommIoCbParams> Dialer;
+ requestSender = JobCallback(11,5,
+ Dialer, this, HttpStateData::wroteLast);
- debugs(11,5, HERE << "doneSendingRequestBody: FD " << fd);
++ comm_write(serverConnection->fd, "0\r\n\r\n", 5, requestSender);
+ return true;
+ }
+
+ void
+ HttpStateData::doneSendingRequestBody()
+ {
+ ServerStateData::doneSendingRequestBody();
++ debugs(11,5, HERE << serverConnection);
+
+ // do we need to write something after the last body byte?
+ const bool chunked = request->header.chunked();
+ if (chunked && finishingChunkedRequest())
+ return;
+ if (!chunked && finishingBrokenPost())
+ return;
- CommIoCbParams io(NULL);
- io.fd = serverConnection->fd;
- io.flag = COMM_OK;
- sendComplete(io);
+ sendComplete();
}
// more origin request body data is available
HttpStateData::abortTransaction(const char *reason)
{
debugs(11,5, HERE << "aborting transaction for " << reason <<
- "; FD " << serverConnection->fd << ", this " << this);
- "; FD " << fd << ", this " << this);
++ "; " << serverConnection << ", this " << this);
- if (fd >= 0) {
- comm_close(fd);
+ if (serverConnection->isOpen()) {
+ serverConnection->close();
return;
}
protected:
virtual HttpRequest *originalRequest();
+ void processReply();
+ void proceedAfter1xx();
+ void handle1xx(HttpReply *msg);
+
private:
+ /**
+ * The current server connection.
+ * Maybe open, closed, or NULL.
+ * Use doneWithServer() to check if the server is available for use.
+ */
+ Comm::ConnectionPointer serverConnection;
AsyncCall::Pointer closeHandler;
enum ConnectionStatus {
INCOMPLETE_MSG,