Http服務器實現文件上傳與下載(四)
一、引言
歡迎大家來到和我一起編寫Http服務器實現文件的上傳和下載,現在我稍微回顧一下之前我說的,第一、二章說明說明了整體的HTTP走向,第三章實現底層的網絡編程。接著這一章我想給大家講的是請求獲取,和響應發送的內容。這裏主要講解的響應內容,為什麽?因為我們編寫的是一個與瀏覽器交互的HTTP服務器,所以大多數的情況下我們只進行被動的應答。
這就是一種"提問--回答"的問題。其實在講解這章的時候,我本來準備給大家講解一下Linux一些信號中斷的問題。因為在網絡層發送的時候,系統會發送一些信號給我們的應用程序,所以會導致我們的程序意外的終止。但當我寫的這篇博客的時候我又放棄,我想在講流程走向的時候再提一個中斷捕獲吧。在這個請求響應層的類其實真正的設計需要很多的內容,這裏就是HttpResponse類和HttpRequest類的設計,在j2EE中,我們編寫Servlet的時候就用到了這2個類,如HttpServletResquest,HttpServletResponse的類,如果對這裏面的內容感興趣,可以下載tomcat,在servlet-api.jar包裏面有這些類。
在本文的實現中,Request類只包含了一個獲取請求頭和解析頭的一些方法。如何解析頭,我在《Http服務器實現文件上傳與下載(一)》已經講解了,讀者只需要對其封裝一個類即可。
二、HttpRequest類
請求消息的解析是通過被定義在命名空間為Http的類名為HttpRequest。這個類的構造函數接受一個套接字,就是跟我們連接的那個套接字,在網絡層我們已經講過了,然後在getHeader方法中調用server_read()獲取請求頭,然後通過Utils::parseHeader()函數進行解析。這樣把解析的內容放入需要的string中,當前不太需要的直接在map裏面。這裏我直接貼出代碼,大家看起來也比較容易。這裏我在這一章節我主要講解的是文件的下載,所以主要會對HttpResponse的類的分析,而HttpRequest類只貼出目前需要的內容。
頭文件(include/httprequest.h)
1 #ifndef HTTPREQUEST_H 2 #define HTTPREQUEST_H 3 #include "socket.h" 4 #include <map> 5 #include <string> 6 #include <fstream> 7 namespace Http{ 8 class HttpRequest{ 9 public: 10 HttpRequest(TCP::Socket &c); 11 virtual ~HttpRequest(); 12 std::map<std::string,std::string> getHeader(int confd) ; 13 ...... 14 protected: 15 private: 16 std::string method; 17 std::string url; 18 std::string host; 19 TCP::Socket &s; 20 }; 21 } 22 23 #endif // HTTPREQUEST_H
源文件(src/httprequest.cpp)
1 #include "httprequest.h" 2 #include "utils.h" 3 namespace Http{ 4 HttpRequest::HttpRequest(TCP::Socket &c):s(c){ 5 } 6 7 HttpRequest::~HttpRequest(){ 8 } 9 std::map<std::string,std::string> HttpRequest::getHeader(int confd){ 10 char recvBuf[1024]; 11 memset(recvBuf,0,sizeof(recvBuf)); 12 s.server_read(confd,recvBuf,1024); 13 std::cout<<recvBuf<<std::endl; 14 std::map<std::string,std::string> mp =Utils::parseHeader(recvBuf); 15 method =mp["Method"]; 16 url=mp["Url"]; 17 host=mp["Host"]; 18 return mp; 19 } 20 ...... 21 }
三、HttpResponse類
當我們訪問Http服務器的時候,瀏覽器顯示可以下載的文件的內容,然後我們點擊需要下載的文件,然後文件就可下載了。首先我點擊這個文件這個URL時,瀏覽器給我們發送一些請求頭,例如它發送一個為/download/HttpServer.zip這個URL,說明他需要下載的文件,而且該文件為HttpServer.zip。在上面我們已經可以用getHeader來捕獲這個請求頭,然後獲取這個URL。之後服務端還是要發送一個響應頭,告訴瀏覽器你的請求我們同意,請求頭結束以空行為標記,接著就是具體的文件的內容了。
在發送響應頭時,還是需要發送協議版本,狀態碼,響應內容類型,文件的長度,文件斷點下載等內容,或者傳輸的時候采用chunk傳輸,但是這裏我采用文件的長度來標記。讀者可以自行查看其它方式傳輸內容。特別要註意ed是一定要在響應頭中指定傳輸實體的大小,否則客戶端不知道什麽時候結束,這時可能拒絕接收服務端發來的字節。在這個類中,請求下載的文件發送時,我采用sendFile這個函數,這個函數讀取文件就是采用二進制的方式,並且在響應頭中也告知瀏覽器以二進制的方式接收文件。這樣都是以二進制的方式讀取和發送文件才不會出現問題。sendLineFile 和sendIndexFile兩者大致相同,都是采用ASCII文本的方式發送內容,這樣比如HTML這些需要顯示在瀏覽器的內容,可以通過這兩個函數。通過函數名可知在sendLineFile會以文件行的方式讀取,而sendIndexFile文件會把內容寫在同一行上。例如:我們瀏覽器請求一個index.html的內容,這時采用2個sendLineFile和sendIndexFile的顯示效果都是一樣的,但是如果點擊右鍵查看源碼時,sendLineFile的內容是以源文件一樣的,而sendIndexFile發送的內容會都在第一行,不會換行。
說了這麽多大家也比較清楚了,下面貼出具體一些代碼。
頭文件(include/httpresponse.h)
1 #ifndef HTTPRESPONSE_H 2 #define HTTPRESPONSE_H 3 #include "socket.h" 4 #include<string> 5 #include<fstream> 6 #include<sstream> 7 #include<iterator> 8 #include<algorithm> 9 #include<time.h> 10 #include "utils.h" 11 namespace Http{ 12 class HttpResponse{ 13 public: 14 HttpResponse(TCP::Socket &c); 15 virtual ~HttpResponse(); 16 ssize_t send(int confd,std::string content); 17 ssize_t sendIndexFile(int confd,std::string FileName); 18 ssize_t sendFile(int &confd,std::string FileName,int64_t pos); 19 ssize_t sendLineFile(int confd,std::string file); 20 void setProtocal(std::string); 21 void setStatusCode(std::string); 22 void setServerName(std::string); 23 void setContentType(std::string); 24 void setContentRange(std::string); 25 void setContentLength(int64_t); 26 protected: 27 std::string getHeader() const; 28 private: 29 std::string protocal; 30 std::string statusCode; 31 std::string serverName; 32 std::string contentType; 33 std::string contentLength; 34 std::string contentRange; 35 std::string connection; 36 std::string date; 37 TCP::Socket &s; 38 }; 39 } 40 #endif // HTTPRESPONSE_H
源文件(src/httpresponse.cpp)
1 #include "httpresponse.h" 2 namespace Http{ 3 HttpResponse::HttpResponse(TCP::Socket &c):s(c){ 4 protocal="HTTP/1.1"; 5 statusCode="200 OK"; 6 serverName="Server:(Unix)"; 7 contentType="Content-type:text/html"; 8 contentLength="Content-length:0"; 9 contentRange="Content-Range:0-"; 10 connection="Connection:Keep-Alive"; 11 time_t timep; 12 time(&timep); 13 char s[50]; 14 sprintf(s,ctime(&timep)); 15 date="Date:"+std::string(s,s+(strlen(s)-1)); 16 } 17 18 HttpResponse::~HttpResponse(){ 19 } 20 void HttpResponse::setProtocal(std::string content){ 21 protocal=content; 22 } 23 void HttpResponse::setStatusCode(std::string content){ 24 statusCode=content; 25 } 26 void HttpResponse::setServerName(std::string content){ 27 serverName=content; 28 } 29 void HttpResponse::setContentType(std::string content){ 30 contentType="Content-type:"+content; 31 } 32 void HttpResponse::setContentLength(int64_t len){ 33 contentLength="Content-length:"+Utils::toString(len); 34 } 35 void HttpResponse::setContentRange(std::string content){ 36 contentRange="Content-Range:"+content; 37 } 38 std::string HttpResponse::getHeader() const{ 39 std::string h1 =protocal+" "+statusCode+"\r\n"; 40 std::string h2 =serverName+"\r\n"; 41 std::string h3 =contentType+"\r\n"; 42 std::string h4 =contentLength+"\r\n"; 43 std::string h5=contentRange+"\r\n"; 44 std::string h6=connection+"\r\n"; 45 std::string h7=date+"\r\n\r\n"; 46 return h1+h2+h3+h4+h5+h6+h7; 47 } 48 ssize_t HttpResponse::send(int confd,std::string content){ 49 setContentType("application/octet-stream"); 50 setContentLength(content.size()); 51 std::string header=getHeader(); 52 s.server_write(confd,(char*)header.c_str(),header.size()); 53 ssize_t len =s.server_write(confd,(char*)content.c_str(),content.size()); 54 s.server_close(confd); 55 return len; 56 } 57 ssize_t HttpResponse::sendLineFile(int confd,std::string file){ 58 std::ifstream in(file.c_str()); 59 in.seekg(0,std::ios::end); 60 int64_t len = in.tellg(); 61 setContentLength(len); 62 std::string header=getHeader(); 63 s.server_write(confd,(char*)header.c_str(),header.size()); 64 in.seekg(0,std::ios::beg); 65 ssize_t n=0; 66 char buf[1024]; 67 while(!in.eof()){ 68 bzero(buf,sizeof(buf)); 69 in.getline(buf,1024); 70 buf[strlen(buf)]=‘\n‘; 71 n+=s.server_write(confd,buf,in.gcount()); 72 } 73 s.server_close(confd); 74 return n; 75 } 76 ssize_t HttpResponse::sendIndexFile(int confd,std::string file){ 77 std::ifstream in(file.c_str()); 78 in.seekg(0,std::ios::end); 79 int64_t len = in.tellg(); 80 setContentLength(len); 81 std::string header=getHeader(); 82 s.server_write(confd,(char*)header.c_str(),header.size()); 83 in.seekg(0,std::ios::beg); 84 char buf[1024]; 85 int sendCount=0; 86 while(!in.eof()){ 87 memset(buf,0,sizeof(buf)); 88 in.getline(buf,1024); 89 sendCount+=s.server_write(confd,buf,in.gcount()); 90 } 91 s.server_close(confd); 92 return sendCount; 93 } 94 ssize_t HttpResponse::sendFile(int &confd,std::string fileName,int64_t pos){ 95 std::ifstream in(fileName.c_str(),std::ios::binary); 96 in.seekg(0, std::ios::end); 97 std::streampos ps = in.tellg(); 98 int64_t len=ps-pos; 99 if(pos!=0){ 100 setStatusCode("206 Partial Content"); 101 } 102 setContentType("application/octet-stream"); 103 setContentLength(len); 104 std::string content="bytes"; 105 content+=" "+Utils::toString(pos)+"-"+Utils::toString((int64_t)ps-1)+"/"+Utils::toString(len); 106 setContentRange(content); 107 std::string header=getHeader(); 108 std::cout<<header<<std::endl; 109 s.server_write(confd,(char*)header.c_str(),header.size()); 110 in.seekg(pos,std::ios::beg); 111 char buf[1024]; 112 ssize_t n=0; 113 while(!in.eof()){ 114 in.read(buf,1024); 115 n+=s.server_write(confd,buf,in.gcount()); 116 } 117 s.server_close(confd); 118 return n; 119 } 120 }
在上面響應頭中Content-Range:這個字段,表示文件內容的範圍,在一般情況下都是從0到lenth(file)-1。如果在之前已經下了一些內容後,如果是斷點續下載時,瀏覽器在請求頭中有Range知道,表示從Range的開始字節傳輸,而我們服務器指定Content-Range為Range字段開始,接著發送這些內容即可,實現文件的斷點下載。接下來的內容請大家看《Http服務器實現文件上傳與下載(五)》。
Http服務器實現文件上傳與下載(四)