目录
- 开篇废话
- 正文
- Http工作流程
- Http.h
- Http.cpp
- 个人网站链接
- 源码地址
- 新的解析协议
-
开篇废话
其实这篇文章一直想写,苦于没有时间,想一气呵成写完,在离回家前一天晚上,在上海的小宾馆里面异常兴奋,写一下如何用C++搭建一个简易的http服务器。
我相信大部分人都希望渴望一个自己的网站(哪怕在破在破也是自己做的),我当初是就是这么渴望的,记得那是2020年的8月,我查阅的各种资料终于学会了socket,于是迫不及待的写了一个聊天程序,一直在研究如何端口映射。那时候还很傻很天真,来了一个连接就开一根线程,离开就销毁这根线程,那会也知道线程创建和销毁的开销很大,但是一直没有想到其他的好办法呀。现在回头看这个问题会显得很蠢,咋就没想到用线程池技术呢?学习就是一步一步进化的过程,不去羡慕谁、不去跟任何人比,只跟自己比!
本篇文章不会涉及到很多复杂的概念,也没有写很难读懂的模板函数,代码简单可读,本篇文章送给每一个想自己用C++写一个http服务器的小伙伴!高手们、大佬们当然可以不用看的啦,因为我目前还是个菜鸟~我会把我再学习http遇到的问题、想法都写在本章中。
正文
怎么写一个简单的http服务器阿?很简单,只需要返回最基本的3个东西即可。
- 状态码
- 发送文件的长度
- 发送文件的类型
状态码如200(找到请求文件)、404(未找到请求文件),我实现的也比较简单,就实现了这两个状态码。
文件长度比如客户请求的是index.html页面,浏览器如何知道收到的这个文件什么时候结束呢?就靠是文件的长度
文件类型 html的类型是html格式,css的是css格式,图片有图片的格式,zip有zip格式文件格式对应的文件类型表
返回给客户端三个这种东西加上请求的文件即可(存在请求文件的情况下)可以了
还想再复杂的话,可以看这篇介绍http各个参数的文章文章链接
Http工作流程
在这个部分大概介绍一下大概的http的工作流程。客户端通过网址访问到你的网站(一定要记住客户端是主动请求连接的,服务端是被动连接的),实则就是通过ip+port访问的,只不过http的默认端口号是80。比如你还没有域名、云服务器这些东西,那如何在本地测试呢?就是在浏览框输入ip:port,例如127.0.0.1:9996,按下回车就可以在本地访问自己的http服务器了。当然了http要给客户(请求者)一个首页,当客户没有指定网页,单纯的打出域名或者127.0.0.1:9996,就给他一个默认的首页,这也是我们要实现的事情。客户写了请求文件,我们来判断是否存在,存在就返回状态码200和请求文件的内容。不存在就直接返回404。那我们如何判断啊,拿最简单的GET为例子吧,其他也大同小异,有兴趣的同学可以自行研究其他的。我们利用正则表达式来解析客户发来的请求是GET还是POST还是其他请求,在解析出来要请求文件。再利用状态机思想来文件是否存在,若存在在判断文件的类型。大概的流程就是这些。
Http.h
#pragma once
#include <string>
#include <unordered_map>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
class TcpClient;
class Http{
const std::string path_ = "./www/dxgzg_src";
std::string filePath_;
std::string fileType_;
std::string header_;
int fileSize_;
int fileFd_;
struct stat fileStat_;
bool isPostMode_ = false;
public:
Http() = default;
void addHeader(const std::string& head);
void Header(bool flag);
void processHead();
void addFilePath(const std::string& requestFile);
void analyseFileType(const std::string& requestFile);
bool analyseFile(const std::string& request);
void SendFile(int clientFd,bool isRequestOk);
bool fileIsExist();
void ReadCallback(TcpClient* t);
};
Http.cpp
#include "Http.h"
#include "TcpClient.h"
#include "Logger.h"
#include <regex>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <unordered_map>
#include <iostream>
using namespace std;
void Http::addHeader(const string& head)
{
if (!head.empty())
{
header_ += head;
header_ += "\r\n";
}
else
{
header_ += "\r\n";
}
}
void Http::Header(bool flag)
{
if(flag == true)
{
header_ = "HTTP/1.1 200 OK\r\n";
}
else
{
header_ = "HTTP/1.1 404 NOTFOUND\r\nContent-Length:0\r\n\r\n";
}
}
void Http::processHead()
{
string ContentType = "Content-Type:";
if (fileType_ == "html")
{
ContentType += "text/html";
}
else if(fileType_ == "js")
{
ContentType += "application/x-javascript";
}
else if(fileType_ == "css")
{
ContentType += "text/css";
}
else if(fileType_=="jpg" || fileType_== "png")
{
ContentType += "image/" + fileType_;
}
else if (fileType_== "zip" || fileType_ == "tar")
{
ContentType += "application/" + fileType_;
}
addHeader(ContentType);
fileSize_= fileStat_.st_size;
string ContentLength = "Content-Length:" + to_string(fileSize_);
addHeader(ContentLength);
addHeader("");
}
void Http::addFilePath(const string& requestFile)
{
filePath_ += requestFile;
}
void Http::analyseFileType(const string& requestFile)
{
for (int i = 0; i < requestFile.size(); ++i)
{
if (requestFile[i] == '.')
{
fileType_ = requestFile.substr(i + 1);
}
}
}
bool Http::fileIsExist(){
fileFd_ = ::open(filePath_.c_str(),O_CLOEXEC | O_RDWR);
if (fileFd_ < 0)
{
return false;
}
return true;
}
bool Http::analyseFile(const string& request)
{
string pattern = "^([A-Z]+) ([A-Za-z./1-9-]*)";
regex reg(pattern);
smatch mas;
regex_search(request,mas,reg);
if(mas.size() < 3){
LOG_INFO("不是正常请求");
return false;
}
string requestMode = mas[1];
if(requestMode == "POST"){
isPostMode_ = true;
cout << "POST请求!!!!!" << endl;
}
string requestFile = mas[2];
bool flag;
if (requestFile == "/")
{
filePath_.clear();
filePath_ = path_;
filePath_ += "/run.html";
fileType_ = "html";
}
else
{
filePath_.clear();
filePath_ = path_;
addFilePath(requestFile);
}
flag = fileIsExist();
if(!flag){
LOG_INFO("未找到客户要的文件");
cout << filePath_ << endl;
return false;
}
::fstat(fileFd_,&fileStat_);
analyseFileType(requestFile);
return true;
}
void Http::SendFile(int clientFd,bool isRequestOk)
{
long len = 0;
while(len < header_.size()){
len += ::send(clientFd,header_.c_str(),header_.size(),0);
cout << "len header" << header_ <<endl;
}
if (isRequestOk == true)
{
len = 0;
int num = 0;
int tmpLen = 0;
while (len < fileSize_)
{
::sendfile(clientFd,fileFd_,(off_t*)&len,fileStat_.st_size- len);
cout << "len sendfile" <<"len:" << len << "fileSize" << fileSize_ <<endl;
if(len <= 0 ){
break;
}
if(tmpLen == len){
++num;
if(num > 10){
break;
}
}
tmpLen = len;
}
}
}
void Http::ReadCallback(TcpClient* t){
cout << "ReadCallback" << endl;
int sockFd = t->getFd();
char buff[1024];
int r = ::recv(sockFd,buff,sizeof(buff),0);
if (r == 0)
{
t->CloseCallback();
return;
}
buff[r] = '\0';
string str = buff;
cout << str << endl;
bool flag = analyseFile(str);
Header(flag);
if(!flag){
SendFile(sockFd,false);
return ;
}
processHead();
SendFile(sockFd,true);
if(isPostMode_){
int fd = ::open("./postLog/message.txt",O_RDWR);
if(fd < 0){
LOG_ERROR("未找到文件");
}
else{
::lseek(fd,0,SEEK_END);
::write(fd,str.c_str(),str.size());
close(fd);
}
isPostMode_ = true;
}
close(fileFd_);
}
不考虑高并发的情况,设计一个同步阻塞的epoll即可,看完http必备的三要素已经能够写出一个服务器了,我的底层socket采用的是自己封装的网络库,Reactor模型,one loop per thread的代码文件比较多,所以就没有放上来,但只要把状态码、文件类型(那一大段if)、文件的长度这三个实现了就可以搭建一个简易的http服务器了。可以利用sendfile零拷贝来发送文件
在补充一点就是,http协议是\r\n结尾。最后还有一个\r\n,就比如404,HTTP/1.1 404 NOTFOUND\r\nContent-Length:0\r\n\r\n,最后面再跟一个\r\n,结束一段跟一个\r\n
个人网站链接
个人网站
源码地址
2021.10.25更,最近终于有空把自己的代码重构了一下,感觉写的还可以,附上链接github链接有兴趣可以点个星哈哈哈哈,可以去github上看一看
新的解析协议
2022.5.18更新,根据\r\n解析每一行,这算一个能跑的伪码,在服务端的话还会设置一下状态码来标识当前解析的状态,所以想了解具体的话可以去github下载源码来看。
简单介绍一下
有一条请求是GET /add?name=hello world&age=19,之前的做法是不支持这样的解析,现在已经支持这样的解析。
#include <string>
#include <unordered_map>
#include <iostream>
#include <map>
#include <vector>
using namespace std;
map<string,string> queryKV;
pair<string,string> spilt(const string& s,string sep,size_t& pos,size_t endIndex,size_t addPos){
size_t startIndex = s.find(sep,pos);
if(startIndex == string::npos)return pair<string,string>();
string key = s.substr(pos,startIndex - pos);
string value ="";
if(endIndex == string::npos){
value = s.substr(startIndex + 1);
pos = s.size();
} else{
value = s.substr(startIndex + 1, endIndex - startIndex - 1);
pos = endIndex + addPos;
}
pair<string,string> ans(key,value);
return ans;
}
void example(){
string s = "POST /addMsg HTTP/1.1\r\nHost: 192.168.0.106 : 9996\r\nConnection : keep - alive\r\n\r\n{\"content\":\"test\"}";
size_t oneIndex = s.find("\r\n");
string oneData = s.substr(0, oneIndex);
size_t index1 = oneData.find(" ");
size_t index2 = oneData.find(" ",index1 + 1);
string method = oneData.substr(0,index1);
string path = oneData.substr(index1 + 1,index2 - index1 - 1);
size_t flagSpilt = path.find("?");
string query = "";
if(flagSpilt != string::npos){
query = path.substr(flagSpilt + 1);
size_t pos = 0;
while(1){
size_t endIndex = query.find("&",pos);
cout << endIndex << endl;
auto p = spilt(query,"=",pos,endIndex,1);
if(p.first == "" && p.second == "")break;
cout << p.first << " " << p.second << endl;
queryKV[p.first] = p.second;
}
}
string httpVersion = oneData.substr(index2 + 1,oneIndex - index2 - 1);
cout << "method:" << method << endl;
cout << "path:" << path <<endl;
cout << "query:" << query << endl;
cout << "httpVersion:" << httpVersion << endl;
for(auto p: queryKV){
cout << "key:" << p.first;
cout << " value:" << p.second << endl;
}
}
void example2(){
string s = "POST /addMsg?name=dxgzg&age=19 HTTP/1.1\r\nHost: 192.168.0.106 : 9996\r\nConnection : keep - alive\r\n\r\n{\"content\":\"test\"}";
size_t oneIndex = s.find("\r\n");
string oneData = s.substr(0, oneIndex);
example();
size_t lastlineIndex = 0;
lastlineIndex = s.find("\r\n\r\n");
size_t index = oneIndex + 2;
size_t endIndex = 0;
map<string,string> m;
while( index < lastlineIndex && ((endIndex = s.find("\r\n",index)) != string::npos)){
auto p = spilt(s,":",index,endIndex,2);
m[p.first] = p.second;
}
for(auto& p : m){
cout << "key:" << p.first;
cout << " value:" << p.second << endl;
}
size_t dataIndex = lastlineIndex + 4;
string data = s.substr(dataIndex);
cout << data.size() << endl;
cout << data << endl;
}
int main() {
example();
example2();
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)