编程实战:自己编写HTTP服务器(系列2:请求)

系列入口:编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客

        本文介绍如何处理请求。

目录

一、概述

二、成员变量

三、接收并分解请求

四、完整代码

五、HTTP处理框架


一、概述

        请求和应答结构其实差不多,但是代码就多了很多分析过程(应答只需要拼接,简单多了)。

        应答的代码:

        整个类从RecvRequest()开始,然后就是各种Get。显然嘛,服务器收到的请求对服务器来说是个只读信息,修改也没啥用啊。

二、成员变量

	private:bool m_isCgi;//是否是CGIstring m_fullrequest;//完整请求,包含了状态行、头标、数据long m_contentlength;//内容长度string m_method;//请求方法,GET、POST等string m_resource;//请求资源,/dir/file,不包括QueryStringstring m_querystring;//查询字符串PARAMLIST_T m_params;//参数表,QueryString,Form Datamap<string, string > m_cookies;string m_content_type;//内容类型string m_content;//内容

        与CGI有关的可以无视,CGI没什么用,因为没人再写CGI了,所以支持CGI也没什么用。

        分解出的各种元素就在这些变量里,有一些内容是不同的分解程度。比如查询字符串同时被分解为参数,form数据也被分解为参数。虽然一般查询字符串和form data是不同的处理方式,但是其实我们都很希望在代码里能同样对待。

        从性能角度来说,这样预分解不是一个好策略,最佳方式是用到什么去找什么,因为大部分内容其实是用不到的。

三、接收并分解请求

        代码如下:

		//接收请求,此调用成功后才能调用GetXXXX//pFullRequest不为空直接从pFullRequest中分析而不需要从s接收bool RecvRequest(CSocket & s, char const * pFullRequest = NULL, bool(*pfNeedBrek)() = NULL){Clear();m_isCgi = false;long timeout = 300;//接收超时string::size_type headlen = 0;char buf[1024];long count;string::size_type pos;if (NULL != pFullRequest)m_fullrequest = pFullRequest;//先接收请求头while (m_fullrequest.npos == (headlen = m_fullrequest.find("\r\n\r\n"))){if (!_Recv(s, timeout, buf, 1023, &count, pfNeedBrek)){//LOG<<"recv error"<<ENDE;s.Close();return false;}if (0 == count){//LOG<<"client closed"<<ENDE;s.Close();return false;}buf[count] = '\0';m_fullrequest += buf;}//获得第一行pos = m_fullrequest.find("\r\n");string str;str = m_fullrequest.substr(0, pos);//获得methodpos = str.find(" ");if (str.npos == pos){return false;}this->m_method = str.substr(0, pos);str.erase(0, pos + 1);//去掉http版本pos = str.find_last_of(" ");if (str.npos == pos){return false;}str.erase(pos);//剩下的是URL,问号之后的是GET参数pos = str.find_first_of("?");if (str.npos != pos){this->m_resource = str.substr(0, pos);str.erase(0, pos + 1);m_querystring = str;}else{this->m_resource = str;}//分析CookieAnalyzeCookie(m_fullrequest);//如果存在请求内容则接收请求内容pos = m_fullrequest.find("Content-Length:");if (m_fullrequest.npos == pos || pos >= headlen){m_contentlength = 0;m_content = "";}else{m_contentlength = atol(m_fullrequest.c_str() + pos + strlen("Content-Length:"));while (m_fullrequest.size() < headlen + strlen("\r\n\r\n") + m_contentlength){if (!_Recv(s, timeout, buf, 1023, &count, pfNeedBrek)){LOG << "recv error" << ENDE;s.Close();return false;}if (0 == count){LOG << "client closed" << ENDE;s.Close();return false;}buf[count] = '\0';m_fullrequest += buf;}m_content = m_fullrequest.substr(headlen + strlen("\r\n\r\n"), m_contentlength);}//根据内容类型处理数据m_content_type = GetHeader("Content-Type");Trim(m_content_type);if ("application/x-www-form-urlencoded" == m_content_type){if (m_querystring.size() != 0)m_querystring += "&";m_querystring += m_content;}else if (isContentTypeXml(m_content_type)){DEBUG_LOG << "content is xml" << ENDI;}else{}this->AnalyzeParam(m_querystring);return true;}

        _Recv()从socket接收数据:

		//出错或需要退出返回false,seconds为0不超时bool _Recv(CSocket & s, long seconds, char * buf, int buflen, long * pReadCount, bool(*pfNeedBrek)() = NULL){bool isReady = false;if (!s.IsSocketReadReady2(seconds, isReady, pfNeedBrek)){LOG << "CSocket error" << ENDE;return false;}if (!isReady){DEBUG_LOG << "CSocket timeout" << ENDI;return false;}else{DEBUG_LOG << "CSocket ready" << ENDI;}return s.Recv(buf, buflen, pReadCount);}

        用的是一个socket包装类,从我的另一篇文章里可以获得源代码。

        接受请求的代码虽然比较长,但是也没什么难点。

