##HTTP
HTTP是一个客户端和服务器端请求和应答的标准(TCP)。客户端是终端用户,服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为80)的HTTP请求。(我们称这个客户端)叫用户代理。应答的服务器上存储着(一些)资源,比如HTML文件和图像。(我们称)这个应答服务器为源服务器。

通常,由HTTP客户端发起一个请求,建立一个到服务器指定端口(默认是80端口)的TCP连接。HTTP服务器则在那个端口监听客户端发送过来的请求。一旦收到请求,服务器(向客户端)发回一个状态行,比如"HTTP/1.1 200 OK",和(响应的)消息,消息的消息体可能是请求的文件、错误消息、或者其它一些信息。HTTP使用TCP而不是UDP的原因在于(打开)一个网页必须传送很多数据,而TCP协议提供传输控制,按顺序组织数据,和错误纠正。

######HTTP服务器
通过浏览器,发送一个标准的HTTP请求,就能够得到一个标准的HTTP响应
项目中实现的是:通用的HTTP服务器框架来实现自己的业务

要实现的核心功能(需求分析(粗略))
  1. 能够接受标准的HTTP请求
  2. a)GET方法
  3. b)POST方法
  4. 能够根据请求作出响应一个标准的HTTP响应
  5. a)能够根据url返回一个服务器上的静态文件(html,css,JavaScript,图片等)
  6. b)根据请求中的参数(GET url,POST body)动态生成一个页面(//这个页面是基于CGI的方法,CGI可以理解成一个标准)
模块划分
  1. 初始化模块 //实现一个TCP服务器
  2. 响应请求模块 //使用多线程的方式处理并发的请求
    a)读取请求并解析 //操作和解析字符串(反序列化)
    b)根据请求内容进行计算
    i)处理静态文件 //直接将静态文件的内容返回
    ii)处理动态页面 //使用CGI的方式实现动态计算生成页面
    c)把响应的结果写回给客户端 //操作和拼接字符串

请求报文的构成:

响应报文的构成

#####告知服务器意图的HTTP方法
GET方法(获取资源)
用来请求访问已被URI识别的资源。指定的资源经服务器端解析后返回响应内容。
使用GET方法的请求·响应的例子

POST方法(传输实体主体)
POST的主要目的并不是获取响应的主体内容。
使用POST方法的请求·响应的例子

######返回结果的HTTP状态码
状态码类别

状态码 类别 原因短语
1XX Information(信息性状态码) 接收的请求正在处理
2XX Success(成功状态码) 请求正常处理完毕
3XX Redirection(重定向状态码) 需要进行附加操作以完成请求
4XX Client Error(客户端错误状态码) 服务器无法处理请求
5XX Server Error(服务器错误状态码) 服务器处理请求出错

//数字中的第一位指定了响应类别,后两位无分类

常用状态码:
200 OK 请求被正常处理
204 No Content 请求已成功处理,但响应报文中不含实体的主体部分
206 Partial Content 范围性请求且服务器成功执行
301 Moved Permanently 永久性重定向
302 Found 临时性重定向
303 See Other 使用GET方法定向获取请求资源
304 Not Modified 请求未满足
307 Temporary Redirect 临时重定向
400 Bad Request 请求报文中存在语法错误
401 Unauthorized 发送的请求需要有通过HTTP认证(BASIC认证、DIGEST认证)的认证信息
403 Forbidden 对请求资源的访问被服务器拒绝了
404 Not Found 服务器上无法找到请求的资源
500 Internal Server Error 服务器端在执行请求时发生了错误
503 Service Unavailable 服务器暂时处于超负载或正在进行停机维护,现在无法处理请求

#####CGI协议
CGI是外部应用程序(CGI程序)与WEB服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的过程。CGI规范允许Web服务器执行外部程序,并将它们的输出发送给Web浏览器,CGI将Web的一组简单的静态超媒体文档变成一个完整的新的交互式媒体。

