• 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+"
    ";
     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服务器实现文件上传与下载(五)》。

  • 相关阅读:
    操作系统基础知识
    os库基本介绍
    原型模式
    ASP .NetCore 部署500错误 查看异常详情
    css设置文本自动换行
    SqlServer数据库链接字符串
    Json列表数据查找更新
    VB中将类标记为可序列化
    VB 性能优化点
    参加公司工作总结会要准备的内容 IT 技术部
  • 原文地址:https://www.cnblogs.com/zhangyuhang3/p/6909900.html
Copyright © 2020-2023  润新知