四、完整代码

        完整代码如下:

	//HTTP请求class CHttpRequest{public:typedef vector<pair<string, string > > PARAMLIST_T;static bool AnalyzeParam(string const & params, PARAMLIST_T & ret){ret.clear();if (0 == params.size())return true;string tmpparams = params + "&";string::size_type pos;while (tmpparams.npos != (pos = tmpparams.find("&"))){string str = tmpparams.substr(0, pos);string::size_type pos2;if (str.npos != (pos2 = str.find("="))){ret.push_back(pair<string, string >(CHtmlDoc::URLDecode(str.substr(0, pos2)), CHtmlDoc::URLDecode(str.substr(pos2 + 1))));}tmpparams.erase(0, pos + 1);}return true;}private:bool m_isCgi;//是否是CGIstring m_fullrequest;//完整请求,包含了状态行、头标、数据long m_contentlength;//内容长度string m_method;//请求方法,GET、POST等string m_resource;//请求资源,/dir/file,不包括QueryStringstring m_querystring;//查询字符串PARAMLIST_T m_params;//参数表,QueryString,Form Datamap<string, string > m_cookies;string m_content_type;//内容类型string m_content;//内容//出错或需要退出返回false,seconds为0不超时bool _Recv(CSocket & s, long seconds, char * buf, int buflen, long * pReadCount, bool(*pfNeedBrek)() = NULL){bool isReady = false;if (!s.IsSocketReadReady2(seconds, isReady, pfNeedBrek)){LOG << "CSocket error" << ENDE;return false;}if (!isReady){DEBUG_LOG << "CSocket timeout" << ENDI;return false;}else{DEBUG_LOG << "CSocket ready" << ENDI;}return s.Recv(buf, buflen, pReadCount);}bool AnalyzeParam(string const & params){return AnalyzeParam(params, m_params);}void AnalyzeCookie(string const & request){string COOKIE = "Cookie: ";string::size_type pos_start;string::size_type pos_end;pos_start = request.find(COOKIE);if (request.npos == pos_start)return;pos_end = request.find("\r\n", pos_start);if (request.npos == pos_end)return;string str = request.substr(pos_start + COOKIE.size(), pos_end - (pos_start + COOKIE.size()));CStringSplit st(str.c_str(), ";");for (CStringSplit::iterator it = st.begin(); it != st.end(); ++it){CStringSplit st2(it->c_str(), "=");if (st2.size() != 2)continue;m_cookies[st2[0]] = st2[1];}}public:void Clear(){m_isCgi = false;m_fullrequest = "";m_contentlength = 0;m_method = "";m_resource = "";m_querystring = "";m_params.clear();m_cookies.clear();m_content_type = "";m_content = "";}map<string, string > const & GetCookies()const { return m_cookies; }string const & GetResource()const { return m_resource; }//获得请求资源,/dir/file,不包括QueryStringstring const & GetQueryString()const { return m_querystring; }//获得QueryStringstring const & GetMethod()const { return m_method; }//获得请求方法string const & GetContentType()const { return m_content_type; }//获得内容类型string const & GetContent()const { return m_content; }//获得内容//获得请求资源的后缀名string GetResourceType()const{long pos = m_resource.size() - 1;while (pos >= 0 && m_resource[pos] != '.' && m_resource[pos] != '/')--pos;if (pos < 0 || m_resource[pos] == '/')return "";return m_resource.substr(pos + 1);}//获得请求资源的文件名string GetResourceFileTitle()const{size_t start = m_resource.find_last_of('/');size_t end = m_resource.find_last_of('.');if (start != m_resource.npos && end != m_resource.npos){if (end > start + 1)return m_resource.substr(start + 1, end - start - 1);}return "";}string const & GetFullRequest()const { return m_fullrequest; }//获得完整请求long GetContentLength()const { return m_contentlength; }//获得contentlengthstring const & GetParam(string const & param)const//获得参数,如果参数有重复则只能取得第一个{STATIC_C string const static_str;string const * p = GetParam(param, m_params);if (NULL != p)return *p;else return static_str;}static string const * GetParam(string const & param, PARAMLIST_T const & params)//获得参数,如果参数有重复则只能取得第一个{string _param = param;for (string::size_type i = 0; i < params.size(); ++i){string _first = params[i].first;if (0 == stricmp(_param.c_str(), _first.c_str()))return &params[i].second;}return NULL;}string GetCookie(string const & cookie)const{map<string, string >::const_iterator it = this->m_cookies.find(cookie);if (it != this->m_cookies.end())return it->second;else return "";}string GetHeader(char const * _header)const{if (m_isCgi){//获得HTTP头标,实际上是从环境变量中直接获取char const * penv = getenv(_header);if (NULL != penv)return penv;else return "";}else{string header = _header;header += ": ";string::size_type pos_start;string::size_type pos_end;if (m_fullrequest.npos == (pos_start = m_fullrequest.find(header)))return "";if (m_fullrequest.npos == (pos_end = m_fullrequest.find("\r\n", pos_start)))return "";string ret = m_fullrequest.substr(pos_start + header.size(), pos_end - pos_start - header.size());return Trim(ret);}}//替换Headerstatic string & ReplaceHeader(string const & oldrequest, char const * _header, long _value, string & newrequest){char buf[256];sprintf(buf, "%ld", _value);return ReplaceHeader(oldrequest, _header, buf, newrequest);}//替换Headerstatic string & ReplaceHeader(string const & oldrequest, char const * _header, char const * _value, string & newrequest){newrequest = oldrequest;string header = _header;header += ": ";string::size_type pos_start;string::size_type pos_end;if (newrequest.npos != (pos_start = newrequest.find(header))){if (newrequest.npos != (pos_end = newrequest.find("\r\n", pos_start))){newrequest.replace(pos_start + header.size(), pos_end - pos_start - header.size(), _value);return newrequest;}else{LOG << "格式错误,没有行结束符" << ENDE;return newrequest = "";}}else{if (newrequest.npos != (pos_start = newrequest.find("\r\n"))){newrequest.insert(pos_start + strlen("\r\n"), header + _value + "\r\n");return newrequest;}else{LOG << "格式错误,没有行结束符" << ENDE;return newrequest = "";}}}bool GetAuthorization(string & user, string & password)const{string AUTHORIZATION = "Authorization: Basic ";string::size_type pos_start;string::size_type pos_end;string base64;if (m_fullrequest.npos == (pos_start = m_fullrequest.find(AUTHORIZATION)))return false;if (m_fullrequest.npos == (pos_end = m_fullrequest.find("\r\n", pos_start)))return false;base64 = m_fullrequest.substr(pos_start + AUTHORIZATION.size(), pos_end - pos_start - AUTHORIZATION.size());char buf[2048];int len;if (0 > (len = CBase64::Base64Dec(buf, base64.c_str(), (int)base64.size()))){LOG << "base64解码错误" << ENDE;}buf[len] = '\0';DEBUG_LOG << buf << ENDI;CStringSplit st(buf, ":");if (st.size() != 2)return false;user = st[0];password = st[1];return true;}PARAMLIST_T GetParamList(PARAMLIST_T & params)const { return params = m_params; }//获得参数表,<参数名,参数值>数组//接收请求,此调用成功后才能调用GetXXXX//pFullRequest不为空直接从pFullRequest中分析而不需要从s接收bool RecvRequest(CSocket & s, char const * pFullRequest = NULL, bool(*pfNeedBrek)() = NULL){Clear();m_isCgi = false;long timeout = 300;//接收超时string::size_type headlen = 0;char buf[1024];long count;string::size_type pos;if (NULL != pFullRequest)m_fullrequest = pFullRequest;//先接收请求头while (m_fullrequest.npos == (headlen = m_fullrequest.find("\r\n\r\n"))){if (!_Recv(s, timeout, buf, 1023, &count, pfNeedBrek)){//LOG<<"recv error"<<ENDE;s.Close();return false;}if (0 == count){//LOG<<"client closed"<<ENDE;s.Close();return false;}buf[count] = '\0';m_fullrequest += buf;}//获得第一行pos = m_fullrequest.find("\r\n");string str;str = m_fullrequest.substr(0, pos);//获得methodpos = str.find(" ");if (str.npos == pos){return false;}this->m_method = str.substr(0, pos);str.erase(0, pos + 1);//去掉http版本pos = str.find_last_of(" ");if (str.npos == pos){return false;}str.erase(pos);//剩下的是URL,问号之后的是GET参数pos = str.find_first_of("?");if (str.npos != pos){this->m_resource = str.substr(0, pos);str.erase(0, pos + 1);m_querystring = str;}else{this->m_resource = str;}//分析CookieAnalyzeCookie(m_fullrequest);//如果存在请求内容则接收请求内容pos = m_fullrequest.find("Content-Length:");if (m_fullrequest.npos == pos || pos >= headlen){m_contentlength = 0;m_content = "";}else{m_contentlength = atol(m_fullrequest.c_str() + pos + strlen("Content-Length:"));while (m_fullrequest.size() < headlen + strlen("\r\n\r\n") + m_contentlength){if (!_Recv(s, timeout, buf, 1023, &count, pfNeedBrek)){LOG << "recv error" << ENDE;s.Close();return false;}if (0 == count){LOG << "client closed" << ENDE;s.Close();return false;}buf[count] = '\0';m_fullrequest += buf;}m_content = m_fullrequest.substr(headlen + strlen("\r\n\r\n"), m_contentlength);}//根据内容类型处理数据m_content_type = GetHeader("Content-Type");Trim(m_content_type);if ("application/x-www-form-urlencoded" == m_content_type){if (m_querystring.size() != 0)m_querystring += "&";m_querystring += m_content;}else if (isContentTypeXml(m_content_type)){DEBUG_LOG << "content is xml" << ENDI;}else{}this->AnalyzeParam(m_querystring);return true;}static bool isContentTypeXml(string const & content_type){return "Text/xml" == content_type;}bool InitCgiRequest(int argc, char ** argv){Clear();m_isCgi = true;string tmp;thelog << "argc " << argc << endi;if (argc >= 2){thelog << m_resource << endi;m_resource = argv[1];}if (argc >= 3){tmp = argv[2];thelog << tmp << endi;m_querystring = tmp;AnalyzeParam(m_querystring);}char const * penv;if (NULL != (penv = getenv("REQUEST_METHOD"))){m_method = penv;}if (NULL != (penv = getenv("QUERY_STRING"))){tmp = penv;AnalyzeParam(tmp);}//如果是POST,内容从标准输入获取,因为没有文件结束符,长度必须从头标获取if (NULL != (penv = getenv("CONTENT_LENGTH"))){char * p = NULL;int content_length = atol(penv);p = new char[content_length + 1];if (NULL == p)return false;if (content_length != read(STDIN_FILENO, p, content_length))return false;p[content_length] = '\0';tmp = p;AnalyzeParam(tmp);delete[] p;}if (NULL != (penv = getenv("REQUEST_URI"))){tmp = penv;string::size_type pos = tmp.find_first_of("?");if (tmp.npos != pos){m_resource = tmp.substr(0, pos);}else{m_resource = tmp;}}return true;}string RequestHtmlReport()const//报告请求信息,HTML格式{string ret = "HttpRequest:<HR/>";ret += m_method;ret += "&nbsp;";ret += m_resource;ret += "<BR>\n";for (PARAMLIST_T::size_type i = 0; i < m_params.size(); ++i){ret += CHtmlDoc::HTMLEncode(m_params[i].first);ret += " = ";ret += CHtmlDoc::HTMLEncode(m_params[i].second);ret += "<BR>\n";}map<string, string >::const_iterator it;for (it = m_cookies.begin(); it != m_cookies.end(); ++it){ret += "Cookie: ";ret += it->first + "=" + it->second + "<BR>\n";}ret += "<HR/>";time_t t1 = time(NULL);ret += asctime(localtime(&t1));return ret;}};