1.收到的请求是一个(GET && 带有query_string )|| (POST) 触发CGI逻辑
2.创建一个子进程,子进程通过exec执行一个本地的可执行程序,通过这个可执行程序完成动态页面的计算
3.这里涉及到一个重要的问题:

  1. 子进程需要知道http请求具体长什么样子;
    需要知道进行替换哪个可执行文件
    需要知道HTTP请求的方法是什么 (环境变量METHOD )
    需要知道query_string是什么 (环境变量 QUERT_STRING )
    需要知道body是什么 (匿名管道)

  2. 子进程也要把计算完毕的结果反馈给父进程
    http响应的body ( 匿名管道 )
    部分header ( 匿名管道 ) (子进程把标准输入和标准输出重定向到管道上)

例如:1+2
收到的http请求可能是GET请求,query_string包含了1+2,也可能是一个POST请求,body中包含了1+2

子进程程序替换之后,CGI程序响应数据会这么写:
假设CGI程序返回的html页面是一个

以下内容是CGI程序所写回管道的内容(一部分header和一部分body)

如果要想仅仅使用管道完成进程间通信的话,要额外的约定协议。
此处的协议指的是,父进程按照啥样的格式组织数据并写入管道,以及子进程按照啥样的格式来解析。
如果以下图这种方式来组织数据的话,则在CGI程序中解析数据会更麻烦些,而如果我们使用环境变量配合管道两个双管齐下的话,子进程直接读取环境变量的内容就能获取方法和content-length,而直接从标准输入中读取数据就能够得到http请求中的body。

我在这个HTTP中实现一个简单的CGI程序:
根据请求中的输入数据,计算两个数字的和
两个要相加的数,通过query_string(GET)或者body(POST)来传递
例如:a=100&b=200

代码

http_server.cc

