前言
前面實現了基礎的跳轉,那麼動態交互中登錄是常用功能。
本篇實現一個動態交互的簡單登錄和註銷功能,在Qt中使用Session和Cookie技術。
Web應用程序通常處理用戶輸入。將開發一個登錄表單,看看進展如何。
創建一個名爲LoginController的新類。同樣,它是從HttpRequestHandl派生的
#ifndef LOGINCONTROLLER_H
#define LOGINCONTROLLER_H
#include "httprequesthandler.h"
using namespace stefanfrings;
class LoginController : public HttpRequestHandler {
Q_OBJECT
public:
LoginController(QObject* parent=0);
void service(HttpRequest& request, HttpResponse& response);
};
#endif // LOGINCONTROLLER_H
#include "logincontroller.h"
LoginController::LoginController(QObject* parent)
:HttpRequestHandler(parent) {
// empty
}
void LoginController::service(HttpRequest &request, HttpResponse &response) {
QByteArray username=request.getParameter("username");
QByteArray password=request.getParameter("password");
qDebug("username=%s",username.constData());
qDebug("password=%s",password.constData());
response.setHeader("Content-Type", "text/html; charset=UTF-8");
response.write("<html><body>");
if (username=="test" and password=="hello") {
response.write("Yes, correct");
}
else {
response.write("<form method='POST' action='/login'>");
if (!username.isEmpty()) {
response.write("No, that was wrong!<br><br>");
}
response.write("Please log in:<br>");
response.write("Name: <input type='text' name='username'><br>");
response.write("Password: <input type='password' name='password'><br>");
response.write("<input type='submit'>");
response.write("</form");
}
response.write("</body></html>",true);
}
(PS:html代表是提交表單)
將這個新控制器添加到請求映射器中,修改requestmapper.h:
#ifndef REQUESTMAPPER_H
#define REQUESTMAPPER_H
#include "httprequesthandler.h"
#include "helloworldcontroller.h"
#include "listdatacontroller.h"
#include "logincontroller.h"
class RequestMapper : public HttpRequestHandler {
Q_OBJECT
public:
RequestMapper(QObject* parent=0);
void service(HttpRequest& request, HttpResponse& response);
private:
HelloWorldController helloWorldController;
ListDataController listDataController;
LoginController loginController;
};
#endif // REQUESTMAPPER_H
修改requestmapper.cpp(切入了/login,調用loginController):
#include "requestmapper.h"
RequestMapper::RequestMapper(QObject* parent)
: HttpRequestHandler(parent) {
// empty
}
void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
QByteArray path=request.getPath();
qDebug("RequestMapper: path=%s",path.data());
if (path=="/" || path=="/hello") {
helloWorldController.service(request, response);
}
else if (path=="/list") {
listDataController.service(request, response);
}
else if (path=="/login") {
loginController.service(request, response);
}
else {
response.setStatus(404,"Not found");
response.write("The URL is wrong, no such document.");
}
qDebug("RequestMapper: finished request");
}
運行程序並打開URLhttp://localhost:8080/login.將看到以下表格:
嘗試使用錯誤的名稱和密碼登錄。然後瀏覽器顯示錯誤消息“That was wrong”,並提示重試。如果輸入了正確的憑據(用戶名“test”和密碼“hello”),則會收到成功消息。
HTML表單定義了兩個名爲“username”和“password”的輸入字段。控制器使用request.getParameter()來獲取這些值。
當參數爲空或傳入的HTTP請求中沒有這樣的參數時,Request.getParameter() 返回一個空的QByteArray。後一種情況發生在打開URL時http://localhost:8080/login開始只有當用戶單擊提交按鈕時,表單字段纔會從web瀏覽器發送到web服務器。
如果需要區分空字段和缺失字段,那麼可以使用request.getParameterMap(),然後檢查所需參數是否在返回的映射中。
作爲表單的替代方案,參數也可以作爲URL的一部分進行傳輸。例如,也可以通過打開URL登錄http://localhost:8080/login?username=test&password=hello.
在URL中使用某些特殊字符時,必須將其編碼爲轉義序列。例如,如果用戶名是“Stefan Frings”,那麼必須寫http://localhost:8080/login?username=Stefan%20Frings&password=hello.HttpRequest類會自動將其解碼回原始形式“Stefan Frings”。
如果需要將字符串編碼爲URL格式,可以使用QUrl類。
(PS:session和cookie是一起搭配使用的,cookie存在本地 session可以拿到cookie來判斷是否登錄了,等一些已有的狀態)
下一個合乎邏輯的步驟是處理會話數據。這意味着,將當前用戶的數據保存在某個地方,並在後續請求中使用這些數據。將存儲的第一個數據是用戶的姓名和登錄時間。
QtWebApp使用隱藏的cookie來識別用戶。
必須在控制會話存儲類的配置文件webapp1.ini中添加一個新的部分:
[sessions]
expirationTime=3600000
cookieName=sessionid
;cookieDomain=mydomain.com
cookiePath=/
cookieComment=Identifies the user
過期時間定義從內存中刪除未使用的會話後的毫秒數。當用戶在該時間之後返回時,他的會話將丟失,因此他必須再次登錄。
- expirationTime:保存時間,實際上從後面的cookie截圖可以看到是3600000ms,則是3600秒一小時的時間過期,這個保存後的默認過期時間。
- sessionid:cookie名稱可以是任意名稱,但通常使用名稱“sessionid”。有些負載均衡器依賴於這個名稱,所以除非有充分的理由,否則不應該更改它。
- cookieDomain:每個cookie總是綁定到一個域。由google.com生成的cookie只發送到該域上的服務器。如果將cookieDomain參數留空或將其註釋掉,則該參數將由web瀏覽器自動設置。可以指定另一個域名,但不知道男人爲什麼要這麼做。所以,除非知道自己在做什麼,否則就把它空着。
- cookiePath:cookie路徑可用於將cookie限制在的域的一部分。如果將cookiePath更改爲/my/very/cool/online/shop,則瀏覽器將僅針對以該路徑開頭的頁面將cookie發送到的服務器。默認值爲“/”,這意味着cookie對域中的所有網站都有效。
- cookieComment:cookieComment是一些網絡瀏覽器在cookie管理屏幕中顯示的文本。
需要HttpSessionStore類的一個實例,整個程序都可以訪問該實例,因此可以在全局空間中訪問。因此,創建了兩個新文件,第一個是global.h:
#ifndef GLOBAL_H
#define GLOBAL_H
#include "httpsessionstore.h"
using namespace stefanfrings;
extern HttpSessionStore* sessionStore;
#endif // GLOBAL_H
global.cpp:
#include "global.h"
HttpSessionStore* sessionStore;
現在有了一個名爲“sessionStore”的全局靜態指針,整個程序可以通過包含global.h文件來訪問該指針。讓加載新的配置設置並初始化sessionStore。
main.cpp中的更改:
#include "global.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QString configFileName=searchConfigFile();
// Session store
QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
sessionSettings->beginGroup("sessions");
sessionStore=new HttpSessionStore(sessionSettings,&app);
// HTTP server
QSettings* listenerSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
listenerSettings->beginGroup("listener");
new HttpListener(listenerSettings,new RequestMapper(&app),&app);
return app.exec();
}
請注意,main.cpp現在加載配置文件兩次。sessionSettings對象選擇“sessions”部分,而listenerSettings選擇“listener”部分。對於每個部分,需要一個單獨的QSettings實例,否則這些部分可能會混淆。
既然已經爲會話數據創建了一個存儲,就可以開始使用它了。添加到logincontroller.cpp:
#include <QTime>
#include "logincontroller.h"
#include "global.h"
LoginController::LoginController(QObject* parent)
:HttpRequestHandler(parent) {
// empty
}
void LoginController::service(HttpRequest &request, HttpResponse &response) {
HttpSession session=sessionStore->getSession(request,response,true);
QByteArray username=request.getParameter("username");
QByteArray password=request.getParameter("password");
qDebug("username=%s",username.constData());
qDebug("password=%s",password.constData());
response.setHeader("Content-Type", "text/html; charset=UTF-8");
response.write("<html><body>");
if (session.contains("username")) {
QByteArray username=session.get("username").toByteArray();
QTime logintime=session.get("logintime").toTime();
response.write("You are already logged in.<br>");
response.write("Your name is: "+username+"<br>");
response.write("You logged in at: "+logintime.toString("HH:mm:ss")+"<br>");
}
else {
if (username=="test" and password=="hello") {
response.write("Yes, correct");
session.set("username",username);
session.set("logintime",QTime::currentTime());
}
else {
response.write("<form method='POST' action='/login'>");
if (!username.isEmpty()) {
response.write("No, that was wrong!<br><br>");
}
response.write("Please log in:<br>");
response.write("Name: <input type='text' name='username'><br>");
response.write("Password: <input type='password' name='password'><br>");
response.write("<input type='submit'>");
response.write("</form")