#include "../shared/ThreadMonitor.h" #include "../shared/InputFramework.h" #include "../shared/ReplyToClient.h" #include "../shared/LogFile.h" #include "GZip.h" #include "OneTimeTimeout.h" #include "HttpThread.h" ///////////////////////////////////////////////////////////////////// // HttpRequest ///////////////////////////////////////////////////////////////////// HttpRequest::~HttpRequest() { if (!_responseSent) { // If no response has been sent, we send a generic error now. This // is mostly for own housekeeping. The actual response isn't very // important. In many cases the client has already abandoned the // old request before we got here. ScgiToServer response; if (defaultResponse) defaultResponse->prepairHttpResponse(this, response); sendResponse(response); } } void HttpRequest::autoCompress(ScgiToServer &response) { static const std::string CONTENT_ENCODING = "Content-Encoding"; if (response._headers.count(CONTENT_ENCODING)) { // This was already compressed. We don't expect to reuse a response. // That could be bad if the first person wants automatic compression // and the second person does not have the ability to handle compression! // But this might happen some custom encoding was applied before this // automatic step. static const std::string COMPRESS_ALEADY = "compress already"; ThreadMonitor::find().increment(COMPRESS_ALEADY); return; } if (response._body.size() < 100) { // The body is small so there is no point in trying to compress it. static const std::string COMPRESS_SKIP = "compress skip"; ThreadMonitor::find().increment(COMPRESS_SKIP); return; } static const std::string ACCEPT_ENCODING = "HTTP_ACCEPT_ENCODING"; const std::string acceptEncoding = getPropertyDefault(fromServer.getHeaders(), ACCEPT_ENCODING); const std::vector< std::string > encodings = explode(",", acceptEncoding); for (std::vector< std::string >::const_iterator it = encodings.begin(); it != encodings.end(); it++) { const std::vector< std::string > pieces = explode(";", *it); static const std::string GZIP = "gzip"; if (trim(pieces[0]) == GZIP) { // We are good to go. Do the compression. // Really, we should do one more test. If pieces[1] is "q=0" or // "q=0.0" or something like that, we should abort and not do // compression. static const std::string COMPRESS_GZIP = "compress gzip"; ThreadMonitor::find().increment(COMPRESS_GZIP); response._headers[CONTENT_ENCODING] = GZIP; response._body = gZipCompress(response._body, gzoSmallest); return; } } static const std::string COMPRESS_NOT_ALLOWED = "compress not allowed"; ThreadMonitor::find().increment(COMPRESS_NOT_ALLOWED); /* TclList msg; msg<first<second; msg<fromServer.httpRootDir(); sendResponse(response); } ///////////////////////////////////////////////////////////////////// // HttpThread ///////////////////////////////////////////////////////////////////// void HttpThread::cleanUp(class SocketInfo *socket, std::string const &reason, bool andDelete) { if (HttpRequest *r = getPropertyDefault(_pendingRequests, socket)) { if (andDelete) delete r; _pendingRequests.erase(socket); ThreadMonitor::find().increment(reason); } } // The web server closed the connection before we had a complete request. static const std::string S_EOF = "eof"; // We parsed a complete request and we sent it on to a listener. static const std::string S_SUCCESS = "success"; // The socket was closed, probably internally to this program, before // we had a complete request. static const std::string S_CLOSED = "closed"; // The server sent us something but were unable to parse anything good // out of it. We found an error before we ran out of data. static const std::string S_FAIL = "fail"; // A new socket was opened. We started parsing. Every new socket should have // a corresponding code from above saying what happened to the connection. static const std::string S_NEW = "NEW"; void HttpThread::threadFunction() { while (true) { _incomingRequests.waitForRequest(); while (Request *current = _incomingRequests.getRequest()) { switch (current->callbackId) { case mtNewInput: { NewInputRequest *r = dynamic_cast(current); SocketInfo *socket = r->getSocketInfo(); if (r->atEOF()) { cleanUp(socket, S_EOF); DeleteSocketThread::deleteSocket(socket); } else if (r->newSocket()) { _pendingRequests[socket] = new HttpRequest(socket); TimeVal timeout(true); // I'm arbitrarily setting a timout of 5 seconds. // The web server doesn't start to talk with us until // it has the complete request from the end user. So // 1/2 secound would probably be sufficient. timeout.addMilliseconds(5000); OneTimeTimeoutRequest *timeoutRequest = new OneTimeTimeoutRequest(socket, timeout, mtTimeout, &_incomingRequests); OneTimeTimeoutThread::getInstance().submit(timeoutRequest); ThreadMonitor::find().increment(S_NEW); } else { if (HttpRequest *hr = getPropertyDefault(_pendingRequests, socket)) { hr->fromServer.addBytes(r->getNewInput()); if (hr->fromServer.success()) { _httpListener->onHttpRequest(hr); cleanUp(socket, S_SUCCESS, false); } else if (hr->fromServer.error()) { cleanUp(socket, S_FAIL); DeleteSocketThread::deleteSocket(socket); } } } break; } case mtTimeout: { SocketInfo *socket = current->getSocketInfo(); if (_pendingRequests.count(socket)) { // If the timer goes off before we hear the end of this // request, we close the connection and give up. ThreadMonitor::find().increment("timeout"); DeleteSocketThread::deleteSocket(socket); } // Otherwise we ignore the timer. break; } case mtQuit: { delete current; return; } case DeleteSocketThread::callbackId: { SocketInfo *socket = current->getSocketInfo(); cleanUp(socket, S_CLOSED); break; } } delete current; } } } HttpThread::HttpThread(HttpListener *httpListener, std::string name) : ThreadClass(name), _httpListener(httpListener), _incomingRequests(name) { startThread(); } HttpThread::~HttpThread() { Request *r = new Request(NULL); r->callbackId = mtQuit; _incomingRequests.newRequest(r); }