#include "http_server.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sstream>#include "util.hpp"typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;namespace http_server{int HttpServer::Start(const std::string& ip,short port){int listen_sock = socket(AF_INET,SOCK_STREAM,0);if(listen_sock <0){perror("socket");return -1;}//要给socket加上一个选项,能够重用我们的连接int opt = 1;setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr(ip.c_str());addr.sin_port = htons(port);int ret = bind(listen_sock,(sockaddr*)&addr, sizeof(addr));if(ret < 0){perror("bind");return -1;}ret = listen( listen_sock, 5);if(ret < 0){perror("listen");return -1;}// printf("ServerStart OK!\n");LOG(INFO)<< "SeverStart OK!\n";while(1){//基于多线程来实现一个TCP服务器sockaddr_in peer;socklen_t len = sizeof(peer);int new_sock = accept(listen_sock,(sockaddr*)&peer,&len);if (new_sock <0){perror("accept");continue;}//如果成功,创建新线程,使用新线程完成这次请求的计算Context* context = new Context();context->new_sock = new_sock;context->server = this;pthread_t tid;pthread_create(&tid,NULL,ThreadEntry,reinterpret_cast<void*>(context));pthread_detach(tid);}close(listen_sock);return 0;
}//线程入口函数
void* HttpServer::ThreadEntry(void* arg){//准备工作Context* context = reinterpret_cast<Context*>(arg);HttpServer* server = context->server;//1.从文件描述符中读取数据,转换成Request对象int ret = 0;ret = server->ReadOneRequest(context);if(ret <0){LOG(ERROR) << "ReadOneRequest error!" << "\n";//用这个函数构造一个404的http Response 对象server->Process404(context);goto END;}//TODO TEST 通过以下函数将一个解析出来的请求打印出来server->PrintRequest(context->req);//2.把Resquest对象计算生成Response对象ret = server->HandlerRequest(context);if(ret <0){LOG(ERROR) << "HandlerRequest error!" << "\n";//用这个函数构造一个404的http Response 对象server->Process404(context);goto END;}
END://TODO 处理失败的情况//3.把Response对象进行序列化,写回到客户端server->WriteOneResponse(context);//收尾工作close(context->new_sock);delete context;return NULL;
}
//构造一个状态码为404 的Response对象
int HttpServer::Process404(Context* context){Response* resp = &context->resp;resp->code = 404;resp->desc = "Not Found";//用""可以续行resp->body = "<head><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"><h1>404!您的页面被喵星人吃掉了</h1>";std::stringstream ss;ss << resp->body.size();std::string size;ss >> size;resp->header["Content-Length"] = resp->body.size();return 0;
}//从socket读取字符串,构造生成Request对象
int HttpServer::ReadOneRequest(Context* context){Request* req = &context->req;//1.从socket中读取一行数据作为Request的首行  按行读取的分隔符应该就是\n  \r  \r\nstd::string first_line;FileUtil::ReadLine(context->new_sock,&first_line);//2.解析首行,获取到请求的method和urlint ret = ParseFirstLine(first_line,&req->method,&req->url);if(ret < 0){LOG(ERROR) <<"ParseFirstLine error! first_line=" << first_line << "\n";return -1;}//3.解析url,获取到url_path,和query_stringret = ParseUrl(req->url,&req->url_path,&req->query_string);if(ret <0){LOG(ERROR) <<"ParseYrl error! url=" << req->url << "\n";return -1;}//4.循环的按行读取数据,每次读取到一行数据,就进行一个hander的解析//读到空行,说明header解析完毕std::string header_line;while(1){FileUtil::ReadLine(context->new_sock,&header_line);//如果 header_line 是空行,就退出循环//由于ReadLine返回的hander——line不包含n等分隔符//因此读到空行的时候,header_line就是空字符串if(header_line == ""){break;}ret = ParseHeader(header_line,&req->header);if(ret <0){LOG(ERROR) << "ParseHeader error! header_line=" << header_line << "\n";return -1;}}//5.如果是POST请求,并且header中包含了Content-Length字段Header::iterator it = req->header.find("Content-Length");if(req->method == "POST" && it == req->header.end()){LOG(ERROR) <<"POST Request has no Content-Length!\n";return -1;}//  如果是GRT请求,就不用读bodyif(req->method == "GET"){return 0;}//  如果是POST请求,但是没有Content-Length字段,认为这次请求失败//  继续读取socket,获取到body的内容int content_length = atoi(it->second.c_str());ret = FileUtil::ReadN(context->new_sock,content_length,&req->body);if(ret <0){LOG(ERROR) << "ReadN error! content_length=" << content_length << "\n";return -1;}return 0;
}//解析首行,就是按照空格进行分割,分割成三个部分
//这三个部分分别是 方法,url,版本号
int HttpServer::ParseFirstLine(const std::string& first_line,std::string* method,std::string* url){std::vector<std::string> tokens;StringUtil::Split(first_line," ", &tokens);if(tokens.size() != 3){//首行格式不对LOG(ERROR) << "ParseFirstLine error! split error! first_line=" << first_line << "\n";return -1;}//如果版本号中不包含HTTP关键字,也认为出错if(tokens[2].find("HTTP") == std::string::npos){//首行的格式不对,版本信息中不包含HTTP关键字LOG(ERROR) << "ParseFirstLine error! version error! first_line=" << first_line << "\n";return -1;}*method = tokens[0];*url = tokens[1];return 0;
}//https://www.baidu.com/s?wd=%E9%B2%9C%E8%8A%B1&rsv_spt=1&rsv_iqid=0x837a570
//解析一个标准的url,比较复杂,核心思路是以?作为分隔符,从?左边来查找url_path,从?右边来查找query_string
//我们此处只实现一个简化版本,只考虑不包含域名和协议的情况,以及#的情况
//例如:
// /s?wd=%E9%B2%9C%E8%8A%B1&rsv_spt=1&rsv_iqid=0x837a570
// 我们就单纯以?为分割,?左边的就是path,?右边的就是query_string
// /path/index.html  这种情况下没有?
int HttpServer::ParseUrl(const std::string& url, std::string* url_path, std::string* query_string){size_t pos = url.find("?");if(pos == std::string::npos){//没找到*url_path =url;*query_string = "";return 0;}//找到了*url_path = url.substr(0,pos);*query_string = url.substr(pos+1);return 0;
}//此函数用于解析一行header
//此处的实现使用string::find来实现
//如果使用split的话,可能有如下问题:
//HOST:http://www.baidu,com
int HttpServer::ParseHeader(const std::string& header_line,Header* header){size_t pos = header_line.find(":");if(pos == std::string::npos){//找不到:说明header的格式有问题LOG(ERROR) << "ParseHeader error! has no : header-line=" << header_line << "\n";return -1;}//这个header的格式还是不正确,没有valueif(pos + 2 >= header_line.size()){LOG(ERROR) << "ParseHeader error! has no value! header_line=" << header_line << "\n";return -1;}(*header)[header_line.substr(0,pos)] = header_line.substr(pos + 2);return 0;
} //该函数实现序列化,把Response对象转换成一个string
//写回到socket中
//此函数完全按照HTTP协议的要求来构造响应数据
//我们实现这个函数的细节可能有很大差异,但是只要能遵守HTTP协议那么就都是ok的
int HttpServer::WriteOneResponse(Context* context){//iostream和stringstream之间的关系类似于printf和sprintf之间的关系//1.进行序列化const Request& resp = context->resp;std::stringstream ss;ss << "HTTP/1.1" << resp.code <<" "<<resp.desc<<"\n";if(resp.cgi_resp == ""){//当前认为是在处理静态页面for(auto item : resp.header){ss << item.first << ":"<<item.second <<"\n";                                    }ss <<"\n";ss << resp.body;}else{//当前是在处理CGI生成的页面//cgi_resp同时包含了响应数据的header空行和bodyss << resp.cgi_resp;}//2.将序列化的结果写入到socket中const std::string& str = ss.str();write(context->new_sock,str.c_str(),str.size());return 0;
}//通过输入的Request对象计算生成Response对象
//1.支持静态文件
//a)GET 并且没有query_string作为参数
//2.动态生成页面(使用CGI的方式来动态生成)
//a)GET并且存在query_string作为参数
//b)POST请求
int HttpServer::HandlerResquest(Context* context){const Request& req = context->req;Response* resp = &context->resp;resp->code = 200;resp->desc = "OK";//判断当前的处理方式是按照静态文件处理还是动态生成if(req.method == "GET" && req.query_string == ""){return context->server->ProcessStaticFile(context);}else if((req.method == "GET" && req.query_string != "")|| req.method == "POST"){return context->server->PoressCGI(context);}else{LOG(ERROR) << "Unsupport Method! method" << req.method << "\n";return -1;}return -1;}//1.通过Request中的url_path字段,计算出文件在磁盘上的路径是什么
//例如url_path /index.html
//想要得到的磁盘上的文件就是 ./wwwroot/index.html
//注意,./wwwroot是我们此处约定的根目录
//2.打开文件,将文件中的所有内容读取出来放到body中
int HttpServer::ProcessStaticFile(Context* context)
{const Request& req = context->req;Response* resp = &context->resp;//1.获取到静态文件的完整路径std::string file_path;GetFilePath(req.url_path,&file_path);//2.打开并读取完整的文件int ret = FileUtil::ReadAll(file_path,&resp->body);if(ret < 0){LOG(ERROR) << "ReadAll error! file_path=" << file_path << "\n";return -1;}return 0;
}
//通过url_path找到对应的文件路径
//例如 请求url可能是 HTTP://192.168.80.132:9090/
//这种情况下 url_path是 /
//此时等价于请求/index.html
//例如 请求url可能是 HTTP://192.168.80.132:9090/image
//这种情况下 url_path是 /
//如果url_path指向的是一个目录,就尝试在这个目录下访问一个叫做index.html的文件
void HttpServer::GetFilePath(const std::string& url_path,std::string* file_path)
{*file_path = "./wwwroot" +url_path;//判定一个路径是普通文件还是目录文件//1.linux的stat函数//2.通过boost filesystem模块进行判断if(FileUtil::IsDir(*file_path)/*如果当前文件是一个目录,就可以进行一个文件名拼接,拼接上index.html后缀*/){//1./image///2./imageif(file_path->back() != '/'){file_path->push_back('/');}(*file_path) += "index.html";}return;
}int HttpServer::ProcessCGI(Context* context){const Request& req = context->req;Response* resp = &context->resp;//1.创建一对匿名管道(父子进程要双向通信,因为匿名管道是半双工的)int fd1[2],fd2[2];pipe(fd1);pipe(fd2);int father_write = fd1[1];//父进程用来写的int child_read = fd1[0];int father_read = fd2[0];//父进程用来读的int child_write = fd2[1];//2.设置环境变量//  a)METHOD 请求方法std::string env = "REQUEST_METHOD" + req.method;putenv(const_cast<char*>(env.c_str()));if(req.method == "GET"){//  b)GET方法,QUERY_STRING 请求的参数env = "QUERY_STRING" + req.query_string;putenv(const_cast<char*>(env.c_str()));}else if(req.method == "POST"){//  c)POST方法,就设置CONTENT_LENGTHauto pos = req.header.find("Content-Length");env = "CONTENT_LENGTH=" + pos->second;putenv(const_cast<char*>(env.c_str()));}pid_t ret = fork();if(ret < 0){perror("fork");goto END;}if(ret > 0){//3.fock,父进程流程close(child_read);close(child_write);//  a)如果是POST请求,父进程就要把body写入到管道中if(req.method == "POST"){write(father_write, req.body.c_str(), req.body.size());}//  b)阻塞式的读取管道,尝试吧子进程的结果读取出来,并且放到Response对象中FileUtil::ReadAll(father_read, &resp->cgi_resp);//  c)对子进程进行进程等待(为了避免僵尸进程)wait(NULL);}else{//4.fock,子进程流程close(father_write);close(father_read);//  a)把标准输入和标准输出进行重定向dup2(child_read,0);dup2(child_write,1);//  b)先获取到要替换的可执行文件是那个(通过url_path来获取)std::string file_path;GetFilePath(req.url_path, &file_path);//  c)进行进程的程序替换execl(file_path.c_str(),file_path.c_str(),NULL);//  d)由我们的CGI可执行程序完成动态页面的计算,并且写回数据到管道中//这部分逻辑,我们需要放到另外的单独的文件中实现,并且根据该文件编译生成我们的CGI可执行程序}
END://TODO   统一处理收尾工作close(father_read);close(father_write); close(child_read); close(child_write); return 0;
}//分割线,以下为测试用函数
///
void HttpServer::PrintRequest(const Request& req)
{LOG(DEBUG) << "Request:" << "\n" << req.method << " " << req.url << "\n" << req.url_path << " " << req.query_string << "\n";//for(Header::const_iterator it = req.header.begin();it != req.header.end();++it)//这种麻烦//C++ 11 range based for 基于区间的循环for(auto it : req.header){//LOG(DEBUG) << it ->first << ":" << it->second << "\n";LOG(DEBUG) << it.first << ":" << it.second << "\n";}LOG(DEBUG) << "\n";LOG(DEBUG) << req.body << "\n";
}             }// end http_server

http_server.h

#pragma once
#include <string>
#include <unordered_map>namespace http_server{typedef std::unordered_map<std::string,std::string> Header;//请求结构
struct Request{std::string method;//请求方法 GET/POSTstd::string url;//例如url为一个形如 http://www.baidu.com/index.html?kwd="cpp"std::string url_path;  //路径 如 index.htmlstd::string query_string;  // kwd="cpp"//std::string version;   //暂时先不考虑版本号Header header; //一组字符串键值对std::string body; //http的请求body};
//响应结构
struct Response {int code;     //状态码std::string desc;  //状态码描述//std::string version;  //暂不考虑版本号//下面这两个变量专门给处理静态页面时使用的。当前请求如果是请求静态页面,这两个字段会被填充,并且cgi_resp字段为空Header header;  //响应报文中的header数据std::string body;   //响应报文中的body数据//下面这个变量专门给CGI来使用的,并且如果当前请求是CGI的话,cgi_resp就会被CGI程序进行填充,并且header和body这两个字段为空std::string cgi_resp;//CGI程序返回给父进程的内容,包含了部分header和body//引入这个变量,是为了避免解析CGI程序返回的内容,因为这部分内容可以直接写到socket中};//当前请求的上下文。包含了这次请求的所有需要的中间数据
//方便进行扩展。整个处理请求的过程中,每一个环节都能够拿到所有和这次请求相关的数据struct Context {Request req;Response resp;int new_sock;HttpServer* server;};//实现核心流程的类
class HttpServer{
public://以下的几个函数返回0表示成功,返回小于0的值表示执行失败int Start(const std::string& ip,short port);   //服务器启动//根据HTTP请求字符串,进行反序列化。从socket 读取一个字符串,输出是一个Request对象int ReadOneRequest(Context* context); //根据Response对象,拼接成一个字符串,写回到客户端int WriteOneRequest(Context* context);//根据Request对象,构造Response对象int HeadlerRequest(Context* context);                                                                                                                int Process404(Context* context);int ProcessStaticFile(Context* context);int ProcessCGI(Context* context);
private:static void* ThreadEntry(void* arg);int ParseFirstLine(const std::string& first_line,std::string* method,std::string* url);int ParseUrl(const std::string& url, std::string* url_path, std::string* query_string);int ParseHeader(const std::string& header_line,Header* header);void GetFilePath(const std::string& url_path,std::string* file_path);//下面为测试函数void PrintRequest(const Request& req);
};}// end http_server

http_server_main.cc

#include <iostream>
#include "http_server.h"using namespace http_server;int main(int argc,char* argv[]){if(argc != 3){std::cout << "Usage ./server [ip][port]" << std::endl;return 1;}HttpServer server;server.Start(argv[1],atoi(argv[2]));
}

http_server_test.cc

#include "http_server.h"
#include <iostream>
using namespace http_server;int main(){HttpServer server;int ret = server.Start("0",9090);std::cout << "ret :"<< ret << std::endl;
}

until.hpp

//
//此文件放置一些工具类和函数
//为了让这些工具用的方便,直接把声明和实现都放在.hpp中
/
#pragma once
#include <iostream>
#include <fstream>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <vector>
#include <sys/time.h>
#include <sys/socket.h>class TimeUtil{
public://获取到当前的秒级时间戳static int64_t TimeStamp(){     struct timeval tv;gettimeofday(&tv,NULL);return tv.tv_sec;}//获取当前的微秒级时间戳static int64_t TimeStampUS(){struct timeval tv;gettimeofday(&tv,NULL);//123456return 1000*1000*tv.tv_sec +tv.tv_usec;}
};enum LogLevel{DEBUG,INFO,     //一般的信息WARNING,   //警告ERROR,     //一般错误CRITIAL,    //致命错误
};inline std::ostream& Log(LogLevel level,const char* file,int line){std::string prefix ="I";if(level == WARNING){prefix ="W";}else if(level == ERROR){prefix ="E";}else if(level == CRITIAL){prefix = "C";}else if(level == DEBUG){prefix = "D";}std::cout <<"[" << prefix << TimeUtil::TimeStamp() << "" << file << ":" << line << "]";return std::cout;
}//__FILE__ __LINE__
#define LOG(level) Log(level,__FILE__,__LINE__)class FileUtil{
public://从文件描述符中读取一行//一行的界定标识是\n  \r  \r\n//返回的line中是不包含界定标识的//例如://aaa\nbbb\nccc//调用ReadLine返回的line对象的内容为aaa,不包含\nstatic int ReadLine(int fd,std::string* line){line->clear();while(true){char c = '\0';ssize_t read_size = recv(fd, &c, 1, 0);//recv用法和read相似if(read_size <= 0){return -1;}if (c == '\n'){break;}//如果当前字符是\r\,把这个情况处理成\nif(c == '\r'){//虽然从缓冲区读了一个字符,但是缓冲区并没有把它删掉recv(fd, &c,1,MSG_PEEK);if(c == '\n'){//发现\r后面一个字符刚好就是\n,为了不影响下次循环就需要把这样的字符从缓冲区中干掉recv(fd, &c, 1, 0);break;}else{     //如果不是\n则强制设置成\nc = '\n';}}//这个条件涵盖了\r和\r\n的情况if (c == '\n'){break;            }line->push_back(c);}return 0;}static int ReadN(int fd,size_t len,std::string* output){output->clear();char c = '\0';for(size_t i= 0;i < len;++i){recv(fd,&c,1,0);output->push_back(c);}return 0;}//从文件中读取全部内容到 std::string中static int ReadAll(const std::string& file_path,std::string* output){std::ifstream file(file_path.c_str());if(!file.is_open()){LOG(ERROR) << "Open file error! file_path=" << file_path << "\n";return -1;}file.seekg(0,file.end);//seekg调整文件指针的位置,此处将文件指针调整到文件末尾int length = file.tellg(); //查询当前文件指针的位置,返回值就是文件指针位置相对于文件起始位置的偏移量//为了从头读取文件,需要把文件指针设置到开头位置file.seekg(0,file.beg);//读取完整的文件内容output->resize(length);//开辟内存,开辟保证能存下length的长度file.read(const_cast<char*>(output->c_str()),length);//万一忘记写close,问题不大,因为ifstream对象会在析构的时候自动的进行关闭文件描述符file.close();return 0;}static int ReadAll(int fd,std::string* output){//TODO 此函数完成从文件描述符中读取所有数据的操作while(true){char buf[1024] = {0};ssize_t read_size = read(fd, buf, sizeof(buf) - 1);if(read_size < 0){perror("read");return -1;}if(read_size == 0){//文件读完了return 0;}buf[read_size] = '\0';(*output) += buf;}return 0;}static bool IsDir(const std::string& file_path){return boost::filesystem::is_directory(file_path);}
};class StringUtil{
public://把一个字符串,按照split_char进行切分,分成的N个子串,放到output数组中//token_compress_on 的含义是://例如分隔符是空格,字符串就是"a  b"(中间有两个空格)//对于打开压缩情况,返回的子串就是有两个,"a","b"//token_compress_off//对于关闭压缩的情况,返回的子串就是有三个,"a"," ","b"static int Split(const std::string& input,const std::string& split_char,std::vector<std::string>* output){boost::split(*output,input,boost::is_any_of(split_char),boost::token_compress_on);return 0;}typedef std::unordered_map<std::string, std::string> UrlParam;static int ParseUrlParam(const std::string& input, UrlParam* output){//1.按照取地址符号切分成若干个kvstd::vector<std::string> params;Split(input, "&", &params);//2.再针对每一个kv,按照 = 切分,放到输出结果中//range based forfor (auto item : params){std::vector<std::string> kv;Split(item, "=", &kv);if(kv.size() != 2){//说明该参数非法LOG(WARNING) << "kv format error! item=" << item << "\n";continue;}(*output)[kv[0]] = kv[1];}return 0;}
};

cgi_main.cc

#include <iostream>
#include <string>
#include <sstream>
#include "until.hpp"void HttpResponse(const std::string& body){std::cout << "Content-Length:" << body.size() << "\n";std::cout << "\n";    //这个\n是http协议中的空行std::cout << body;return;
}
//这个代码生成的就是我们的CGI程序,通过这个CGI程序完成具体的业务
//本CGI程序仅仅完成简单的两个数的加法运算
int main(int argc,char* argv[], char* env[]){size_t i =0;for(;env[i] != NULL;++i){std::cerr << "env:" << env[i] << "\n";}//1.先获取到methodconst char* method = getenv("REQUEST_METHOD");if(method == NULL){HttpResponse("No env REQUEST_METHOD!");return 1;}StringUtil::UrlParam params;if(std::string(method) == "GET"){//2.如果是GET请求,从QUERY_STRING读取请求参数 //4.解析query_string或者body中的数据 const char* query_string = getenv("QUERY_STRING");StringUtil::ParseUrlParam(query_string,&params);}else if(std::string(mothod) == "POST"){//3.如果是POST请求,从CONTENT_LENGTH读取body的长度//  根据body的长度,从标准输入中读取请求的body//4.解析query_string或者body中的数据const char* content_length = getenv("CONTENT_LENGTH");char buf[1024 *10] = {0};read (0.buf,sizeof(buf) - 1);StringUtil::ParserlParam(buf,&params);//5.根据业务需要进行计算,此处的计算是a+b的值int a = std::stoi(params["a"]);int b = std::stoi(params["b"]);int result = a + b;//6.根据计算结果,构造响应的数据,写回到标准输出中std::stringstream ss;ss << "<hl> result = " << result << "</hl>";HttpResponse(ss.str());return 0;
}