五、HTTP处理框架

        待续

(这里是结束,但是并不是整个系列的结束)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/257204.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电子学会C/C++编程等级考试2021年03月(五级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:最小新整数 给定一个十进制正整数n(0 < n < 1000000000),每个数位上数字均不为0。n的位数为m。 现在从m位中删除k位(0<k < m),求生成的新整数最小为多少? 例如: n = 9128456, k = 2, 则生成的新整数最小为12456…

导入JDBC元数据到Apache Atlas

前言 前期实现了导入MySQL元数据到Apache Atlas, 由于是初步版本&#xff0c;且功能参照Atlas Hive Hook&#xff0c;实现的不够完美 本期对功能进行改进&#xff0c;实现了导入多种关系型数据库元数据到Apache Atlas 数据库schema与catalog 按照SQL标准的解释&#xff0c;…

DDD架构思想专栏一《初识领域驱动设计DDD落地》

引言 最近准备给自己之前写的项目做重构&#xff0c;这是一个单体架构的小项目&#xff0c;后端采用的是最常见的三层架构。因为项目比较简单&#xff0c;其实采用三层架构就完全够了。但是呢&#xff0c;小编最近在做DDD架构的项目&#xff0c;于是就先拿之前写的一个老项目试…

使用React 18、Echarts和MUI实现温度计

关键词 React 18 Echarts和MUI 前言 在本文中&#xff0c;我们将结合使用React 18、Echarts和MUI&#xff08;Material-UI&#xff09;库&#xff0c;展示如何实现一个交互性的温度计。我们将使用Echarts绘制温度计的外观&#xff0c;并使用MUI创建一个漂亮的用户界面。 本文…

pytorch优化之SAM优化器

1. SAM介绍 人机验证 2. 案例 ❀精度优化❀优化策略1&#xff1a;网络SAM优化器_夏天&#xff5c;여름이다的博客-CSDN博客文章浏览阅读3.3k次&#xff0c;点赞10次&#xff0c;收藏30次。精度优化策略&#xff1a;SAM:Sharpness AwarenessMinimization锐度感知最小化论文&…

css 十字分割线(含四等分布局)

核心技术 伪类选择器含义li:nth-child(2)第2个 lili:nth-child(n)所有的lili:nth-child(2n)所有的第偶数个 lili:nth-child(2n1)所有的第奇数个 lili:nth-child(-n5)前5个 lili:nth-last-child(-n5)最后5个 lili:nth-child(7n)选中7的倍数 border-right: 3px solid white;borde…

Java 简易版 TCP UDP聊天

客户端 import java.io.*; import java.net.Socket; import java.util.Date; import javax.swing.*;public class MyClient {private JFrame jf;private JButton jBsend;private JTextArea jTAcontent;private JTextField jText;private JLabel JLcontent;private Date data;pr…

vue3日常知识点学习归纳

1&#xff0c;父子组件传递&#xff1a; 父组件传递参数 <template><div><!-- 子组件 参数&#xff1a;num 、nums --><child :num"nums.num" :doubleNum"nums.doubleNum" increase"handleIncrease"></child>&l…

HarmonyOS4.0从零开始的开发教程10管理组件状态

HarmonyOS&#xff08;八&#xff09;管理组件状态 概述 在应用中&#xff0c;界面通常都是动态的。如图1所示&#xff0c;在子目标列表中&#xff0c;当用户点击目标一&#xff0c;目标一会呈现展开状态&#xff0c;再次点击目标一&#xff0c;目标一呈现收起状态。界面会根…

【Proteus仿真】【STM32单片机】蓝牙遥控小车

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使LCD1602液晶&#xff0c;L298电机&#xff0c;直流电机&#xff0c;HC05/06蓝牙模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显…

LAMP和分离式LNMP部署

目录 一.什么是LAMP&#xff1f; 二.安装LAMP 先安装apache&#xff0c;httpd网页服务&#xff1a; 接着安装mysql&#xff1a; 安装php&#xff1a; 创建论坛&#xff1a; 三.安装分布式LNMP&#xff1a; 先安装nginx&#xff1a; 到另一台主机安装php&#xff1a; …

论文阅读:LSeg: LANGUAGE-DRIVEN SEMANTIC SEGMENTATION

可以直接bryanyzhu的讲解&#xff1a;CLIP 改进工作串讲&#xff08;上&#xff09;【论文精读42】_哔哩哔哩_bilibili 这里是详细的翻译工作 原文链接 https://arxiv.org/pdf/2201.03546.pdf ICLR 2022 0、ABSTRACT 我们提出了一种新的语言驱动的语义图像分割模型LSeg。…