
Source from the Internet, please delete if infringing
This article will not involve many complex concepts and does not write difficult-to-understand template functions. The code is simple and readable. This article is dedicated to everyone who wants to write an HTTP server in C++! Experts and big shots can skip this!
Main Text
How to write a simple HTTP server? It’s very simple, you just need to return the three basic things.
-
Status Code
-
File Length
-
File Type
Status Code such as 200 (file found), 404 (file not found), I implemented these two status codes quite simply.
File Length For example, if the client requests the index.html page, how does the browser know when the received file ends? It relies on the file length.
File Type The type for HTML is HTML format, for CSS it is CSS format, images have their formats, and ZIP has its ZIP format corresponding to the file type table (https://tool.oschina.net/commons).
Return these three items to the client along with the requested file (if the requested file exists).
HTTP Workflow
This section briefly introduces the general workflow of HTTP. The client accesses your website via the URL (remember that the client actively requests the connection, while the server is passively connected). In essence, it is accessed through IP + port, but the default port for HTTP is 80. For example, if you do not have a domain name or cloud server, how can you test locally? Just enter ip:port in the browser, such as 127.0.0.1:9996, and press Enter to access your HTTP server locally. Of course, HTTP should give the client (requester) a homepage. When the client does not specify a webpage and simply types the domain name or 127.0.0.1:9996, it should provide a default homepage, which is also something we need to implement. When the client writes a request file, we check whether it exists. If it exists, we return status code 200 and the content of the requested file. If it does not exist, we return 404 directly. How do we determine that? Let’s take the simplest GET as an example; the others are similar, and interested students can research the others themselves. We use regular expressions to parse whether the request sent by the client is GET or POST or another type of request, and parse the requested file. We then use the state machine concept to check if the file exists; if it exists, we determine the file type. This is the general flow.
Http.h
#pragma once
#include <string>
#include <unordered_map>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
class TcpClient;
class Http {
// Root directory of the file
const std::string path_ = "./www/dxgzg_src";
std::string filePath_; // Absolute path of the requested file
std::string fileType_; // Type of the requested file
std::string header_; // HTTP header response
int fileSize_;
int fileFd_;
struct stat fileStat_;
// If it's a POST request, save the message to a local file
bool isPostMode_ = false;
public:
Http() = default;
void addHeader(const std::string& head);
void Header(bool flag);
// Add header information, only call this function when successful,
// and return data from the file
void processHead();
// Add the requested file's path
void addFilePath(const std::string& requestFile);
// Get the file type
void analyseFileType(const std::string& requestFile);
bool analyseFile(const std::string& request);
void SendFile(int clientFd, bool isRequestOk);
bool fileIsExist();
// User-defined callback function must handle exceptions correctly and close the socket
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> // For test
#include <iostream>
using namespace std;
void Http::addHeader(const string& head) {
if (!head.empty()) {
header_ += head;
header_ += "\r\n"; // Console.WriteLine("I'm here head!= null" + header_);
} else {
header_ += "\r\n"; // Console.WriteLine("I'm here head == null" + header_);
}
}
void Http::Header(bool flag) {
// Determine the header to send; true means 200, false means 404
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);
// To be improved, need to open the file; filePath_ is the path of the requested file
fileSize_ = fileStat_.st_size;
string ContentLength = "Content-Length:" + to_string(fileSize_);
addHeader(ContentLength);
// Finally add an end
addHeader(""); // Console.WriteLine("process fileContent_:");
}
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] == '.') {
// Get the ending of the requested file
fileType_ = requestFile.substr(i + 1);
}
}
}
bool Http::fileIsExist() {
fileFd_ = ::open(filePath_.c_str(), O_CLOEXEC | O_RDWR);
if (fileFd_ < 0) {
// File not found
return false;
}
return true;
}
bool Http::analyseFile(const string& request) {
// Call the header
// In [], ^ means starting with something, what's inside [] means not
string pattern = "^([A-Z]+) ([A-Za-z./1-9-]*)";
regex reg(pattern);
smatch mas;
regex_search(request, mas, reg);
// Index 0 represents the overall match
if (mas.size() < 3) {
LOG_INFO("Not a normal request");
// Not anything directly return false
return false;
}
string requestMode = mas[1];
if (requestMode == "POST") {
isPostMode_ = true;
cout << "POST request!!!!!!!" << endl;
}
// Specific requested file
string requestFile = mas[2];
// If it's /, give default value
bool flag;
if (requestFile == "/") {
filePath_.clear(); // Clear first
filePath_ = path_;
filePath_ += "/run.html";
// File type must also be added
fileType_ = "html";
} else {
filePath_.clear(); // Clear first
filePath_ = path_;
addFilePath(requestFile);
// Use open function
}
flag = fileIsExist();
// If the file is not found
if (!flag) {
LOG_INFO("File not found for the client");
cout << filePath_ << endl;
return false;
}
::fstat(fileFd_, &fileStat_);
// If the file does not exist, there is no need to parse type
analyseFileType(requestFile);
return true;
}
void Http::SendFile(int clientFd, bool isRequestOk) {
long len = 0;
// The header must exist.
while (len < header_.size()) {
len += ::send(clientFd, header_.c_str(), header_.size(), 0);
cout << "len header" << header_ << endl;
}
// After sending the header, send the requested file information. If it's 404, there is none
if (isRequestOk == true) {
len = 0;
int num = 0;
int tmpLen = 0; // If it doesn't change for several times, add a num
while (len < fileSize_) {
// The number of files sent has been written into len
::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;
// If the file is not found, respond with 404.
bool flag = analyseFile(str);
Header(flag);
if (!flag) {
SendFile(sockFd, false);
// t->CloseCallback();
return;
}
// This modifies the header file, call this first
processHead();
// This is the file found and sent
SendFile(sockFd, true);
if (isPostMode_) {
int fd = ::open("./postLog/message.txt", O_RDWR);
if (fd < 0) {
LOG_ERROR("File not found");
} else {
// Move the file offset to the end
::lseek(fd, 0, SEEK_END);
::write(fd, str.c_str(), str.size());
close(fd);
}
isPostMode_ = true;
}
// Close the file socket
close(fileFd_);
// Close the connection after sending, mainly to allow several threads to run faster
// t->CloseCallback();
}
Without considering high concurrency, design a synchronous blocking epoll. After understanding the three essentials of HTTP, you can already write a server. My underlying socket uses a self-encapsulated network library with the Reactor model; the code files are quite numerous, so I did not include them. As long as you implement the status code, file type (that large section of if statements), and file length, you can build a simple HTTP server. You can use sendfile zero-copy to send files.
Additionally, the HTTP protocol ends with \r\n. There is also a \r\n at the end, such as 404, HTTP/1.1 404 NOTFOUND\r\nContent-Length:0\r\n\r\n, followed by another \r\n to end a segment and another \r\n.
If you are over 18 years old and find learning [C Language] too difficult? Want to try another programming language? Then I recommend you learn Python. There is a limited-time free course worth 499 yuan for Python beginners, limited to 10 spots!
▲ Scan the QR code - Get it for free