Web版简易加法计算相关推荐

  1. 智慧零售erp通用版管理系统+门店管理+商品管理+厂商管理+财务管理+销售管理+仓储管理+Axure高保真交互ERP通用版零售行业web端简易版管理系统

    作品介绍:智慧零售erp通用版管理系统+门店管理+商品管理+厂商管理+财务管理+销售管理+仓储管理+Axure高保真交互ERP通用版零售行业web端简易版管理系统 原型交互及下载请点击:https:/ ...

  2. Python 简易版小工具 | 计算天数

    文章目录 简易版小工具 | 计算天数 需求 实现思路 代码实现 使用效果 简易版小工具 | 计算天数 需求 给定一个日期,格式如 "2020-2-12",计算出这个日期是 2020 ...

  3. jquery字体颜色_基于jquery实现的web版excel

    基于jquery实现的web版excel.包含excel的基本功能 支持合并单元格,拆分单元格 支持插入单元格,删除单元格 支持整行整列选择单元格 自定义右键菜单,可以设置单元格数量 支持鼠标左键拖动 ...

  4. 重磅!微软发布 Visual Studio Online:Web 版 VS Code + 云开发环境

    今天(北京时间 2019 年 11 月 4 日),在 Microsoft Ignite 2019 大会上,微软正式发布了 Visual Studio Online 公开预览版! 概览 Visual S ...

  5. 微软神操作!Web 版 VS Code 来了!

    作者 | formulahendry 责编 | 屠敏 今天(北京时间 2019 年 5 月 7 日),在微软 Build 2019 开发者大会上,微软宣布了 Web 版本的 VS Code - Vis ...

  6. 使用webgl(three.js)搭建3D智慧园区、3D大屏,3D楼宇,智慧灯杆三维展示,3D灯杆,web版3D,bim管理系统——第六课...

    前言: 今年是建国70周年,爱国热情异常的高涨,为自己身在如此安全.蓬勃发展的国家深感自豪. 我们公司楼下为庆祝国庆,拉了这样的标语,每个人做好一件事,就组成了我们强大的祖国. 看到这句话,深有感触, ...

  7. 斗地主Java课程设计_JAVA面向对象编程课程设计——web版斗地主

    一.团队课程设计博客链接 二.个人负责模块或任务说明 实体类的设计 斗地主规则的实现 人机自动出牌的算法 实现数据库的DAO模式 三.自己的代码提交记录 注:这里只有部分提交记录,详细的提交记录在团队 ...

  8. 【Three.js】web版 3D坦克大战

    基于 three.js 开发的web版坦克大战 基础部分介绍 光源 坐标 履带印和爆炸灰烬 波纹 音效 玩家初始护盾 地图元素 升级提示 坦克 敌人逻辑 玩法介绍 敌人种类 道具种类 性能优化 移动碰 ...

  9. web版“节奏猜猜看“小工具-Web Workers+requestAnimationFrame+transition

    "节奏猜猜看"锻炼节奏感的web小工具 前段时间想弹吉他,之前在琴行学了点基础,由于很久没摸琴(*/ω\*),对节奏感缺乏自信,便想到了节拍器,市面上的节拍器多种多样,主要功能就是 ...

