对于 http 请求处理来说,主要分为四大类,有 页面请求、注册请求、登录请求、用户信息获取请求,其中第一种请求属于 静态资源请求,后面三种是属于 动态功能请求,下面给出它们的框架:
/* http请求处理函数 */
private:
// 静态资源请求处理函数
void static_resource_handler(wsserver_t::connection_ptr& conn)
{}
// 注册功能请求的处理函数
void register_handler(wsserver_t::connection_ptr& conn)
{}
// 登录功能请求的处理函数
void login_handler(wsserver_t::connection_ptr& conn)
{}
// 获取个人信息功能请求的处理函数
void information_handler(wsserver_t::connection_ptr& conn)
{}
private:
// 服务器收到http请求的处理函数,会根据请求类型的不同去调用上面四个函数来完成处理
void http_callback(websocketpp::connection_hdl hdl)
{
// 1. 获取请求的方法和uri,用于区分不同的请求类型
wsserver_t::connection_ptr conn = _server.get_con_from_hdl(hdl);
websocketpp::http::parser::request req = conn->get_request();
std::string method = req.get_method();
std::string uri = req.get_uri();
// 2. 根据不同的请求类型调用对应的处理函数
if(method == "POST" && uri == "/reg") // 注册请求
return register_handler(conn);
else if(method == "POST" && uri == "/login") // 登录请求
return login_handler(conn);
else if(method == "GET" && uri == "/info") // 获取用户信息请求
return information_handler(conn);
else
return static_resource_handler(conn); // 剩下的都认为是静态资源请求
} 其中 http_callback() 函数就是 websocket 服务器收到 http 请求的处理函数,我们会在这个函数内部根据请求类型的不同,选择要去调用哪个处理函数来处理请求!
下面我们来实现它们!
1、静态资源请求处理函数
// 静态资源请求处理函数
void static_resource_handler(wsserver_t::connection_ptr& conn)
{
// 1. 获取请求的uri资源路径
websocketpp::http::parser::request req = conn->get_request();
std::string uri = req.get_uri();
// 2. 通过根目录和请求的uri组合成实际路径
std::string path = _webroot + uri;
// 3. 如果请求的是一个目录,那么就默认增加一个后缀login.html
if(path.back() == '/')
path += "index.html";
// 4. 读取文件内容,并且判断是否读写成功
std::string body;
bool ret = file_util::read(path, body);
if(ret == false)
{
DLOG("静态资源请求异常,%s, 大小: %d", path.c_str(), body.size());
// 读写失败则返回一个状态码为404的NOT FOUND页面
body.clear();
body += "<html>";
body += "<head>";
body += "<meta charset='UTF-8'/>";
body += "</head>";
body += "<body>";
body += "<h1> Not Found </h1>";
body += "</body>";
body += "</html>";
conn->set_status(websocketpp::http::status_code::not_found);
conn->set_body(body);
return;
}
// 5. 读写成功后设置响应正文,也就是将静态资源传回去
conn->set_status(websocketpp::http::status_code::ok);
conn->set_body(body);
DLOG("静态资源请求成功, %s, 大小: %d", path.c_str(), body.size());
}2、注册功能请求的处理函数
后台处理
这段代码非常的冗余,对于响应的操作,基本都是一致的:
// 注册功能请求的处理函数
void register_handler(wsserver_t::connection_ptr& conn)
{
// 1. 获取到请求正文 -- 为了获取用户名和密码
std::string req_body = conn->get_request_body();
// 2. 对正文进行反序列化,得到用户名和密码
Json::Value body;
bool ret = json_util::unserialize(req_body, body);
if(ret == false)
{
// 失败的话要返回错误响应
DLOG("反序列化注册信息失败");
Json::Value response;
response["result"] = false;
response["reason"] = "请求的正文格式错误";
std::string response_body;
json_util::serialize(response, response_body);
conn->set_body(response_body);
conn->set_status(websocketpp::http::status_code::bad_request);
conn->append_header("Content-Type", "application/json");
return;
}
// 3. 判断用户名和密码是否填写完整
if(body["username"].isNull() || body["password"].isNull())
{
// 填写不完整的话要返回错误响应
DLOG("用户名密码不完整");
Json::Value response;
response["result"] = false;
response["reason"] = "请输入用户名/密码";
std::string response_body;
json_util::serialize(response, response_body);
conn->set_body(response_body);
conn->set_status(websocketpp::http::status_code::bad_request);
conn->append_header("Content-Type", "application/json");
return;
}
// 4. 进行数据库的用户信息操作 -- 如果成功状态码返回200,失败返回400
ret = _user_table.sign_up(body);
if(ret == false)
{
// 写入用户信息错误的话要返回错误响应
DLOG("向数据库插入数据失败");
Json::Value response;
response["result"] = false;
response["reason"] = "该用户名已经被占用!";
std::string response_body;
json_util::serialize(response, response_body);
conn->set_body(response_body);
conn->set_status(websocketpp::http::status_code::bad_request);
conn->append_header("Content-Type", "application/json");
return;
}
Json::Value response;
response["result"] = true;
response["reason"] = "用户注册成功!";
std::string response_body;
json_util::serialize(response, response_body);
conn->set_body(response_body);
conn->set_status(websocketpp::http::status_code::bad_request);
conn->append_header("Content-Type", "application/json");
} 所以不妨我们再优化一下,将它们这些冗余部分提出来,用一个子函数 http_response() 来解决响应的问题,而我们要做的就是给这个子函数传递通信句柄、状态码、result字段结果、响应原因。
这样子的好处不仅仅是对注册函数有优化帮助,后面的几个 http 请求处理函数同样可以需要响应,所以同样能够通过调用 http_response() 减少冗余!
下面给出优化后的代码:
// http响应处理通用函数
void http_response(wsserver_t::connection_ptr& conn,
bool result,
websocketpp::http::status_code::value status,
const std::string& reason)
{
// 1. 填写响应信息
Json::Value response;
response["result"] = result;
response["reason"] = reason;
// 2. 进行序列化
std::string response_body;
json_util::serialize(response, response_body);
// 3. 设置http响应,类型为application/json
conn->set_body(response_body);
conn->set_status(status);
conn->append_header("Content-Type", "application/json");
}
// 注册功能请求的处理函数
void register_handler(wsserver_t::connection_ptr& conn)
{
// 1. 获取到请求正文 -- 为了获取用户名和密码
std::string req_body = conn->get_request_body();
// 2. 对正文进行反序列化,得到用户名和密码
Json::Value body;
bool ret = json_util::unserialize(req_body, body);
if(ret == false)
{
// 反序列化失败的话要返回错误响应
DLOG("反序列化注册信息失败");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "请求的正文格式错误");
}
// 3. 判断用户名和密码是否填写完整
if(body["username"].isNull() || body["password"].isNull() || body["username"].asString().empty() || body["password"].asString().empty())
{
// 填写不完整的话要返回错误响应
DLOG("用户名密码不完整");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "请输入正确的用户名和密码");
}
// 4. 进行数据库的用户信息操作 -- 如果成功状态码返回200,失败返回400
ret = _user_table.sign_up(body);
if(ret == false)
{
// 写入用户信息错误的话要返回错误响应
DLOG("向数据库插入数据失败");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "该用户名已经被占用!");
}
http_response(conn, true, websocketpp::http::status_code::ok, "用户注册成功");
}前端处理
- 给提交按钮添加点击事件
reg(),调用 javascript 写的注册函数 ret()注册函数的实现:- 获取两个输入框中的数据,组织成一个 json 串
- 通过 ajax 向后台发送用户注册请求
- 如果请求成功,则跳转到登录页面
- 如果请求失败,则清空输入框内容,并提示错误原因
下面代码中主要看脚本标签那里的代码,那才是处理和后台发送请求和响应的部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>注册</title>
<link rel="stylesheet" href="./css/common.css">
<link rel="stylesheet" href="./css/login.css">
</head>
<body>
<div class="nav">
网络五子棋对战游戏
</div>
<div class="login-container">
<!-- 登录界面的对话框 -->
<div class="login-dialog">
<!-- 提示信息 -->
<h3>注册</h3>
<!-- 这个表示一行 -->
<div class="row">
<span>用户名</span>
<input type="text" id="user_name" name="username">
</div>
<!-- 这是另一行 -->
<div class="row">
<span>密码</span>
<input type="password" id="password" name="password">
</div>
<!-- 提交按钮 -->
<div class="row">
<button id="submit" onclick="reg()">提交</button>
</div>
<div class="row">
<button id="login" onclick="location.href='./login.html'">登录页面</button>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script>
// 1. 给提交按钮添加点击事件
function reg()
{
// 2. 获取两个输入框中的数据,组织成一个json串
var reg_info = {
username: document.getElementById("user_name").value,
password: document.getElementById("password").value
};
// 3. 通过ajax向后台发送用户注册请求
$.ajax ({
url: "/reg",
type: "post",
data: JSON.stringify(reg_info),
success: function(res){
if(res.result == true){
// 如果请求成功,则跳转到登录页面
alert(res.reason)
window.location.assign("/login.html");
}else{
// 如果请求失败,则清空输入框内容,并提示错误原因
document.getElementById("user_name").value = "";
document.getElementById("password").value = "";
alert(res.reason);
}
},
error: function(xhr)
{
document.getElementById("user_name").value = "";
document.getElementById("password").value = "";
// alert(JSON.stringify(xhr))
alert("请重新输入正确用户名和密码:可能用户名已存在或者用户名违例!")
}
})
}
</script>
</body>
</html>3、登录功能请求的处理函数
后台处理
// 登录功能请求的处理函数
void login_handler(wsserver_t::connection_ptr& conn)
{
// 1. 获取请求正文,进行反序列化,得到用户名和密码
std::string req_body = conn->get_request_body();
Json::Value body;
bool ret = json_util::unserialize(req_body, body);
if(ret == false)
{
DLOG("反序列化登录信息失败");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "请求的正文格式错误");
}
// 2. 验证用户名和密码是否都填写
if(body["username"].isNull() || body["password"].isNull() || body["username"].asString().empty() || body["password"].asString().empty())
{
DLOG("用户名密码不完整");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名和密码");
}
// 3. 进行数据库的用户信息验证,验证用户名和密码 -- 验证失败则返回400
ret = _user_table.login(body);
if(ret == false)
{
DLOG("用户不存在或者密码错误");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "请输入正确的用户名和密码");
}
// 4. 如果验证成功,则给客户端创建session,并且设置过期时间
session_ptr sp = _session.add_session(body["id"].asUInt64(), LOGIN);
if(sp.get() == nullptr)
{
// 创建会话失败的话返回的状态码是500
DLOG("创建会话失败");
return http_response(conn, false, websocketpp::http::status_code::internal_server_error, "创建会话失败");
}
_session.set_session_expire_time(sp->getSessionID(), SESSION_EXPIRE_TIME);
// 5. 设置响应头部:Set-Cookie,将sessionID通过cookie响应返回
conn->append_header("Set-Cookie", "SSID=" + std::to_string(sp->getSessionID()));
http_response(conn, true, websocketpp::http::status_code::ok, "登录成功");
}前端处理
下面代码中主要看脚本标签那里的代码,那才是处理和后台发送请求和响应的部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录</title>
<link rel="stylesheet" href="./css/common.css">
<link rel="stylesheet" href="./css/login.css">
</head>
<body>
<div class="nav">
网络五子棋对战游戏
</div>
<div class="login-container">
<!-- 登录界面的对话框 -->
<div class="login-dialog">
<h3>登录</h3>
<div class="row">
<span>用户名</span>
<input type="text" id="user_name">
</div>
<div class="row">
<span>密码</span>
<input type="password" id="password">
</div>
<div class="row">
<button id="submit" onclick="login()">提交</button>
</div>
<div class="row">
<button id="register" onclick="location.href='./register.html'">注册页面</button>
</div>
</div>
</div>
<script src="./js/jquery.min.js"></script>
<script>
// 1. 给按钮添加点击事件,调用登录请求函数
// 2. 封装登录请求函数
function login(){
// 1. 获取输入框中的用户名和密码,并组织json对象
var login_info = {
username: document.getElementById("user_name").value,
password: document.getElementById("password").value
};
// 2. 通过ajax向后台发送登录请求
$.ajax({
url: "/login",
type: "post",
data: JSON.stringify(login_info),
success: function(res){
// 3. 如果验证通过,则跳转到游戏大厅页面
alert("登录成功");
window.location.assign("/game_hall.html");
},
error: function(xhr){
// 4. 如果验证失败,则提示错误信息,并清空输入框
alert("请输入正确的用户名和密码!");
document.getElementById("user_name").value = "";
document.getElementById("password").value = "";
}
})
}
</script>
</body>
</html>4、获取个人信息功能请求的处理函数
后台处理
其中因为要从 cookie 中分割出会话 id,所以我们封装出一个接口 get_sessionID_from_cookie() 提供此操作!
// 获取个人信息功能请求的处理函数
void information_handler(wsserver_t::connection_ptr& conn)
{
// 1. 获取请求信息中的cookie信息
std::string cookie_str = conn->get_request_header("Cookie");
if(cookie_str.empty())
{
// 如果没有cookie信息,则返回错误,让客户端重新登录
DLOG("找不到cookie信息,请重新登录");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "找不到cookie信息,请重新登录");
}
// 2. 从cookie中获取sessionID -- 封装一个接口并且调用
std::string sessionID;
bool ret = get_sessionID_from_cookie(cookie_str, "SSID", sessionID);
if(ret == false)
{
// cookie中没有会话id,则返回错误,让客户端重新登录
DLOG("找不到会话id,请重新登录");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "找不到会话id,请重新登录");
}
// 3. 在session管理对象中查找对应的session信息
session_ptr sp = _session.get_session_by_sesssionID(std::stol(sessionID));
if(sp.get() == nullptr)
{
// 在本地会话管理中找不到该会话,则认为登录已经过期,需要重新登录
DLOG("会话过期,请重新登录");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "会话过期,请重新登录");
}
// 4. 先获取用户id,然后从数据库中取出用户信息
uint64_t userid = sp->getUserID();
Json::Value userinfo;
ret = _user_table.select_by_id(userid, userinfo);
if(ret == false)
{
// 获取用户信息失败,返回错误:找不到用户信息
DLOG("找不到用户信息,请重新登录");
return http_response(conn, false, websocketpp::http::status_code::bad_request, "找不到用户信息,请重新登录");
}
// 5. 将用户信息进行序列化后发送给客户端 -- 一般不会序列化失败,所以这里不判断
std::string body;
json_util::serialize(userinfo, body);
conn->set_body(body);
conn->set_status(websocketpp::http::status_code::ok);
conn->append_header("Content-Type", "application/json");
// 6. 刷新session过期时间,也就是重新设置过期时间
_session.set_session_expire_time(sp->getSessionID(), SESSION_EXPIRE_TIME);
}前端处理
下面代码中主要看脚本标签那里的代码,那才是处理和后台发送请求和响应的部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>游戏大厅</title>
<link rel="stylesheet" href="./css/common.css">
<link rel="stylesheet" href="./css/game_hall.css">
</head>
<body>
<div class="nav">网络五子棋对战游戏</div>
<!-- 整个页面的容器元素 -->
<div class="container">
<!-- 这个 div 在 container 中是处于垂直水平居中这样的位置的 -->
<div>
<!-- 展示用户信息 -->
<div id="screen"></div>
<!-- 匹配按钮 -->
<div id="match-button">开始匹配</div>
</div>
</div>
<script src="./js/jquery.min.js"></script>
<script>
function get_user_info() {
$.ajax({
url: "/info",
type: "get",
success: function(res) {
var sl = 0;
if(res.win_count != 0){ sl = parseInt(res.win_count * 100 / res.total_count); }
var info_html = "<p>" + "用户: " + res.username + " 积分: " + res.score +
"</br>" + "比赛场次: " + res.total_count + " 获胜场次: " + res.win_count +
" 胜率: " + sl + "%" + "</p>";
var screen_div = document.getElementById("screen");
screen_div.innerHTML = info_html;
},
error: function(xhr) {
alert(JSON.stringify(xhr));
location.replace("/login.html");
}
})
}
get_user_info();
</script>
</body>
</html>