Nginx网络epoll多进程系列:应用层协议实现系列(二)——HTTP服务器之http协议解析

166 阅读4分钟

blog.csdn.net/zhaoxy_thu/…

上一篇文章《仿nginx Http服务器的设计与实现(一)——多进程和多路IO的实现》中实现了一个仿照nginx的支持高并发的服务器,但只是实现了端口监听和数据接收,并没有实现对http协议的解析,下面就对如何解析http协议进行说明。

我们可以通过浏览器访问之前所搭建的http服务器,可以看到终端输出如下:

[plain]  view plain  copy

  1. GET / HTTP/1.1  
  2. Host: 127.0.0.1:8080  
  3. Connection: keep-alive  
  4. Cache-Control: max-age=0  
  5. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8  
  6. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36  
  7. Accept-Encoding: gzip,deflate,sdch  
  8. Accept-Language: zh-CN,zh;q=0.8  


参考一些网上的资料可以知道,http协议主要有三部分组成,即请求行、若干请求字段、请求体。请求行主要包括所使用的http方法,访问的路径以及http的版本。请求字段主要包括若干个详细说明本次http请求的字段,每个字段由字段名+冒号+空格+字段值组成。请求体主要包括发送到客户端的数据。其中请求行和请求字段之间是连续的,而请求字段与请求体之间会有两个空白行(\r\n)分隔。

在明确了这些内容之后,我们就可以开始对接收到的http请求进行解析了。本文将使用两个类,CHttpRequest和CHttpResponse来实现这一功能。下面首先修改上一篇文章中的

handleRequest方法:

[cpp]  view plain  copy

  1. //处理http请求  
  2. bool handleRequest(int connFd) {  
  3.     if (connFd<=0) return false;  
  4.     //读取缓存  
  5.     char buff[4096];  
  6.     //读取http header  
  7.     int len = (int)recv(connFd, buff, sizeof(buff), 0);  
  8.     if (len<=0) {  
  9.         return false;  
  10.     }  
  11.     buff[len] = '\0';  
  12.     std::cout<<buff<<std::endl;  
  13.   
  14.     CHttpRequest *httpRequest = new CHttpRequest();  
  15.     httpRequest->handleRequest(buff);  
  16.     CHttpResponse *httpResponse = new CHttpResponse(httpRequest);  
  17.     bool result = httpResponse->response(connFd);  
  18.     //返回是否需要中断连接  
  19.     std::string transformConnection(httpRequest->connection);  
  20.     std::transform(transformConnection.begin(), transformConnection.end(), transformConnection.begin(), ::tolower);  
  21.     return transformConnection == "Keep-Alive" && result;  
  22. }  


该代码中采用了一个长度为4096的缓冲区接收http头,接收完成之后,调用CHttpRequest进行解析。下面来看看CHttpRequest的代码:

[cpp]  view plain  copy

  1. #include "CHttpRequest.h"  
  2. #include "define.h"  
  3.   
  4. using namespace std;  
  5.   
  6. CHttpRequest::CHttpRequest() {  
  7.     connection = "Close";  
  8.     modifiedTime = "";  
  9.     fileStart = 0;  
  10.     fileEnd = 0;  
  11.       
  12.     fieldMap[TS_HTTP_HEADER_CONNECTION] = &CHttpRequest::handleConnection;  
  13.     fieldMap[TS_HTTP_HEADER_AUTHORIZATION] = &CHttpRequest::handleAuthorization;  
  14.     fieldMap[TS_HTTP_HEADER_RANGE] = &CHttpRequest::handleRange;  
  15.     fieldMap[TS_HTTP_HEADER_IF_MOD_SINCE] = &CHttpRequest::handleIfModSince;  
  16. }  
  17.   
  18. void CHttpRequest::handleRequest(char *header) {  
  19.     stringstream stream;  
  20.     stream<<header;  
  21.       
  22.     int count = 0;  
  23.     while (1) {  
  24.         if (stream.eof()) {  
  25.             break;  
  26.         }  
  27.         char line[1024];  
  28.         stream.getline(line, sizeof(line));  
  29.         if (strcmp(line, "")==0) {  
  30.             continue;  
  31.         }  
  32.           
  33.         stringstream lineStream;  
  34.         lineStream<<line;  
  35.         //first line  
  36.         if (count == 0) {  
  37.             lineStream>>method;  
  38.             lineStream>>path;  
  39.             lineStream>>version;  
  40.         }else {  
  41.             string fieldName;  
  42.             lineStream>>fieldName;  
  43.             //remove \r  
  44.             line[strlen(line)-1] = '\0';  
  45.             void(CHttpRequest::*func)(char*) = fieldMap[fieldName];  
  46.             if (func!=NULL) {  
  47.                 (this->*func)(line+fieldName.length()+1);  
  48.             }  
  49.         }  
  50.         count++;  
  51.     }  
  52. }  
  53.   
  54. void CHttpRequest::handleConnection(char *field) {  
  55.     if (ENABLE_KEEP_ALIVE) {  
  56.         connection = string(field);  
  57.     }  
  58. }  
  59.   
  60. void CHttpRequest::handleAuthorization(char *field) {  
  61.     char authName[10], authInfo[256];  
  62.     sscanf(field, "%s %s", authName, authInfo);  
  63.     authorize = string(authInfo);  
  64. }  
  65.   
  66. void CHttpRequest::handleRange(char *field) {  
  67.     if (strstr(field, "bytes=")==field) {  
  68.         char *start = strtok(field+strlen("bytes="), "-");  
  69.         fileStart = start==NULL?0:atol(start);  
  70.         char *end = strtok(NULL, "-");  
  71.         fileEnd = end==NULL?0:atol(end);  
  72.     }  
  73. }  
  74.   
  75. void CHttpRequest::handleIfModSince(char *field) {  
  76.     modifiedTime = string(field);  
  77. }  