最新文章

  1. 华为视觉研究路线图:三大挑战,六项计划
  2. 基于MFC相机采集的实现与采集回调函数的应用实例
  3. Struts框架的入门使用
  4. 创建代码生成器可以很简单:如何通过T4模板生成代码?[下篇]
  5. guava中的Joiner
  6. 正弦水波纹波动画 - SJWaveView
  7. nohttp网络框架
  8. Emacs下查词典(StarDict篇)
  9. java语言的演化——读JavaOne ppt笔记
  10. Mysql数据恢复有哪几种_MYSQL 数据恢复有哪些
  11. 清华大学马少平:一款智能硬件背后的“AI争霸”...
  12. 岗位po是什么意思_敏捷开发团队中PO和SM角色介绍
  13. 新番 | 万万没想到,Hulu有一天也开始推新番了
  14. 小马哥----高仿苹果6S A236 刷机拆机主板图与开机界面图 更新解锁界面 全网通4G 警惕
  15. Android系统直接输出Excel文档
  16. 停止线程 暂停线程
  17. 异步(感应)电机概述
  18. matlab圈和叉,画圈圈和画叉叉的区别
  19. NB-IoT使用笔记(3)在linux下使用python搭建UDP服务
  20. js判断字符串是否包含中文或英文

热门文章

  1. 用栈和队列实现魔王语言
  2. 高中计算机矩阵算法ppt,矩阵及其基本算法.ppt
  3. (钻石问题)一楼到十楼的每层电梯门口都放着一颗钻石,钻石大小不一。你乘坐电梯从一楼到十楼,每层楼电梯门都会打开一 次,只能拿一次钻石,问怎样才能拿到最大的一颗?
  4. 电脑pin码忘了登录不进系统_升级win10系统必须要做的5件事,装机、重装系统必知...
  5. 微信正测试新功能:同一个手机号可注册两个微信号
  6. windows上面运行jar文件
  7. QT打开文件并显示文件内容
  8. 为什么要有refreshToken
  9. 用Visual Studio查看图片的二进制流
  10. css制作炫酷的罗盘时钟特效(附代码)