#include "http_conn.h"
http_conn::http_conn(){}
http_conn::~http_conn(){}
int http_conn::m_epoll_fd = -1; // 类中静态成员需要外部定义
int http_conn::m_user_cnt = 0;
int http_conn::m_request_cnt = 0;
sort_timer_lst http_conn::m_timer_lst;
// locker http_conn::m_timer_lst_locker;
// 网站的根目录
const char* doc_root = "/home/cyf/Linux/webserver/resources";
// 定义HTTP响应的一些状态信息
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";
// 设置文件描述符为非阻塞
void set_nonblocking(int fd){
int flag = fcntl(fd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(fd, F_SETFL, flag);
}
// 添加需要监听的文件描述符到epoll中
void addfd(int epoll_fd, int fd, bool one_shot, bool et){
epoll_event event;
event.data.fd = fd;
if(et){
event.events = EPOLLIN | EPOLLRDHUP | EPOLLET; // 对所有fd设置边沿触发,但是listen_fd不需要,可以另行判断处理
}else{
event.events = EPOLLIN | EPOLLRDHUP; // 默认水平触发 对端连接断开触发的epoll 事件包含 EPOLLIN | EPOLLRDHUP挂起,不用根据返回值判断,直接通过事件判断异常断开
}
if(one_shot){
event.events |= EPOLLONESHOT; // 注册为 EPOLLONESHOT事件,防止同一个通信被不同的线程处理
}
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event);
// 设置文件描述符为非阻塞(epoll ET模式)
set_nonblocking(fd);
}
// 从epoll中删除文件描述符
void rmfd(int epoll_fd, int fd){
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, 0);
close(fd);
}
// 在epoll中修改文件描述符,重置socket上EPOLLONESHOT事件,确保下次可读时,EPOLLIN 事件被触发
void modfd(int epoll_fd, int fd, int ev){
epoll_event event;
event.data.fd = fd;
event.events = ev | EPOLLONESHOT | EPOLLRDHUP;
epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event);
}
// 初始化新的连接
void http_conn::init(int sock_fd, const sockaddr_in& addr){
m_sock_fd = sock_fd; // 套接字
m_addr = addr; // 客户端地址
// 设置端口复用
int reuse = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse));
// 添加sock_fd到epoll对象中
addfd(m_epoll_fd, sock_fd, true, ET);
++m_user_cnt;
char ip[16] = "";
const char* str = inet_ntop(AF_INET, &addr.sin_addr.s_addr, ip, sizeof(ip));
EMlog(LOGLEVEL_INFO, "The No.%d user. sock_fd = %d, ip = %s.\n", m_user_cnt, sock_fd, str);
init(); // 初始化其他信息,私有
// 创建定时器,设置其回调函数与超时时间,然后绑定定时器与用户数据,最后将定时器添加到链表timer_lst中
util_timer* new_timer = new util_timer;
new_timer->user_data = this;
time_t curr_time = time(NULL);
new_timer->expire = curr_time + 3 * TIMESLOT;
this->timer = new_timer;
m_timer_lst.add_timer(new_timer);
}
// 初始化连接之外的其他信息
void http_conn::init(){
m_method = GET;
m_url = 0;
m_version = 0;
m_linger = false; // 默认不保持连接
m_content_len = 0;
m_host = 0;
m_check_stat = CHECK_STATE_REQUESTLINE; // 初始化状态为正在解析请求首行
m_checked_idx = 0; // 初始化解析字符索引
m_line_start = 0; // 行的起始位置
m_rd_idx = 0; // 读取字符的位置
m_write_idx = 0;
bytes_have_send = 0;
bytes_to_send = 0;
bzero(m_rd_buf, RD_BUF_SIZE); // 清空读缓存
bzero(m_write_buf, WD_BUF_SIZE); // 清空写缓存
bzero(m_real_file, FILENAME_LEN); // 清空文件路径
}
// 关闭连接
void http_conn::conn_close(){
if(m_sock_fd != -1){
--m_user_cnt; // 客户端数量减一
EMlog(LOGLEVEL_INFO, "closing fd: %d, rest user num :%d\n", m_sock_fd, m_user_cnt);
rmfd(m_epoll_fd, m_sock_fd); // 移除epoll检测,关闭套接字
m_sock_fd = -1;
}
}
// 循环读取客户数据,直到无数据可读 或 关闭连接
bool http_conn::read(){
if(timer) { // 更新超时时间
time_t curr_time = time( NULL );
timer->expire = curr_time + 3 * TIMESLOT;
m_timer_lst.adjust_timer( timer );
}
if(m_rd_idx >= RD_BUF_SIZE) return false; // 超过缓冲区大小
int bytes_rd = 0;
while(true){ // m_sock_fd已设置非阻塞
bytes_rd = recv(m_sock_fd, m_rd_buf + m_rd_idx, RD_BUF_SIZE - m_rd_idx, 0); // 第二个参数传递的是缓冲区中开始读入的地址偏移
if(bytes_rd == -1){
if(errno == EAGAIN || errno == EWOULDBLOCK){
break; // 非阻塞读取,没有数据了
}
return false; // 读取错误,调用conn_close()
}else if(bytes_rd == 0){
return false; // 对方关闭连接,调用conn_close()
}
m_rd_idx += bytes_rd; // 更新下一次读取位置
}
++m_request_cnt;
EMlog(LOGLEVEL_INFO, "sock_fd = %d read done. request cnt = %d\n", m_sock_fd, m_request_cnt); // 全部读取完毕
return true;
}
// 主状态机 解析HTTP请求
http_conn::HTTP_CODE http_conn::process_read(){
LINE_STATUS line_stat = LINE_OK;
HTTP_CODE ret = NO_REQUEST;
char* text = 0;
while((m_check_stat == CHECK_STATE_CONTENT && line_stat == LINE_OK) // 主状态机正在解析请求体,且从状态机OK,不需要一行一行解析
|| (line_stat = parse_one_line()) == LINE_OK){ // 从状态机解析到一行数据
// 获取一行数据
text = get_line();
m_line_start = m_checked_idx; // 更新下一行的起始位置
EMlog(LOGLEVEL_DEBUG, ">>>>>> %s\n", text);
switch(m_check_stat){
case CHECK_STATE_REQUESTLINE:
{
ret = parse_request_line(text);
if(ret == BAD_REQUEST){
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:
{
ret = parse_request_headers(text);
if(ret == BAD_REQUEST){
return BAD_REQUEST;
}else if(ret == GET_REQUEST){
return do_request(); // 解析具体的请求信息
}
break;
}
case CHECK_STATE_CONTENT:
{
ret = parse_request_content(text);
if(ret == GET_REQUEST){
return do_request(); // 解析具体的请求信息
}
line_stat = LINE_OPEN; // != GET_REQUEST
break;
}
default:
{
return INTERNAL_ERROR; // 内部错误
}
}
}
return NO_REQUEST; // 数据不完整
}
// 解析请求首行,获得请求方法,目标URL,HTTP版本
http_conn::HTTP_CODE http_conn::parse_request_line(char* text){
// GET /index.html HTTP/1.1
m_url = strpbrk(text, " \t"); // 找到第一次出现空格或者\t的下标
if(!m_url) return BAD_REQUEST;
*m_url = '\0'; // GET\0/index.html HTTP/1.1,此时text到\0结束,表示 GET\0
m_url++; // /index.h