1. 程式人生 > >Http服務器實現文件上傳與下載(四)

Http服務器實現文件上傳與下載(四)

讀取 版本 html 出現問題 type 函數名 range 讀取文件 都是

一、引言

  歡迎大家來到和我一起編寫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服務器實現文件上傳與下載(四)