目录
- 背景
- 目标
- 描述
- 技术特点
- 开发环境
- WWW
- 客户端浏览发展史
- 服务端http发展史
- http分层概览
背景
http协议被广泛使用,从移动端,pc浏览器,http无疑是打开互联网应用窗口的重要协议,http在网络应用层中的地位不可撼动,是能准确区分前后的重要协议、
目标
对http协议的理论学习,从零开始完成web服务器开发,作用下三层协议,从技术到应用,让网络难点无处遁形
描述
采用C/S模型,编写支持中小型应用的http,并结合mysql,理解常见互联网应用行为,完全理解从上网开始,到关闭浏览器的所有操作中的技术细节
技术特点
网络编程(TCP/IP协议,socket流式套接字,http协议)
多线程技术
cgi技术
线程池
开发环境
cents 7/ubuntu 20.04 + vim/gcc/gdb+c/c++
WWW
www是环球信息网的缩写,(亦作“Web”、“WWW”、“W3”,英文全称为“World Wide Web”),中文名字‘万维网,“环球网”’等,常简称为Web
分为Web客户端和Web服务器程序。www可以让Web客户端(常用浏览器)访问浏览Web的页面。是一个由许多互相连接的超文本组成的系统,通过互联网访问。在这个系统中,每个有用的事物,称为一样“资源”;并且由一个全局“统一资源标识符”(URI)标识;这些资源通过超文本传输协议(Hypertext Transfer protocol)传送给用户,而后者通过点击链接来获得资源
万维网联盟(World Wide Web Consortium,简称W3C),又称W3C理事会。1994年10月在麻省理工学院计算机科学实验室成立。万维网联盟的创始人是万维网的发明者蒂姆·博纳斯-李。–摘自这里
客户端浏览发展史
1990年11月,世界上第一台Web服务器和Web浏览器诞生
1993年1月,NCSA(美国国家超级计算机应⽤中心NationalCenterfor SupercomputerApplications,简称NCSA)研发html内联显⽰图⽚的浏览器Mosaic,不久windows和苹果mac版的Mosaic相继出
现
NASAhttpd1.0也差不多这个时期出现
1994年12⽉网景公司NetscapeNavigator1.0(网景领航员)浏览器出现
1995年微软发布IE1.0和2.0
紧随其后,web服务器标准Apache0.2诞⽣
1995年左右,微软和⽹景针对html标准开始打仗
2000年,⽹景衰落
2004年,Mozilla(缩写MF或MoFo,全称Mozilla基⾦会,是为⽀持和领导开源的Mozilla项⽬⽽设⽴的⼀个⾮营利组织)基⾦发布firefox,第⼆次浏览器⼤战⼜开始了
随后,IE发布6,7,8,9,10版本,同步Chrome,Opera,Safari浏览器也开始抢占市场
今天的浏览器格局形成
服务端http发展史
1990年,HTTP/0.9诞⽣
1996年5⽉,HTTP/1.0标准诞⽣,记载于RFC1945,该标准⾄今仍然被使⽤
1997年1⽉,HTTP/1.1问世,是⽬前使⽤的主流http版本
HTTP/2.0正在定制,但要被⼴泛使⽤,仍旧需要较多时间
http分层概览
整体来看
细节
http相关协议
TCP
IP
DNS
DNS
http背景补充
目前主流使用http1.1,按照1.0来实现
http是无状态的,不会保留之前的请求和响应,为了保持状态,引入了cookie
URI、URL、URN
URI,是uniform resource identifier,统⼀资源标识符,⽤来唯⼀的标识⼀个资源
URL,是uniform resource locator,统⼀资源定位符,它是⼀种具体的URI,即URL可以⽤来标识⼀个资源,⽽且还指明了如何locate这个资源。
URN,uniform resource name,统⼀资源命名,是通过名字来标识资源,⽐如mailto:javanet@java.sun.com。
URI是一种抽象的,高层次概括定义统一资源表示,URL和URN是具体的资源表示方式,都是一种URI
URL是URI的子集,任何东西,只要能唯一标识出来,都可以说是URI。如果这个标识可以获取到上述对象的路径,同时也它可以是一个URL,但如果这个标识不提供获取对象的路径,,必然不是URL
URI: /home/index.html
URL: www.xxx.com:/home/index.html
浏览器URL格式
http是基于tcp的连接方式进行网络连接,1.1版本可以持续的长连接机制,绝大多数的web开发,都是构建在http协议上的应用
http url的格式如下:
http表⽰要通过HTTP协议来定位⽹络资源
host表⽰合法的Internet主机域名或者IP地址,本主机IP:127.0.0.1
port指定⼀个端⼝号,为空则使⽤缺省端⼝80
abs_path指定请求资源的URI
如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,通常这个⼯作浏览器⾃动帮我们完成。默认访问首页
一个较完整的http请求:
http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1
请求与响应
示意图
细节
请求格式
响应格式
请求的方法
GET:获取资源,获取被URI标识的资源,重点
POST:传输实体主体,重点
PUT:传输文件,指定文件放的uri所标识的路径,类似ftp,有安全问题,大部分web不用
HEAD:获取报文首部,和GET类似,但是不返回报文主体部分。用于确认uri的有效性以及资源的日期等
DELETE:与put相反,删除uri指定的资源,不安全,一般也不使用
OPTIONS:询问支持方法
TRACE:追踪路径
CONNECT:使用隧道协议连接代理
总结方法:
响应,状态码及描述
状态码用表示服务器http响应状态的3位数字代码、通过状态码,就可以知道服务端是否正确的处理请求,如果不正确,是什么原因导致的(404)
是http返回给浏览器的状态码
状态码分类
常见状态码
2XX 成功:结果正确处理
200 OK:客户端的请求,被正确处理了
204:请求结果正确处理,但响应信息没有正文
206 Partial Content:表示客户端队服务器进行了范围请求,而且服务器成功执行了这部分GET请求,响应报文中由Content-Range指定的实体内容范围
3XX 成功:浏览器需要执行某些特殊的处理以正确处理请求
301 Moved Permanently:永久性重定向,表示请求的资源被分配了新的uri,以后用新的uri,如果之前将老的uri保存为书签了,后面应该按照响应的location首部字段重新保存书签
302 Found:临时重定向,目标资源分配了新的uri,希望用户本次用新的uri访问
307 Temporary Redirect:临时重定向,该状态码与302有相同含义,307会遵守标准,不会从post变get,每种浏览器可能有不同的情况
4XX 客户端错误:客户端发生错误的原因
400 Bad Request:表明请求报文中存在语法错误,需修改请求内容重新发送,另外,浏览器会像200 OK一样对待该状态码
403 Forbidden:表明浏览器请求的资源被服务器拒绝了,服务器没有必要给出详细理由,如果要说明,可以在响应实体内部说明
404 Not Found:没有请求的资源
5XX 服务器错误:服务器本身发生错误
500 Internal Server Error:服务端执行发生错误,可能是web本身的bug或临时故障
503 Server Unavailable:服务器处于超负荷或正在停机维护,目前无法处理请求,最好写入Retry-After首部字段返回客户端
CGI机制
CGI(CommonGatewayInterface)是WWW技术中最重要的技术之⼀,有着不可替代的重要地位。CGI是外部应⽤程序(CGI程序)与WEB服务器之间的接⼝标准,是在CGI程序和Web服务器之间传递信息的过程
浏览器除了从服务器下获得资源(⽹⻚,图⽚,⽂字等),有时候还有能上传⼀些东西(提交表单,注册⽤⼾之类的),看看我们⽬前的http只能进⾏获得资源,并不能够进⾏上传资源,所以⽬前http并不具有交互式。为了让我们的⽹站能够实现交互式,我们需要使⽤CGI完成,时刻记着,我们⽬前是要写⼀个http,所以,CGI的所有交互细节,都需要我们来完成。包括http提供的CGI机制和自己实现CGI程序
首先区分get和post的区别
GET⽅法从浏览器传参数给http服务器时,是需要将参数跟到URI后⾯的
POST⽅法从浏览器传参数给http服务器时,是需要将参数放的请求正⽂的
GET⽅法,如果没有传参,http按照⼀般的⽅式进⾏,返回资源即可
GET⽅法,如果有参数传⼊,http就需要按照CGI⽅式处理参数,并将执⾏结果(期望资源)返回给浏览器
POST⽅法,⼀般都需要使⽤CGI⽅式来进⾏处理
线程池介入
大量链接会导致内部进程线程暴增,让服务器效率恒定
完整代码
服务器部分
TcpServer.hpp
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include "Log.hpp"
#define BACKLOG 5
class TcpServer
{
public:
TcpServer(int port)
:_port(port),
_listen_sock(-1)
{
}
void InitServer()
{
Socket();
Bind();
Listen();
LOG(INFO, "tcpserver init success...");
}
void Socket()
{
_listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_listen_sock < 0)
{
LOG(FATAL, "socket error!");
exit(1);
}
int opt = 1;
setsockopt(_listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
}
void Bind()
{
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_addr.s_addr = INADDR_ANY;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
if (bind(_listen_sock, (const sockaddr*)&local, sizeof(local)) < 0)
{
LOG(FATAL, "bind error!");
exit(2);
}
}
void Listen()
{
if (listen(_listen_sock, BACKLOG) < 0)
{
LOG(FATAL, "listen error!");
exit(3);
}
}
static TcpServer* GetInstance(int port)
{
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
if (_svr == nullptr)
{
pthread_mutex_lock(&mutex);
if (_svr == nullptr)
{
_svr = new TcpServer(port);
_svr->InitServer();
}
pthread_mutex_unlock(&mutex);
}
return _svr;
}
inline int Sock()
{
return _listen_sock;
}
~TcpServer()
{
if(_listen_sock >= 0)
{
close(_listen_sock);
}
}
private:
int _port;
int _listen_sock;
static TcpServer* _svr;
TcpServer(const TcpServer& x){
}
};
TcpServer* TcpServer::_svr = nullptr;
HttpServer.hpp
#pragma once
#include <signal.h>
#include "TcpServer.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#define PORT 8000
class HttpServer
{
public:
HttpServer(int port = PORT)
:_port(port),
//_tpc_server(nullptr),
_stop(false)
{
}
void InitServer()
{
// 信号忽略,不然写入时可能直接崩溃
signal(SIGPIPE, SIG_IGN);
//_tpc_server = TcpServer::GetInstance(_port);
}
void Loop()
{
//int listen_sock = _tpc_server->Sock();
TcpServer *tsvr = TcpServer::GetInstance(_port);
while (!_stop)
{
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(tsvr->Sock(), (sockaddr *)&peer, &len);
if (sock < 0)
{
continue;
}
LOG(INFO, "get a new link...");
// int* tmp_sock = new int(sock);
// pthread_t tid;
// pthread_create(&tid, nullptr, Entrance::HandlerRequest, tmp_sock);
// 引入线程池
Task task(sock);
ThreadPool::GetInstance()->PushTask(task);
//_pool.push_back(task);
}
}
~HttpServer()
{
}
private:
int _port;
//TcpServer* _tpc_server;
bool _stop;
};
服务器辅助分
Util.hpp
#pragma once
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
class Util
{
public:
static int ReadLine(int sock, std::string& out)
{
char ch;
while (ch != '\n')
{
ssize_t s = recv(sock, &ch, sizeof(ch), 0);
if (s > 0)
{
if (ch == '\r')
{
// 看看后面是不是\n \r或\r\n->\n
recv(sock, &ch, sizeof(ch), MSG_PEEK);
if (ch == '\n')
{
// \r\n只读\n
// 窥探到就一定存在
recv(sock, &ch, sizeof(ch), 0);
}
else
{
ch = '\n';
}
}
// 普通字符
// \n
out.push_back(ch);
}
else if (s == 0)
{
return 0;
}
else
{
return -1;
}
}
return out.size();
}
static bool CutString(const std::string& target, std::string& sub_out1, std::string& sub_out2, std::string sep)
{
ssize_t pos = target.find(sep);
if (pos != std::string::npos)
{
sub_out1 = target.substr(0, pos);
sub_out2 = target.substr(pos + sep.size());
return true;
}
return false;
}
private:
};
线程池部分
#pragma once
#include <queue>
#include <thread>
#include "Task.hpp"
#define NUM 6
class ThreadPool
{
private:
ThreadPool(int num = NUM)
: _num(num),
_stop(false)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
}
ThreadPool(const ThreadPool&){
}
public:
bool InitThreadPool()
{
for (int i = 0; i < _num; i++)
{
pthread_t tid;
if (pthread_create(&tid, nullptr, ThreadRoutinue, this) != 0)
{
LOG(FATAL, "create thread pool error");
return false;
}
}
LOG(FATAL, "create thread pool success");
return true;
}
bool IsStop()
{
return _stop;
}
static void* ThreadRoutinue(void* args)
{
ThreadPool *tp = (ThreadPool *)(args);
while (true)
{
Task task;
tp->Lock();
// while 防止误唤醒
while (tp->TaskQueueIsEmpty())
{
tp->ThreadWait(); // 醒来时,一定占有锁
}
tp->UnLock();
tp->PopTask(task);
task.ProcessOn();
}
}
void ThreadWait()
{
pthread_cond_wait(&_cond, &_mutex);
}
void ThreadWakeUp()
{
pthread_cond_signal(&_cond);
}
void Lock()
{
pthread_mutex_lock(&_mutex);
}
void UnLock()
{
pthread_mutex_unlock(&_mutex);
}
void PushTask(const Task& task)
{
Lock();
_task_queue.push(task);
UnLock();
ThreadWakeUp();
}
bool TaskQueueIsEmpty()
{
return _task_queue.size() == 0 ? true : false;
}
void PopTask(Task& task)
{
task = _task_queue.front();
_task_queue.pop();
}
static ThreadPool* GetInstance()
{
static pthread_mutex_t _lock = PTHREAD_MUTEX_INITIALIZER;
if (_instance == nullptr)
{
pthread_mutex_lock(&_lock);
if (_instance == nullptr)
{
_instance = new ThreadPool();
_instance->InitThreadPool();
}
pthread_mutex_unlock(&_lock);
}
return _instance;
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
private:
int _num;
bool _stop;
std::queue<Task> _task_queue;
pthread_mutex_t _mutex;
pthread_cond_t _cond;
static ThreadPool* _instance;
};
ThreadPool* ThreadPool::_instance = nullptr;
Task.hpp
#pragma once
#include "Protocol.hpp"
class Task
{
public:
Task()
{
}
Task(int sock)
: _sock(sock)
{
}
// 处理任务
void ProcessOn()
{
_handler(_sock);
}
private:
int _sock;
CallBack _handler; // 设置回调
};
协议部分
Protocol.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <vector>
#include <sstream>
#include <unordered_map>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <algorithm>
#include <sys/wait.h>
#include "Util.hpp"
#define SEP ": "
#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define SERVER_ERROR 500
#define WROOT "wwwroot"
#define HOME_PAGE "index.html"
#define PAGE_404 "404.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
//#define DEBUG
static std::string CodeDesc(int code)
{
std::string desc;
switch (code)
{
case 200:
desc = "OK";
break;
case 404:
desc = "Not Found";
break;
default:
break;
}
return desc;
}
static std::string SuffixDesc(const std::string suffix)
{
static std::unordered_map<std::string, std::string> suffixdesc =
{
{
".html", "text/html"},
{
".css", "text/css"},
{
".js", "application/json"},
{
".xml", "application/xml"},
{
".jpg", "image/jpeg"}
};
auto it = suffixdesc.find(suffix);
if (it != suffixdesc.end())
{
return it->second;
}
return "text/html";
}
class HttpRequest
{
public:
HttpRequest()
:_content_length(0),
_cgi(false)
{
}
~HttpRequest()
{
}
public:
std::string _request_line;
std::vector<std::string> _request_header;
std::string _blank;
std::string _request_body;
// 解析完毕的结果
std::string _method;
std::string _url;
std::string _path; // 请求目录和参数
std::string _query_string;
std::string _version;
std::unordered_map<std::string, std::string> _header_kv;
int _content_length;
std::string _suffix;
int _size;
// CGI机制
bool _cgi;
};
class HttpResponse
{
public:
HttpResponse()
:_blank(LINE_END),
_status_code(OK),
_fd(-1)
{
}
~HttpResponse()
{
}
public:
std::string _status_line;
std::vector<std::string> _response_header;
std::string _blank;
std::string __response_body;
int _fd;
int _status_code;
};
class EndPoint
{
public:
bool RecvHttpRequestLine()
{
auto &line = _http_request._request_line;
if (Util::ReadLine(_sock, line) > 0)
{
line.resize(line.size() - 1);
LOG(INFO, _http_request._request_line);
}
else
{
_stop = true;
}
return _stop;
}
bool RecvHttpRequestHeader()
{
std::string line;
while (true)
{
line.clear();
if (Util::ReadLine(_sock, line) <= 0)
{
_stop = true;
break;
}
if (line == "\n")
{
_http_request._blank = line;
break;
}
line.resize(line.size() - 1);
_http_request._request_header.push_back(line);
}
return _stop;
}
void ParseHttpRequestLine()
{
auto& line = _http_request._request_line;
std::stringstream s(line);
s >> _http_request._method >> _http_request._url >> _http_request._version;
auto &method = _http_request._method;
std::transform(method.begin(), method.end(), method.begin(), ::toupper);
}
void ParseHttpRequestHander()
{
for (auto& line: _http_request._request_header)
{
std::string key;
std::string value;
if (Util::CutString(line, key, value, SEP))
{
std::cout << key << ":" << value << std::endl;
_http_request._header_kv.insert({
key, value});
}
}
}
bool IsNeedRecvHttpRequestBody()
{
if (_http_request._method == "POST")
{
auto it = _http_request._header_kv.find("Content-Length");
if (it != _http_request._header_kv.end())
{
_http_request._content_length = atoi(it->second.c_str());
return true;
}
}
return false;
}
bool RecvHttpRequestBody()
{
if (IsNeedRecvHttpRequestBody())
{
int len = _http_request._content_length;
auto &body = _http_request._request_body;
char ch = 0;
while (len)
{
ssize_t s = recv(_sock, &ch, 1, 0);
if (s > 0)
{
body.push_back(ch);
len--;
}
else
{
_stop = true;
break;
}
}
}
return _stop;
}
public:
EndPoint(int sock)
:_sock(sock),
_stop(false)
{
}
void RecvHttpRequest()
{
if (!RecvHttpRequestLine() && !RecvHttpRequestHeader())
{
ParseHttpRequestLine();
ParseHttpRequestHander();
RecvHttpRequestBody();
}
}
// cgi处理
int ProcessCgi()
{
int code = OK;
auto &bin = _http_request._path;
auto &method = _http_request._method;
auto &query_string = _http_request._query_string; // GET
auto &body_text = _http_request._request_body; // POST
auto &response_body = _http_response.__response_body;
int content_length = _http_request._content_length;
// 环境变量,用来GET传
std::string query_string_env;
std::string method_env;
std::string content_length_env;
// 管道通信
int input[2];
int output[2];
if (pipe(input) < 0)
{
LOG(ERROR, "pipe input error");
code = SERVER_ERROR;
return code;
}
if (pipe(output) < 0)
{
LOG(ERROR, "pipe input error");
code = SERVER_ERROR;
return code;
}
// 父进程关闭input写端,只读。关output读端,只写
// 新线程,从头到尾只有一个进程,不能本进程替换, 创建新进程
pid_t pid = fork();
if (pid == 0)
{
// exec
close(input[0]);
close(output[1]);
// 通过方法判断从哪取参数
method_env = "METHOD=";
method_env += method;
putenv(((char*)method_env.c_str()));
if (method == "GET")
{
query_string_env += "QUERY_STRING=";
query_string_env += query_string;
// 注册环境变量
putenv(((char*)query_string_env.c_str()));
}
else if (method == "POST")
{
content_length_env = "CONTENT_LENGTH=";
content_length_env += std::to_string(content_length);
putenv((char*)(content_length_env.c_str()));
}
else
{
// do nothing
}
std::cout << "替换的程序为:" << bin << std::endl;
dup2(input[1], 1);
dup2(output[0], 0);
execl(bin.c_str(), bin.c_str(), nullptr);
exit(1);
}
else if (pid > 0)
{
// parent
close(input[1]);
close(output[0]);
if (method == "POST")
{
const char *start = body_text.c_str();
int total = 0;
int size = 0;
// 防止一次没写完
while ((total < content_length) && (size = write(output[1], start + total, body_text.size() - total)) > 0)
{
total += size;
}
}
// 读取cgi返回的数据
char ch = 0;
while (read(input[0], &ch, 1) > 0)
{
response_body.push_back(ch);
}
// 判断子进程退出状态
int status = 0;
pid_t ret = waitpid(pid, &status, 0);
if (ret == pid)
{
if (WIFEXITED(status))
{
if (WEXITSTATUS(status) == 0)
{
code = OK;
}
else
{
code = BAD_REQUEST;
}
}
else
{
code = SERVER_ERROR;
}
}
// 使用完后关闭描述符
close(input[0]);
close(output[1]);
}
else
{
LOG(ERROR, "fork error");
return 404;
}
return code;
}
int ProcessNoCgi()
{
_http_response._fd = open(_http_request._path.c_str(), O_RDONLY);
if (_http_response._fd >= 0)
{
return OK;
}
return NOT_FOUND;
}
void BuildOkResponse()
{
std::string line = "Content-Type: ";
line += SuffixDesc(_http_request._suffix);
line += LINE_END;
_http_response._response_header.push_back(line);
line += "Content-Length: ";
if (_http_request._cgi)
{
line += std::to_string(_http_response.__response_body.size());
}
else
{
// get
line += std::to_string(_http_request._size);
}
line += LINE_END;
_http_response._response_header.push_back(line);
}
void HandlerError(std::string page)
{
_http_request._cgi = false;
// 给用户返回对应错误页面
_http_response._fd = open(page.c_str(), O_RDONLY);
if (_http_response._fd > 0)
{
struct stat st;
stat(page.c_str(), &st);
_http_request._size = st.st_size;
std::string line = "Content-Type: text/html"; // 这里明确了是返回网页
line += LINE_END;
_http_response._response_header.push_back(line);
line = "Content-Length: ";
line += std::to_string(st.st_size);
line += LINE_END;
_http_response._response_header.push_back(line);
}
}
void BuildHttpResponseHelper()
{
auto &code = _http_response._status_code;
auto &status_line = _http_response._status_line;
// 构建状态行
status_line = HTTP_VERSION;
status_line += " ";
status_line += std::to_string(code);
status_line += " ";
status_line += CodeDesc(code);
status_line += LINE_END;
// 构建响应正文,可能包含报头
std::string path = WROOT;
path += "/";
switch (code)
{
case OK:
BuildOkResponse();
break;
case NOT_FOUND:
path += PAGE_404;
HandlerError(path);
break;
// case 500:
// HandlerError(PAGE_500);
// break;
default:
path += PAGE_404;
HandlerError(path);
break;
}
}
void BuildHttpResponse()
{
struct stat st;
int found = 0;
std::string path;
auto &code = _http_response._status_code;
std::cout << "方法是: " << _http_request._method << std::endl;
if (_http_request._method != "GET" && _http_request._method != "POST")
{
// 非法请求
LOG(WARNING, "method not is right");
code = BAD_REQUEST;
goto END;
}
if (_http_request._method == "GET")
{
ssize_t pos = _http_request._url.find("?");
if (pos != std::string::npos)
{
Util::CutString(_http_request._url, _http_request._path, _http_request._query_string, "?");
_http_request._cgi = true;
}
else
{
_http_request._path = _http_request._url;
}
}
else if (_http_request._method == "POST")
{
_http_request._cgi = true;
_http_request._path = _http_request._url;
}
else
{
// do nothing
}
// std::cout << _http_request._path << " " << _http_request._query_string << std::endl;
path = _http_request._path;
_http_request._path = WROOT;
_http_request._path += path;
// 访问的是根目录, 换成主页
if (_http_request._path[_http_request._path.size() - 1] == '/')
{
_http_request._path += HOME_PAGE;
}
if (stat(_http_request._path.c_str(), &st) == 0)
{
// 访问的是目录
if (S_ISDIR(st.st_mode))
{
// 不允许访问目录,请求目录,设置为这个目录的主页
_http_request._path += "/";
_http_request._path += HOME_PAGE;
// 上面修改了路径,重新获取
stat(_http_request._path.c_str(), &st);
}
// 访问可执行程序
if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
{
std::cout << "访问程序:" << _http_request._path << std::endl;
// 特殊处理
_http_request._cgi = true;
}
_http_request._size = st.st_size;
}
else
{
// 资源不存在
LOG(WARNING, _http_request._path + "NOT FOUND");
code = NOT_FOUND;
goto END;
}
// std::cout << "path:" << _http_request._path << std::endl;
// suffix
found = _http_request._path.rfind(".");
if (found == std::string::npos)
{
_http_request._suffix = ".html";
}
else
{
_http_request._suffix = _http_request._path.substr(found);
}
// 是否cgi处理
if (_http_request._cgi == true)
{
code = ProcessCgi(); // 执行目标程序,拿到结果:正文
}
else
{
// 目标网页一定存在
// 返回要构建http响应
code = ProcessNoCgi(); // 简单的网页处理,返回静态网页,只需要打开即可
}
END:
// 构建错误响应
BuildHttpResponseHelper(); // 状态行填充了,报头有了,空行有了,正文有了
}
void SendHttpResponse()
{
//std::cout << "status_line" << _http_response._status_line << std::endl;
// for (auto it : _http_response._response_header)
// {
// //std::cout << "status_head" << _http_response._status_line << std::endl;
// }
send(_sock, _http_response._status_line.c_str(), _http_response._status_line.size(), 0);
for (auto it : _http_response._response_header)
{
send(_sock, it.c_str(), it.size(), 0);
}
send(_sock, _http_response._blank.c_str(), _http_response._blank.size(), 0);
//std::string con = "hello world";
if (_http_request._cgi)
{
// 内容在response里
auto &response_body = _http_response.__response_body;
size_t size = 0;
size_t total = 0;
const char *start = response_body.c_str();
while ((total < response_body.size()) && (size = send(_sock, start + total, response_body.size() - total, 0)) > 0)
{
total += size;
}
}
else
{
// 发送打开的文件
sendfile(_sock, _http_response._fd, nullptr, _http_request._size);
close(_http_response._fd);
}
}
bool IsStop()
{
return _stop;
}
~EndPoint()
{
}
private:
int _sock;
HttpRequest _http_request;
HttpResponse _http_response;
bool _stop;
};
class CallBack
{
public:
CallBack()
{
}
void operator()(int sock)
{
HandlerRequest(sock);
}
void HandlerRequest(int sock)
{
LOG(INFO, "handler request begin...");
//int sock = *(int*)tmp_sock;
//delete tmp_sock;
#ifdef DEBUG
std::cout << "begin---------------------------" << sock << std::endl;
char buff[4096];
recv(sock, buff, sizeof(buff), 0);
std::cout << buff;
std::cout << "end-----------------------------" << sock << std::endl;
#else
EndPoint *ep = new EndPoint(sock);
ep->RecvHttpRequest();
if (!ep->IsStop())
{
LOG(INFO, "recv success, begin build and send");
ep->BuildHttpResponse();
ep->SendHttpResponse();
}
else
{
LOG(INFO, "recv error, stop");
}
delete ep;
#endif
LOG(INFO, "handle request end...");
//return nullptr;
}
~CallBack()
{
}
};
日志打印
Log.hpp
#pragma