上一篇文章《抄nginx Httpserver设计与实现(一)——多进程和多通道IO现》中实现了一个仿照nginx的支持高并发的server。但仅仅是实现了port监听和数据接收。并没有实现对http协议的解析,以下就对怎样解析http协议进行说明。
我们能够通过浏览器訪问之前所搭建的httpserver,能够看到终端输出例如以下:
GET / HTTP/1.1 Host: 127.0.0.1:8080 Connection: keep-alive Cache-Control: max-age=0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 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 Accept-Encoding: gzip,deflate,sdch Accept-Language: zh-CN,zh;q=0.8
參考一些网上的资料能够知道,http协议主要有三部分组成,即请求行、若干请求字段、请求体。请求行主要包含所使用的http方法,訪问的路径以及http的版本号。请求字段主要包含若干个具体说明本次http请求的字段。每一个字段由字段名+冒号+空格+字段值组成。
请求体主要包含发送到client的数据。
当中请求行和请求字段之间是连续的,而请求字段与请求体之间会有两个空白行( )分隔。
在明白了这些内容之后,我们就能够開始对接收到的http请求进行解析了。本文将使用两个类,CHttpRequest和CHttpResponse来实现这一功能。
以下首先改动上一篇文章中的
handleRequest方法:
//处理http请求 bool handleRequest(int connFd) { if (connFd<=0) return false; //读取缓存 char buff[4096]; //读取http header int len = (int)recv(connFd, buff, sizeof(buff), 0); if (len<=0) { return false; } buff[len] = ' '; std::cout<<buff<<std::endl; CHttpRequest *httpRequest = new CHttpRequest(); httpRequest->handleRequest(buff); CHttpResponse *httpResponse = new CHttpResponse(httpRequest); bool result = httpResponse->response(connFd); //返回是否须要中断连接 std::string transformConnection(httpRequest->connection); std::transform(transformConnection.begin(), transformConnection.end(), transformConnection.begin(), ::tolower); return transformConnection == "Keep-Alive" && result; }
该代码中採用了一个长度为4096的缓冲区接收http头,接收完毕之后,调用CHttpRequest进行解析。
以下来看看CHttpRequest的代码:
#include "CHttpRequest.h" #include "define.h" using namespace std; CHttpRequest::CHttpRequest() { connection = "Close"; modifiedTime = ""; fileStart = 0; fileEnd = 0; fieldMap[TS_HTTP_HEADER_CONNECTION] = &CHttpRequest::handleConnection; fieldMap[TS_HTTP_HEADER_AUTHORIZATION] = &CHttpRequest::handleAuthorization; fieldMap[TS_HTTP_HEADER_RANGE] = &CHttpRequest::handleRange; fieldMap[TS_HTTP_HEADER_IF_MOD_SINCE] = &CHttpRequest::handleIfModSince; } void CHttpRequest::handleRequest(char *header) { stringstream stream; stream<<header; int count = 0; while (1) { if (stream.eof()) { break; } char line[1024]; stream.getline(line, sizeof(line)); if (strcmp(line, "")==0) { continue; } stringstream lineStream; lineStream<<line; //first line if (count == 0) { lineStream>>method; lineStream>>path; lineStream>>version; }else { string fieldName; lineStream>>fieldName; //remove line[strlen(line)-1] = ' '; void(CHttpRequest::*func)(char*) = fieldMap[fieldName]; if (func!=NULL) { (this->*func)(line+fieldName.length()+1); } } count++; } } void CHttpRequest::handleConnection(char *field) { if (ENABLE_KEEP_ALIVE) { connection = string(field); } } void CHttpRequest::handleAuthorization(char *field) { char authName[10], authInfo[256]; sscanf(field, "%s %s", authName, authInfo); authorize = string(authInfo); } void CHttpRequest::handleRange(char *field) { if (strstr(field, "bytes=")==field) { char *start = strtok(field+strlen("bytes="), "-"); fileStart = start==NULL?0:atol(start); char *end = strtok(NULL, "-"); fileEnd = end==NULL?0:atol(end); } } void CHttpRequest::handleIfModSince(char *field) { modifiedTime = string(field); }
为了保证http解析的效率,本文採用了与nginx中类似的做法,将字段名与解析函数放到了map中(nginx中使用的是hash表,在这里简化为map)。
在解析完毕之后,调用CHttpResponse构造响应。CHttpResponse代码例如以下:
#include "CHttpResponse.h" #include "CHttpRequest.h" #include <sys/socket.h> #include "define.h" #include <string.h> #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>" std::string getStringFromTime(time_t time) { char timeBuff[64]; struct tm tm = *gmtime(&time); strftime(timeBuff, sizeof timeBuff, "%a, %d %b %Y %H:%M:%S %Z", &tm); return std::string(timeBuff); } CHttpResponse::CHttpResponse(CHttpRequest *request) { m_request = request; if (m_request->method.compare(TS_HTTP_METHOD_GET_S)==0 || m_request->method.compare(TS_HTTP_METHOD_HEAD_S)==0) { std::string path = ROOT_PATH; if (m_request->path.compare("/")==0) { path += ROOT_HTML; }else { path += m_request->path; } m_statusCode = 0; //if file exist if (isFileExist(path.c_str())) { //if receive modified time if (!m_request->modifiedTime.empty()) { time_t time = fileModifiedTime(path.c_str()); if (getStringFromTime(time) == m_request->modifiedTime) { m_statusCode = TS_HTTP_STATUS_NOT_MODIFIED; m_statusMsg = TS_HTTP_STATUS_NOT_MODIFIED_S; } } //if file modified if (m_statusCode == 0) { if (m_request->fileStart || m_request->fileEnd) { long long fileSize = getFileSize(path.c_str()); //if request range satisfied if (m_request->fileStart<fileSize && m_request->fileEnd<fileSize) { m_statusCode = TS_HTTP_STATUS_PARTIAL_CONTENT; m_statusMsg = TS_HTTP_STATUS_PARTIAL_CONTENT_S; m_sendFilePath = path; }else { m_statusCode = TS_HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE; m_statusMsg = TS_HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE_S; } }else { m_statusCode = TS_HTTP_STATUS_OK; m_statusMsg = TS_HTTP_STATUS_OK_S; m_sendFilePath = path; } } } else { m_statusCode = TS_HTTP_STATUS_NOT_FOUND; m_statusMsg = TS_HTTP_STATUS_NOT_FOUND_S; m_sendStr = HTTP_RESPONSE_404; } } } bool CHttpResponse::response(int connFd) { bool result = true; std::stringstream responseStream; responseStream<<m_request->version<<" "<<m_statusMsg<<" "; //time responseStream<<"Date: "<<getStringFromTime(time(0))<<" "; //server name responseStream<<"Server: "<<SERVER_NAME<<" "; //keep alive responseStream<<"Connection: "<<m_request->connection<<" "; //content length long long contentLength = 0; //if file exist if (!m_sendFilePath.empty()) { //if define file end if (m_request->fileEnd) { contentLength = m_request->fileEnd - m_request->fileStart + 1; } //if define file start else if (m_request->fileStart) { contentLength = getFileSize(m_sendFilePath.c_str()) - m_request->fileStart + 1; } //if undefine start or end else { contentLength = getFileSize(m_sendFilePath.c_str()); } } else if (!m_sendStr.empty()) { contentLength = m_sendStr.length(); } if (contentLength) { responseStream<<"Content-Length: "<<contentLength<<" "; } //last modified if (!m_sendFilePath.empty()) { responseStream<<"Last-Modified: "<<getStringFromTime(fileModifiedTime(m_sendFilePath.c_str()))<<" "; responseStream<<"Accept-Ranges: "<<"bytes"<<" "; } //content type if (!m_sendFilePath.empty()) { char path[256]; strcpy(path, m_sendFilePath.c_str()); char *ext = strtok(path, "."); char *lastExt = ext; while (ext!=NULL) { ext = strtok(NULL, "."); if (ext) lastExt = ext; } for (int i=0; i<38; i++) { if (strcmp(mmt[i].ext, lastExt)==0) { responseStream<<"Content-Type: "<<mmt[i].type<<" "; break; } } } //other switch (m_statusCode) { case TS_HTTP_STATUS_UNAUTHORIZED: responseStream<<"WWW-Authenticate: Basic realm="zhaoxy.com" "; break; case TS_HTTP_STATUS_FOUND: responseStream<<"Location: /index.html "; break; case TS_HTTP_STATUS_PARTIAL_CONTENT: responseStream<<"Content-Range: "<<"bytes "<<m_request->fileStart<<"-"<<(m_request->fileEnd==0?contentLength:m_request->fileEnd)<<"/"<<getFileSize(m_sendFilePath.c_str())<<" "; break; default: break; } //seperator responseStream<<" "; //send response header std::string responseStr = responseStream.str(); std::cout<<responseStr<<std::endl; send(connFd, responseStr.c_str(), responseStr.length(), 0); //content //if not head method if (m_request->method.compare(TS_HTTP_METHOD_HEAD_S)!=0) { if (!m_sendFilePath.empty()) { std::ifstream file(m_sendFilePath); file.seekg(m_request->fileStart, std::ifstream::beg); while(file.tellg() != -1) { char *p = new char[1024]; bzero(p, 1024); file.read(p, 1024); int n = (int)send(connFd, p, 1024, 0); if (n < 0) { std::cout<<"ERROR writing to socket"<<std::endl; result = false; break; } delete p; } file.close(); }else { send(connFd, m_sendStr.c_str(), m_sendStr.length(), 0); } } return result; }
该代码支持断点续传、last modified和authorization字段。具体的逻辑不作具体说明,有疑问的能够留言。
该Httpserver的代码已经上传到GitHub上,大家能够直接下载。
版权声明:本文博主原创文章,博客,未经同意不得转载。