• 【项目实战】自主实现HTTP(七)——错误处理、线程池引入、项目扩展及结项


    目录

    构建响应报文

    构建成功的报文相应

    构建错误处理的相应报文

    及时性错误处理改善

    STOP机制的引入

    线程池引入

    回调函数的构建 

     任务队列的创建

    线程池构建

    项目拓展:链接数据库


    构建响应报文

          我们之前通过管道子进程,把对应文件的信息读取上来,直接返回回去不就行了吗,其实并不是这样的,我们读取上来的只是正文的部分,其相应行依然需要我们来自己构建。

          但是现在又有一个问题,就是我们对于合法的请求,找到对应的文件,构建请求成功(OK)的响应,但是对于请求过程中发生错误的时候,我们应该如何构建相应的相应呢?——也是根据其错误类型构建相应处理结果的相应。

    1. #define OK 200
    2. #define BAD_REQUEST 400
    3. #define NOT_FOUND 404
    4. #define SERVER_ERROR 500
    5. void BuildHttpResponseHelper()
    6. {
    7. //http_request;
    8. //http_response;
    9. auto &code = http_response.status_code;
    10. //构建状态行
    11. auto &status_line = http_response.status_line;
    12. status_line += HTTP_VERSION;
    13. status_line += " ";
    14. status_line += std::to_string(code);
    15. status_line += " ";
    16. status_line += Code2Desc(code);
    17. status_line += LINE_END;
    18. //构建响应正文,可能包括响应报头
    19. std::string path = WEB_ROOT;
    20. path += "/";
    21. switch(code){
    22. case OK:
    23. BuildOkResponse();
    24. break;
    25. case NOT_FOUND:
    26. path += PAGE_404;
    27. HandlerError(path);
    28. break;
    29. case BAD_REQUEST:
    30. path += PAGE_404;
    31. HandlerError(path);
    32. break;
    33. case SERVER_ERROR:
    34. path += PAGE_404;
    35. HandlerError(path);
    36. break;
    37. // case 500:
    38. // HandlerError(PAGE_500);
    39. // break;
    40. default:
    41. break;
    42. }
    43. }

             1.我们先构建状态行,包括版本号,状态码已经状态码的描述,将这三者加入到我们状态行之中。

           2. 在之前,我们的错误都是统一按照404处理了,而现在我们需要进行错误处理,所以我们需要把所有的错误分开来看,比如新加入400(页面域名不存在或者请求错误),500(服务器内部错误)等待,我们根据相关的错误码,返回对应的错误网页。

    构建成功的报文相应

    1. void BuildOkResponse()
    2. {
    3. std::string line = "Content-Type: ";
    4. line += Suffix2Desc(http_request.suffix);
    5. line += LINE_END;
    6. http_response.response_header.push_back(line);
    7. line = "Content-Length: ";
    8. if(http_request.cgi){
    9. line += std::to_string(http_response.response_body.size());
    10. }
    11. else{
    12. line += std::to_string(http_request.size); //Get
    13. }
    14. line += LINE_END;
    15. http_response.response_header.push_back(line);
    16. }

             假设现在请求是成功的,那么我们就构建成功的相应,我们通过之前截取的文件类型加入到文件类型中来,然后插入请求方法这个内容中,之后再加入文件长度的属性,如果是cgi,那么取正文的个数属性size,如果不是cgi,那么就返回当时请求读取的数据(静态网页)的数据,再插入到请求内容之中。

    构建错误处理的相应报文

    1. void HandlerError(std::string page)
    2. {
    3. std::cout << "debug: " << page << std::endl;
    4. http_request.cgi = false;
    5. //要给用户返回对应的404页面
    6. http_response.fd = open(page.c_str(), O_RDONLY);
    7. if(http_response.fd > 0){
    8. struct stat st;
    9. stat(page.c_str(), &st);
    10. http_request.size = st.st_size;
    11. std::string line = "Content-Type: text/html";
    12. line += LINE_END;
    13. http_response.response_header.push_back(line);
    14. line = "Content-Length: ";
    15. line += std::to_string(st.st_size);
    16. line += LINE_END;
    17. http_response.response_header.push_back(line);
    18. }
    19. }

          有可能在cgi之前发生了错误,这个时候就直接返回错误网页就可以了,所以先将cgi改为false,然后打开对应的404的错误网页,然后通过之前讲过的用stat来获取资源的大小,将其填充到请求大小以及返回报文的大小。

    及时性错误处理改善

         为了能够及时的把错误进行处理,我们可以从读取分析请求报文开始所有过程中可能遇到的错误用相关的错误码进行分离然后进行相关的处理。

    STOP机制的引入

          我们设置一个stop,然后将处理分析报文函数都设置成stop的布尔值,当stop=1的时候,表明此时不再需要读取。

    1. class EndPoint{
    2. private:
    3. int sock;
    4. HttpRequest http_request;
    5. HttpResponse http_response;
    6. bool stop;
    7. private:
    8. bool RecvHttpRequestLine()
    9. {
    10. auto &line = http_request.request_line;
    11. if(Util::ReadLine(sock, line) > 0){
    12. line.resize(line.size()-1);
    13. LOG(INFO, http_request.request_line);
    14. }
    15. else{
    16. stop = true;
    17. }
    18. std::cout << "RecvHttpRequestLine: " << stop << std::endl;
    19. return stop;
    20. }
    21. bool RecvHttpRequestHeader()
    22. {
    23. std::string line;
    24. while(true){
    25. line.clear();
    26. if(Util::ReadLine(sock, line) <= 0){
    27. stop = true;
    28. break;
    29. }
    30. if(line == "\n"){
    31. http_request.blank = line;
    32. break;
    33. }
    34. line.resize(line.size()-1);
    35. http_request.request_header.push_back(line);
    36. LOG(INFO, line);
    37. }
    38. std::cout <<"stop debug: " << stop << std::endl;
    39. return stop;
    40. }
    41. void ParseHttpRequestLine()
    42. {
    43. auto &line = http_request.request_line;
    44. std::stringstream ss(line);
    45. ss >> http_request.method >> http_request.uri >> http_request.version;
    46. auto &method = http_request.method;
    47. std::transform(method.begin(), method.end(), method.begin(), ::toupper);
    48. }
    49. void ParseHttpRequestHeader()
    50. {
    51. std::string key;
    52. std::string value;
    53. for(auto &iter : http_request.request_header)
    54. {
    55. if(Util::CutString(iter, key, value, SEP)){
    56. http_request.header_kv.insert({key, value});
    57. }
    58. }
    59. }
    60. bool IsNeedRecvHttpRequestBody()
    61. {
    62. auto &method = http_request.method;
    63. if(method == "POST"){
    64. auto &header_kv = http_request.header_kv;
    65. auto iter = header_kv.find("Content-Length");
    66. if(iter != header_kv.end()){
    67. LOG(INFO, "Post Method, Content-Length: "+iter->second);
    68. http_request.content_length = atoi(iter->second.c_str());
    69. return true;
    70. }
    71. }
    72. return false;
    73. }
    74. bool RecvHttpRequestBody()
    75. {
    76. if(IsNeedRecvHttpRequestBody()){
    77. int content_length = http_request.content_length;
    78. auto &body = http_request.request_body;
    79. char ch = 0;
    80. while(content_length){
    81. ssize_t s = recv(sock, &ch, 1, 0);
    82. if(s > 0){
    83. body.push_back(ch);
    84. content_length--;
    85. }
    86. else{
    87. stop = true;
    88. break;
    89. }
    90. }
    91. LOG(INFO, body);
    92. }
    93. return stop;
    94. }
    95. void RecvHttpRequest()
    96. {
    97. // || 短路求值
    98. if( (!RecvHttpRequestLine()) && (!RecvHttpRequestHeader()) ){
    99. ParseHttpRequestLine();
    100. ParseHttpRequestHeader();
    101. RecvHttpRequestBody();
    102. }
    103. }
    104. ep->RecvHttpRequest();
    105. if(!ep->IsStop()){ //一定要注意逻辑关系
    106. LOG(INFO, "Recv No Error, Begin Build And Send");
    107. ep->BuildHttpResponse();
    108. ep->SendHttpResponse();
    109. }
    110. else{
    111. LOG(WARNING, "Recv Error, Stop Build And Send");
    112. }

          我们分析的时候也是一样的,利用短路求值的方法,只有当接收请求行以及接收请求方法都正确的时候,我们才会进行分析请求行和请求方法,如果在接收的出现错误 stop作为返回值直接就会被置为1,这个时候不会再进行分析,直接跳出,而当程序运行到后面检测到stop为1,此时会直接报错返回,不会在进行后续处理。

    线程池引入

    回调函数的构建 

      为了进一步使用线程池来进行多线程调用的问题,我们现在就完成封装一下回调函数,当有线程接收到任务的时候就需要进行调用回调函数进行处理,我们只需要把之前的代码进行一下修改即可。

    1. class CallBack{
    2. public:
    3. CallBack()
    4. {}
    5. void operator()(int sock)
    6. {
    7. HandlerRequest(sock);
    8. }
    9. void HandlerRequest(int sock)
    10. {
    11. LOG(INFO, "Hander Request Begin");
    12. #ifdef DEBUG
    13. //For Test
    14. char buffer[4096];
    15. recv(sock, buffer, sizeof(buffer), 0);
    16. std::cout << "-------------begin----------------" << std::endl;
    17. std::cout << buffer << std::endl;
    18. std::cout << "-------------end----------------" << std::endl;
    19. #else
    20. EndPoint *ep = new EndPoint(sock);
    21. ep->RecvHttpRequest();
    22. if(!ep->IsStop()){ //一定要注意逻辑关系
    23. LOG(INFO, "Recv No Error, Begin Build And Send");
    24. ep->BuildHttpResponse();
    25. ep->SendHttpResponse();
    26. }
    27. else{
    28. LOG(WARNING, "Recv Error, Stop Build And Send");
    29. }
    30. delete ep;
    31. #endif
    32. LOG(INFO, "Hander Request End");
    33. }
    34. ~CallBack()
    35. {}
    36. };

     任务队列的创建

    1. #pragma once
    2. #include
    3. #include "Protocol.hpp"
    4. class Task{
    5. private:
    6. int sock;
    7. CallBack handler; //设置回调
    8. public:
    9. Task()
    10. {}
    11. Task(int _sock):sock(_sock)
    12. {}
    13. //处理任务
    14. void ProcessOn()
    15. {
    16. handler(sock);
    17. }
    18. ~Task()
    19. {}
    20. };

    线程池构建

    之前在别的章节里面有完整解释过线程池,在这里就不过多解释了,直接上代码。

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include "Log.hpp"
    6. #include "Task.hpp"
    7. #define NUM 6
    8. class ThreadPool{
    9. private:
    10. int num;
    11. bool stop;
    12. std::queue task_queue;
    13. pthread_mutex_t lock;
    14. pthread_cond_t cond;
    15. ThreadPool(int _num = NUM):num(_num),stop(false)
    16. {
    17. pthread_mutex_init(&lock, nullptr);
    18. pthread_cond_init(&cond, nullptr);
    19. }
    20. ThreadPool(const ThreadPool &){}
    21. static ThreadPool *single_instance;
    22. public:
    23. static ThreadPool* getinstance()
    24. {
    25. static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
    26. if(single_instance == nullptr){
    27. pthread_mutex_lock(&_mutex);
    28. if(single_instance == nullptr){
    29. single_instance = new ThreadPool();
    30. single_instance->InitThreadPool();
    31. }
    32. pthread_mutex_unlock(&_mutex);
    33. }
    34. return single_instance;
    35. }
    36. bool IsStop()
    37. {
    38. return stop;
    39. }
    40. bool TaskQueueIsEmpty()
    41. {
    42. return task_queue.size() == 0 ? true : false;
    43. }
    44. void Lock()
    45. {
    46. pthread_mutex_lock(&lock);
    47. }
    48. void Unlock()
    49. {
    50. pthread_mutex_unlock(&lock);
    51. }
    52. void ThreadWait()
    53. {
    54. pthread_cond_wait(&cond, &lock);
    55. }
    56. void ThreadWakeup()
    57. {
    58. pthread_cond_signal(&cond);
    59. }
    60. static void *ThreadRoutine(void *args)
    61. {
    62. ThreadPool *tp = (ThreadPool*)args;
    63. while(true){
    64. Task t;
    65. tp->Lock();
    66. while(tp->TaskQueueIsEmpty()){
    67. tp->ThreadWait(); //当我醒来的时候,一定是占有互斥锁的!
    68. }
    69. tp->PopTask(t);
    70. tp->Unlock();
    71. t.ProcessOn();
    72. }
    73. }
    74. bool InitThreadPool()
    75. {
    76. for(int i = 0; i < num; i++){
    77. pthread_t tid;
    78. if( pthread_create(&tid, nullptr, ThreadRoutine, this) != 0){
    79. LOG(FATAL, "create thread pool error!");
    80. return false;
    81. }
    82. }
    83. LOG(INFO, "create thread pool success!");
    84. return true;
    85. }
    86. void PushTask(const Task &task) //in
    87. {
    88. Lock();
    89. task_queue.push(task);
    90. Unlock();
    91. ThreadWakeup();
    92. }
    93. void PopTask(Task &task) //out
    94. {
    95. task = task_queue.front();
    96. task_queue.pop();
    97. }
    98. ~ThreadPool()
    99. {
    100. pthread_mutex_destroy(&lock);
    101. pthread_cond_destroy(&cond);
    102. }
    103. };
    104. ThreadPool* ThreadPool::single_instance = nullptr;

    项目拓展:链接数据库

    1. #include
    2. #include
    3. #include "comm.hpp"
    4. #include "mysql.h"
    5. bool InsertSql(std::string sql)
    6. {
    7. MYSQL *conn = mysql_init(nullptr);
    8. mysql_set_character_set(conn, "utf8");
    9. if(nullptr == mysql_real_connect(conn, "127.0.0.1", "http_test", "12345678", "http_test", 3306, nullptr, 0)){
    10. std::cerr << "connect error!" << std::endl;
    11. return 1;
    12. }
    13. std::cerr << "connect success" << std::endl;
    14. std::cerr << "query : " << sql <
    15. int ret = mysql_query(conn, sql.c_str());
    16. std::cerr << "result: " << ret << std::endl;
    17. mysql_close(conn);
    18. return true;
    19. }
    20. int main()
    21. {
    22. std::string query_string;
    23. //from http
    24. if(GetQueryString(query_string)){
    25. std::cerr << query_string << std::endl;
    26. //数据处理?
    27. //name=tom&passwd=111111
    28. std::string name;
    29. std::string passwd;
    30. CutString(query_string, "&", name, passwd);
    31. std::string _name;
    32. std::string sql_name;
    33. CutString(name, "=", _name, sql_name);
    34. std::string _passwd;
    35. std::string sql_passwd;
    36. CutString(passwd, "=", _passwd, sql_passwd);
    37. std::string sql = "insert into user(name, passwd) values (\'";
    38. sql += sql_name;
    39. sql += "\',\'";
    40. sql += sql_passwd;
    41. sql += "\')";
    42. //插入数据库
    43. if(InsertSql(sql)){
    44. std::cout << "";
    45. std::cout << "";
    46. std::cout << "

      注册成功!

      "
      ;
    47. }
    48. }
    49. return 0;
    50. }

  • 相关阅读:
    动态规划:13目标和
    控制工程学 en
    python自动化Selenium的使用
    基于Java+SpringBoot+Vue前后端分离社区养老服务平台设计和实现
    Java基础篇 数组
    ServletConfig接口干什么
    MeterSphere 之 Idea插件开发
    机器学习中的决策树算法
    MyBatis Like 拼接
    Leetcode 64. 最小路径和 动态规划+空间优化
  • 原文地址:https://blog.csdn.net/m0_61703823/article/details/127194326