系列入口:编程实战:自己编写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 ¶ms[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 += " ";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处理框架
待续
(这里是结束,但是并不是整个系列的结束)