void TActionContext::execute() { T_TRACEFUNC(); try { httpSocket = new THttpSocket; if (!httpSocket->setSocketDescriptor(socketDesc)) { emitError(httpSocket->error()); delete httpSocket; httpSocket = 0; return; } else { socketDesc = 0; } while (!httpSocket->canReadRequest()) { if (stopped) { tSystemDebug("Detected stop request"); break; } // Check timeout if (!httpSocket->waitForReadyRead(10000)) { tWarn("Read timed out after 10 seconds."); break; } } if (!httpSocket->canReadRequest()) { httpSocket->abort(); delete httpSocket; httpSocket = 0; return; } THttpRequest httpRequest = httpSocket->read(); const THttpRequestHeader &hd = httpRequest.header(); // Access log QByteArray firstLine = hd.method() + ' ' + hd.path(); firstLine += QString(" HTTP/%1.%2").arg(hd.majorVersion()).arg(hd.minorVersion()).toLatin1(); TAccessLog accessLog(httpSocket->peerAddress().toString().toLatin1(), firstLine); tSystemDebug("method : %s", hd.method().data()); tSystemDebug("path : %s", hd.path().data()); Tf::HttpMethod method = httpRequest.method(); QString path = THttpUtility::fromUrlEncoding(hd.path().split('?').value(0)); // Routing info exists? Routing rt = TUrlRoute::instance().findRouting(method, path); tSystemDebug("Routing: controller:%s action:%s", rt.controller.data(), rt.action.data()); if (rt.isEmpty()) { // Default URL routing rt.params = path.split('/'); if (path.startsWith(QLatin1Char('/')) && !rt.params.isEmpty()) { rt.params.removeFirst(); // unuse first item } if (path.endsWith(QLatin1Char('/')) && !rt.params.isEmpty()) { rt.params.removeLast(); // unuse last item } // Direct view render mode? if (Tf::app()->treefrogSettings().value(DIRECT_VIEW_RENDER_MODE).toBool()) { // Direct view setting rt.controller = "directcontroller"; rt.action = "show"; QString tmp = rt.params.join("/"); rt.params.clear(); rt.params << tmp; } else { if (!rt.params.value(0).isEmpty()) { rt.controller = rt.params.takeFirst().toLower().toLatin1() + "controller"; if (!rt.params.value(0).isEmpty()) { rt.action = rt.params.takeFirst().toLatin1(); } } tSystemDebug("Active Controller : %s", rt.controller.data()); } } // Call controller method TDispatcher<TActionController> ctlrDispatcher(rt.controller); TActionController *controller = ctlrDispatcher.object(); if (controller) { controller->setActionName(rt.action); controller->setHttpRequest(httpRequest); openDatabase(); // Session if (controller->sessionEnabled()) { TSession session; QByteArray sessionId = httpRequest.cookie(TSession::sessionName()); if (!sessionId.isEmpty()) { session = TSessionManager::instance().find(sessionId); } controller->setSession(session); // Exports flash-variant controller->exportAllFlashVariants(); } // Verify authenticity token if (controller->forgeryProtectionEnabled() && !controller->protectFromForgeryExcept().contains(rt.action)) { if (method == Tf::Post || method == Tf::Put || method == Tf::Delete) { if (!controller->verifyRequest(httpRequest)) { throw SecurityException("Invalid authenticity token", __FILE__, __LINE__); } } } // Sets the default status code of HTTP response int statusCode = 404; // Not Found // Re-generate session ID if (controller->sessionEnabled() && (controller->session().id().isEmpty() || Tf::app()->treefrogSettings().value(AUTO_ID_REGENERATION).toBool())) { controller->session().regenerateId(); } if (controller->transactionEnabled()) { // Begins a transaction on the database beginTransaction(); } // Do filters if (controller->preFilter()) { // Dispathes bool dispatched = ctlrDispatcher.invoke(rt.action, rt.params); if (dispatched) { autoRemoveFiles = controller->autoRemoveFiles; // Set auto remove files // Post fileter controller->postFilter(); if (controller->transactionEnabled() && !controller->rollbackEnabled()) { // Commits a transaction to the database commitTransaction(); } // Session store if (controller->sessionEnabled()) { bool stored = TSessionManager::instance().store(controller->session()); if (stored) { controller->addCookie(TSession::sessionName(), controller->session().id(), QDateTime(), "/"); } } // Sets status code statusCode = (!controller->response.isBodyNull()) ? controller->statusCode() : 500 /* Internal Server Error */; } } controller->response.header().setStatusLine(statusCode, THttpUtility::getResponseReasonPhrase(statusCode)); accessLog.statusCode = statusCode; accessLog.responseBytes = writeResponse(controller->response.header(), controller->response.bodyIODevice(), controller->response.bodyLength()); // Outputs access log tAccessLog(accessLog); } if (!controller) { // GET Method? if (method == Tf::Get) { path.remove(0, 1); QFile reqPath(Tf::app()->publicPath() + path); QFileInfo fi(reqPath); if (fi.isFile() && fi.isReadable()) { QByteArray type = Tf::app()->internetMediaType(fi.suffix()); writeResponse(200, type, &reqPath, reqPath.size()); } else { writeResponse(404); // Not Found } } else if (method == Tf::Post) { // file upload? } else { // HEAD, DELETE, ... } } } catch (ClientErrorException &e) { tWarn("Caught ClientErrorException: status code:%d", e.statusCode()); writeResponse(e.statusCode()); } catch (SqlException &e) { tError("Caught SqlException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (SecurityException &e) { tError("Caught SecurityException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (RuntimeException &e) { tError("Caught RuntimeException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (...) { tError("Caught Exception"); } closeDatabase(); httpSocket->disconnectFromHost(); // Destorys the object in the thread which created it delete httpSocket; httpSocket = 0; }
void TActionContext::execute() { T_TRACEFUNC(""); TAccessLog accessLog; THttpResponseHeader responseHeader; try { httpSocket = new THttpSocket; if (!httpSocket->setSocketDescriptor(socketDesc)) { emitError(httpSocket->error()); delete httpSocket; httpSocket = 0; return; } else { socketDesc = 0; } while (!httpSocket->canReadRequest()) { if (stopped) { tSystemDebug("Detected stop request"); break; } // Check idle timeout if (httpSocket->idleTime() >= 10) { tSystemWarn("Reading a socket timed out after 10 seconds. Descriptor:%d", (int)httpSocket->socketDescriptor()); break; } httpSocket->waitForReadyRead(100); } if (!httpSocket->canReadRequest()) { httpSocket->abort(); delete httpSocket; httpSocket = 0; return; } THttpRequest httpRequest = httpSocket->read(); const THttpRequestHeader &hdr = httpRequest.header(); // Access log QByteArray firstLine = hdr.method() + ' ' + hdr.path(); firstLine += QString(" HTTP/%1.%2").arg(hdr.majorVersion()).arg(hdr.minorVersion()).toLatin1(); accessLog.request = firstLine; accessLog.remoteHost = (Tf::app()->appSettings().value(LISTEN_PORT).toUInt() > 0) ? httpSocket->peerAddress().toString().toLatin1() : QByteArray("(unix)"); tSystemDebug("method : %s", hdr.method().data()); tSystemDebug("path : %s", hdr.path().data()); Tf::HttpMethod method = httpRequest.method(); QString path = THttpUtility::fromUrlEncoding(hdr.path().split('?').value(0)); // Routing info exists? TRouting rt = TUrlRoute::instance().findRouting(method, path); tSystemDebug("Routing: controller:%s action:%s", rt.controller.data(), rt.action.data()); if (rt.isEmpty()) { // Default URL routing rt.params = path.split('/'); if (path.startsWith(QLatin1Char('/')) && !rt.params.isEmpty()) { rt.params.removeFirst(); // unuse first item } if (path.endsWith(QLatin1Char('/')) && !rt.params.isEmpty()) { rt.params.removeLast(); // unuse last item } // Direct view render mode? if (Tf::app()->appSettings().value(DIRECT_VIEW_RENDER_MODE).toBool()) { // Direct view setting rt.controller = "directcontroller"; rt.action = "show"; } else { if (!rt.params.value(0).isEmpty()) { rt.controller = rt.params.takeFirst().toLower().toLatin1() + "controller"; if (rt.controller == "applicationcontroller") { rt.controller.clear(); // Can not call 'ApplicationController' } // Default action: index rt.action = rt.params.value(0, QLatin1String("index")).toLatin1(); if (!rt.params.isEmpty()) { rt.params.takeFirst(); } } tSystemDebug("Active Controller : %s", rt.controller.data()); } } // Call controller method TDispatcher<TActionController> ctlrDispatcher(rt.controller); currController = ctlrDispatcher.object(); if (currController) { currController->setActionName(rt.action); currController->setHttpRequest(httpRequest); // Session if (currController->sessionEnabled()) { TSession session; QByteArray sessionId = httpRequest.cookie(TSession::sessionName()); if (!sessionId.isEmpty()) { // Finds a session session = TSessionManager::instance().findSession(sessionId); } currController->setSession(session); // Exports flash-variant currController->exportAllFlashVariants(); } // Verify authenticity token if (Tf::app()->appSettings().value(ENABLE_CSRF_PROTECTION_MODULE, true).toBool() && currController->csrfProtectionEnabled() && !currController->exceptionActionsOfCsrfProtection().contains(rt.action)) { if (method == Tf::Post || method == Tf::Put || method == Tf::Delete) { if (!currController->verifyRequest(httpRequest)) { throw SecurityException("Invalid authenticity token", __FILE__, __LINE__); } } } if (currController->sessionEnabled()) { if (currController->session().id().isEmpty() || Tf::app()->appSettings().value(AUTO_ID_REGENERATION).toBool()) { TSessionManager::instance().remove(currController->session().sessionId); // Removes the old session // Re-generate session ID currController->session().sessionId = TSessionManager::instance().generateId(); tSystemDebug("Re-generate session ID: %s", currController->session().sessionId.data()); } // Sets CSRF protection informaion TActionController::setCsrfProtectionInto(currController->session()); } // Database Transaction transactions.setEnabled(currController->transactionEnabled()); // Do filters if (currController->preFilter()) { // Dispathes bool dispatched = ctlrDispatcher.invoke(rt.action, rt.params); if (dispatched) { autoRemoveFiles << currController->autoRemoveFiles; // Adds auto-remove files // Post fileter currController->postFilter(); if (currController->rollbackRequested()) { rollbackTransactions(); } else { // Commits a transaction to the database commitTransactions(); } // Session store if (currController->sessionEnabled()) { bool stored = TSessionManager::instance().store(currController->session()); if (stored) { QDateTime expire; if (TSessionManager::sessionLifeTime() > 0) { expire = QDateTime::currentDateTime().addSecs(TSessionManager::sessionLifeTime()); } // Sets the path in the session cookie QString cookiePath = Tf::app()->appSettings().value(SESSION_COOKIE_PATH).toString(); currController->addCookie(TSession::sessionName(), currController->session().id(), expire, cookiePath); } } } } // Sets the default status code of HTTP response accessLog.statusCode = (!currController->response.isBodyNull()) ? currController->statusCode() : Tf::InternalServerError; currController->response.header().setStatusLine(accessLog.statusCode, THttpUtility::getResponseReasonPhrase(accessLog.statusCode)); // Writes a response and access log accessLog.responseBytes = writeResponse(currController->response.header(), currController->response.bodyIODevice(), currController->response.bodyLength()); httpSocket->disconnectFromHost(); // Session GC TSessionManager::instance().collectGarbage(); } else { accessLog.statusCode = Tf::BadRequest; if (method == Tf::Get) { // GET Method path.remove(0, 1); QFile reqPath(Tf::app()->publicPath() + path); QFileInfo fi(reqPath); if (fi.isFile() && fi.isReadable()) { // Check "If-Modified-Since" header for caching bool sendfile = true; QByteArray ifModifiedSince = hdr.rawHeader("If-Modified-Since"); if (!ifModifiedSince.isEmpty()) { QDateTime dt = THttpUtility::fromHttpDateTimeString(ifModifiedSince); sendfile = (!dt.isValid() || dt != fi.lastModified()); } if (sendfile) { // Sends a request file responseHeader.setRawHeader("Last-Modified", THttpUtility::toHttpDateTimeString(fi.lastModified())); QByteArray type = Tf::app()->internetMediaType(fi.suffix()); accessLog.responseBytes = writeResponse(Tf::OK, responseHeader, type, &reqPath, reqPath.size()); } else { // Not send the data accessLog.responseBytes = writeResponse(Tf::NotModified, responseHeader); } } else { accessLog.responseBytes = writeResponse(Tf::NotFound, responseHeader); } accessLog.statusCode = responseHeader.statusCode(); } else if (method == Tf::Post) { // file upload? } else { // HEAD, DELETE, ... } } } catch (ClientErrorException &e) { tWarn("Caught ClientErrorException: status code:%d", e.statusCode()); accessLog.responseBytes = writeResponse(e.statusCode(), responseHeader); accessLog.statusCode = e.statusCode(); } catch (SqlException &e) { tError("Caught SqlException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (SecurityException &e) { tError("Caught SecurityException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (RuntimeException &e) { tError("Caught RuntimeException: %s [%s:%d]", qPrintable(e.message()), qPrintable(e.fileName()), e.lineNumber()); } catch (...) { tError("Caught Exception"); } accessLog.timestamp = QDateTime::currentDateTime(); writeAccessLog(accessLog); // Writes access log // Push to the pool TActionContext::releaseDatabases(); httpSocket->disconnectFromHost(); // Destorys the object in the thread which created it delete httpSocket; httpSocket = 0; }