前面实现了基础的跳转,那么动态交互中登录是常用功能。
本篇实现一个动态交互的简单登录和注销功能,在Qt中使用Session和Cookie技术。
链接:https://pan.baidu.com/s/1nkmsHgr-11Khe9k6Ntyf_g?pwd=1234
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
过期时间定义从内存中删除未使用的会话后的毫秒数。当用户在该时间之后返回时,他的会话将丢失,因此他必须再次登录。
#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")