一、引言
欢迎大家来到和我一起编写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+" "; 40 std::string h2 =serverName+" "; 41 std::string h3 =contentType+" "; 42 std::string h4 =contentLength+" "; 43 std::string h5=contentRange+" "; 44 std::string h6=connection+" "; 45 std::string h7=date+" "; 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)]=' '; 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服务器实现文件上传与下载(五)》。