为了保证http解析的效率,本文采用了与nginx中类似的做法,将字段名与解析函数放到了map中(nginx中使用的是hash表,在这里简化为map)。在解析完成之后,调用CHttpResponse构造响应。CHttpResponse代码如下:

[cpp]  view plain  copy

  1. #include "CHttpResponse.h"  
  2. #include "CHttpRequest.h"  
  3. #include <sys/socket.h>  
  4. #include "define.h"  
  5. #include <string.h>  
  6.   
  7. #define HTTP_RESPONSE_404 "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>"  
  8.   
  9. std::string getStringFromTime(time_t time) {  
  10.     char timeBuff[64];  
  11.     struct tm tm = *gmtime(&time);  
  12.     strftime(timeBuff, sizeof timeBuff, "%a, %d %b %Y %H:%M:%S %Z", &tm);  
  13.     return std::string(timeBuff);  
  14. }  
  15.   
  16. CHttpResponse::CHttpResponse(CHttpRequest *request) {  
  17.     m_request = request;  
  18.     if (m_request->method.compare(TS_HTTP_METHOD_GET_S)==0 || m_request->method.compare(TS_HTTP_METHOD_HEAD_S)==0) {  
  19.         std::string path = ROOT_PATH;  
  20.         if (m_request->path.compare("/")==0) {  
  21.             path += ROOT_HTML;  
  22.         }else {  
  23.             path += m_request->path;  
  24.         }  
  25.           
  26.         m_statusCode = 0;  
  27.         //if file exist  
  28.         if (isFileExist(path.c_str())) {  
  29.             //if receive modified time  
  30.             if (!m_request->modifiedTime.empty()) {  
  31.                 time_t time = fileModifiedTime(path.c_str());  
  32.                 if (getStringFromTime(time) == m_request->modifiedTime) {  
  33.                     m_statusCode = TS_HTTP_STATUS_NOT_MODIFIED;  
  34.                     m_statusMsg = TS_HTTP_STATUS_NOT_MODIFIED_S;  
  35.                 }  
  36.             }  
  37.             //if file modified  
  38.             if (m_statusCode == 0) {  
  39.                 if (m_request->fileStart || m_request->fileEnd) {  
  40.                     long long fileSize = getFileSize(path.c_str());  
  41.                     //if request range satisfied  
  42.                     if (m_request->fileStart<fileSize && m_request->fileEnd<fileSize) {  
  43.                         m_statusCode = TS_HTTP_STATUS_PARTIAL_CONTENT;  
  44.                         m_statusMsg = TS_HTTP_STATUS_PARTIAL_CONTENT_S;  
  45.                         m_sendFilePath = path;  
  46.                     }else {  
  47.                         m_statusCode = TS_HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE;  
  48.                         m_statusMsg = TS_HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE_S;  
  49.                     }  
  50.                 }else {  
  51.                     m_statusCode = TS_HTTP_STATUS_OK;  
  52.                     m_statusMsg = TS_HTTP_STATUS_OK_S;  
  53.                     m_sendFilePath = path;  
  54.                 }  
  55.             }  
  56.         } else {  
  57.             m_statusCode = TS_HTTP_STATUS_NOT_FOUND;  
  58.             m_statusMsg = TS_HTTP_STATUS_NOT_FOUND_S;  
  59.             m_sendStr = HTTP_RESPONSE_404;  
  60.         }  
  61.     }  
  62. }  
  63.   
  64. bool CHttpResponse::response(int connFd) {  
  65.     bool result = true;  
  66.     std::stringstream responseStream;  
  67.       
  68.     responseStream<<m_request->version<<" "<<m_statusMsg<<"\r\n";  
  69.     //time  
  70.     responseStream<<"Date: "<<getStringFromTime(time(0))<<"\r\n";  
  71.       
  72.     //server name  
  73.     responseStream<<"Server: "<<SERVER_NAME<<"\r\n";  
  74.       
  75.     //keep alive  
  76.     responseStream<<"Connection: "<<m_request->connection<<"\r\n";  
  77.       
  78.     //content length  
  79.     long long contentLength = 0;  
  80.     //if file exist  
  81.     if (!m_sendFilePath.empty()) {  
  82.         //if define file end  
  83.         if (m_request->fileEnd) {  
  84.             contentLength = m_request->fileEnd - m_request->fileStart + 1;  
  85.         }  
  86.         //if define file start  
  87.         else if (m_request->fileStart) {  
  88.             contentLength = getFileSize(m_sendFilePath.c_str()) - m_request->fileStart + 1;  
  89.         }  
  90.         //if undefine start or end  
  91.         else {  
  92.             contentLength = getFileSize(m_sendFilePath.c_str());  
  93.         }  
  94.     } else if (!m_sendStr.empty()) {  
  95.         contentLength = m_sendStr.length();  
  96.     }  
  97.     if (contentLength) {  
  98.         responseStream<<"Content-Length: "<<contentLength<<"\r\n";  
  99.     }  
  100.       
  101.     //last modified  
  102.     if (!m_sendFilePath.empty()) {  
  103.         responseStream<<"Last-Modified: "<<getStringFromTime(fileModifiedTime(m_sendFilePath.c_str()))<<"\r\n";  
  104.           
  105.         responseStream<<"Accept-Ranges: "<<"bytes"<<"\r\n";  
  106.     }  
  107.       
  108.     //content type  
  109.     if (!m_sendFilePath.empty()) {  
  110.         char path[256];  
  111.         strcpy(path, m_sendFilePath.c_str());  
  112.         char *ext = strtok(path, ".");  
  113.         char *lastExt = ext;  
  114.         while (ext!=NULL) {  
  115.             ext = strtok(NULL, ".");  
  116.             if (ext) lastExt = ext;  
  117.         }  
  118.         for (int i=0; i<38; i++) {  
  119.             if (strcmp(mmt[i].ext, lastExt)==0) {  
  120.                 responseStream<<"Content-Type: "<<mmt[i].type<<"\r\n";  
  121.                 break;  
  122.             }  
  123.         }  
  124.     }  
  125.       
  126.     //other  
  127.     switch (m_statusCode) {  
  128.         case TS_HTTP_STATUS_UNAUTHORIZED:  
  129.             responseStream<<"WWW-Authenticate: Basic realm=\"zhaoxy.com\"\r\n";  
  130.             break;  
  131.         case TS_HTTP_STATUS_FOUND:  
  132.             responseStream<<"Location: /index.html\r\n";  
  133.             break;  
  134.         case TS_HTTP_STATUS_PARTIAL_CONTENT:  
  135.             responseStream<<"Content-Range: "<<"bytes "<<m_request->fileStart<<"-"<<(m_request->fileEnd==0?contentLength:m_request->fileEnd)<<"/"<<getFileSize(m_sendFilePath.c_str())<<"\r\n";  
  136.             break;  
  137.         default:  
  138.             break;  
  139.     }  
  140.       
  141.     //seperator  
  142.     responseStream<<"\r\n";  
  143.       
  144.     //send response header  
  145.     std::string responseStr = responseStream.str();  
  146.     std::cout<<responseStr<<std::endl;  
  147.       
  148.     send(connFd, responseStr.c_str(), responseStr.length(), 0);  
  149.       
  150.     //content  
  151.       
  152.     //if not head method  
  153.     if (m_request->method.compare(TS_HTTP_METHOD_HEAD_S)!=0) {  
  154.         if (!m_sendFilePath.empty()) {  
  155.             std::ifstream file(m_sendFilePath);  
  156.             file.seekg(m_request->fileStart, std::ifstream::beg);  
  157.             while(file.tellg() != -1)  
  158.             {  
  159.                 char *p = new char[1024];  
  160.                 bzero(p, 1024);  
  161.                 file.read(p, 1024);  
  162.                 int n = (int)send(connFd, p, 1024, 0);  
  163.                 if (n < 0) {  
  164.                     std::cout<<"ERROR writing to socket"<<std::endl;  
  165.                     result = false;  
  166.                     break;  
  167.                 }  
  168.                 delete p;  
  169.             }  
  170.             file.close();  
  171.         }else {  
  172.             send(connFd, m_sendStr.c_str(), m_sendStr.length(), 0);  
  173.         }  
  174.     }  
  175.       
  176.     return result;  
  177. }  


该代码支持断点续传、last modified和authorization字段。具体的逻辑不作详细说明,有疑问的可以留言。

该Http服务器的代码已经上传到GitHub上,大家可以直接下载

\

如果大家觉得对自己有帮助的话,还希望能帮顶一下,谢谢:)

个人博客: blog.csdn.net/zhaoxy2850

本文地址: blog.csdn.net/zhaoxy_thu/…

转载请注明出处,谢谢!

\