自己动手写http服务器——处理http连接(二)
关于http报文格式请看这篇文章
//http_conn.h
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include "locker.h"
class http_conn
{
public:
static const int FILENAME_LEN = 200;
static const int READ_BUFFER_SIZE = 2048;
static const int WRITE_BUFFER_SIZE = 1024;
enum METHOD { GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH }; //http请求的格式,只支持get
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };//主状态机的状态,检查请求行,检查请求头部,检查内容实体
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION }; //http响应报文的状态码
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN }; //请求行的状态
public:
http_conn(){}
~http_conn(){}
public:
void init( int sockfd, const sockaddr_in& addr );
void close_conn( bool real_close = true );
void process();
bool read();
bool write();
private:
void init();
HTTP_CODE process_read();
bool process_write( HTTP_CODE ret );
HTTP_CODE parse_request_line( char* text );
HTTP_CODE parse_headers( char* text );
HTTP_CODE parse_content( char* text );
HTTP_CODE do_request();
char* get_line() { return m_read_buf + m_start_line; }
LINE_STATUS parse_line();
void unmap();
bool add_response( const char* format, ... ); //添加响应
bool add_content( const char* content ); //添加响应内容
bool add_status_line( int status, const char* title ); //添加响应状态行
bool add_headers( int content_length ); //添加http响应报文的头部字段
bool add_content_length( int content_length ); //添加内容实体的长度
bool add_linger();//添加响应报文的Connection字段,指定是否保持连接
bool add_blank_line(); //添加\r\n
public:
static int m_epollfd; //epoll文件句柄
static int m_user_count;
private:
int m_sockfd; //当前连接的client sockdfd
sockaddr_in m_address;
char m_read_buf[ READ_BUFFER_SIZE ]; //读缓冲区
int m_read_idx; //标示读缓冲区已经读入的客户数据的最后一个字节的下一个位置
int m_checked_idx; //当前正在分析的字符在读缓冲区的位置
int m_start_line;//当前正在解析的行的起始位置
char m_write_buf[ WRITE_BUFFER_SIZE ];
int m_write_idx; //写缓冲区中待发送的字节数。
CHECK_STATE m_check_state; //主状态机的状态
METHOD m_method;
char m_real_file[ FILENAME_LEN ];//客户请求文件的完整路径。
char* m_url;
char* m_version; //http版本信息,我们只支持1.1
char* m_host;
int m_content_length;//http请求的消息体的长度
bool m_linger; //http请求是否要保持链接
char* m_file_address; //客户请求的目标文件被映射到内存中位置
struct stat m_file_stat; //目标文件的状态
struct iovec m_iv[2];
int m_iv_count;
};
#endif
//http_conn.cpp
#include "http_conn.h"
//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";
const char* doc_root = "/var/www/html";
//将文件IO操作设置为非阻塞
int setnonblocking( int fd )
{
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
//向epoll中加入要监听的文件描述符
void addfd( int epollfd, int fd, bool one_shot )
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
if( one_shot )
{
event.events |= EPOLLONESHOT;
}
epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
setnonblocking( fd );
}
//在epoll删除文件描述符
void removefd( int epollfd, int fd )
{
epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 );
close( fd );
}
//修该文件描述符的监听事件
void modfd( int epollfd, int fd, int ev )
{
epoll_event event;
event.data.fd = fd;
event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );
}
int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;
void http_conn::close_conn( bool real_close )
{
if( real_close && ( m_sockfd != -1 ) )
{
//modfd( m_epollfd, m_sockfd, EPOLLIN );
removefd( m_epollfd, m_sockfd );
m_sockfd = -1;
m_user_count--;
}
}
void http_conn::init( int sockfd, const sockaddr_in& addr )
{
m_sockfd = sockfd;
m_address = addr;
int error = 0;
socklen_t len = sizeof( error );
getsockopt( m_sockfd, SOL_SOCKET, SO_ERROR, &error, &len );
int reuse = 1;
setsockopt( m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) );
addfd( m_epollfd, sockfd, true );
m_user_count++;
init();
}
void http_conn::init()
{
m_check_state = CHECK_STATE_REQUESTLINE;
m_linger = false;
m_method = GET;
m_url = 0;
m_version = 0;
m_content_length = 0;
m_host = 0;
m_start_line = 0;
m_checked_idx = 0;
m_read_idx = 0;
m_write_idx = 0;
memset( m_read_buf, '\0', READ_BUFFER_SIZE );
memset( m_write_buf, '\0', WRITE_BUFFER_SIZE );
memset( m_real_file, '\0', FILENAME_LEN );
}
//截取http请求行
http_conn::LINE_STATUS http_conn::parse_line()
{
char temp;
for ( ; m_checked_idx < m_read_idx; ++m_checked_idx )
{
temp = m_read_buf[ m_checked_idx ];
if ( temp == '\r' )
{
if ( ( m_checked_idx + 1 ) == m_read_idx )
{
return LINE_OPEN;
}
else if ( m_read_buf[ m_checked_idx + 1 ] == '\n' )
{
m_read_buf[ m_checked_idx++ ] = '\0';
m_read_buf[ m_checked_idx++ ] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
else if( temp == '\n' )
{
if( ( m_checked_idx > 1 ) && ( m_read_buf[ m_checked_idx - 1 ] == '\r' ) )
{
m_read_buf[ m_checked_idx-1 ] = '\0';
m_read_buf[ m_checked_idx++ ] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}
return LINE_OPEN;
}
//将请求报文读到读缓冲区中
bool http_conn::read()
{
if( m_read_idx >= READ_BUFFER_SIZE )
{
return false;
}
int bytes_read = 0;
while( true )
{
bytes_read = recv( m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0 );
if ( bytes_read == -1 )
{
if( errno == EAGAIN || errno == EWOULDBLOCK )
{
break;
}
return false;
}
else if ( bytes_read == 0 )
{
return false;
}
m_read_idx += bytes_read;
}
return true;
}
//获取http请求行中的请求方式,URL,version
http_conn::HTTP_CODE http_conn::parse_request_line( char* text )
{
m_url = strpbrk( text, " \t" );
if ( ! m_url )
{
return BAD_REQUEST;
}
*m_url++ = '\0';
char* method = text;
if ( strcasecmp( method, "GET" ) == 0 )
{
m_method = GET;
}
else
{
return BAD_REQUEST;
}
m_url += strspn( m_url, " \t" );
m_version = strpbrk( m_url, " \t" );
if ( ! m_version )
{
return BAD_REQUEST;
}
*m_version++ = '\0';
m_version += strspn( m_version, " \t" );
if ( strcasecmp( m_version, "HTTP/1.1" ) != 0 )
{
return BAD_REQUEST;
}
if ( strncasecmp( m_url, "http://", 7 ) == 0 )
{
m_url += 7;
m_url = strchr( m_url, '/' );
}
if ( ! m_url || m_url[ 0 ] != '/' )
{
return BAD_REQUEST;
}
m_check_state = CHECK_STATE_HEADER;
return NO_REQUEST;
}
//分析请求报文的首部字段
http_conn::HTTP_CODE http_conn::parse_headers( char* text )
{
if( text[ 0 ] == '\0' )
{
if ( m_method == HEAD )
{
return GET_REQUEST;
}
if ( m_content_length != 0 )
{
m_check_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}
return GET_REQUEST;
}
else if ( strncasecmp( text, "Connection:", 11 ) == 0 )
{
text += 11;
text += strspn( text, " \t" );
if ( strcasecmp( text, "keep-alive" ) == 0 )
{
m_linger = true;
}
}
else if ( strncasecmp( text, "Content-Length:", 15 ) == 0 )
{
text += 15;
text += strspn( text, " \t" );
m_content_length = atol( text );
}
else if ( strncasecmp( text, "Host:", 5 ) == 0 )
{
text += 5;
text += strspn( text, " \t" );
m_host = text;
}
else
{
printf( "oop! unknow header %s\n", text );
}
return NO_REQUEST;
}
//分析请求保卫的内容实体
http_conn::HTTP_CODE http_conn::parse_content( char* text )
{
if ( m_read_idx >= ( m_content_length + m_checked_idx ) )
{
text[ m_content_length ] = '\0';
return GET_REQUEST;
}
return NO_REQUEST;
}
//处理读请求
http_conn::HTTP_CODE http_conn::process_read()
{
LINE_STATUS line_status = LINE_OK;
HTTP_CODE ret = NO_REQUEST;
char* text = 0;
while ( ( ( m_check_state == CHECK_STATE_CONTENT ) && ( line_status == LINE_OK ) )
|| ( ( line_status = parse_line() ) == LINE_OK ) )
{
text = get_line();
m_start_line = m_checked_idx;
printf( "got 1 http line: %s\n", text );
switch ( m_check_state )
{
case CHECK_STATE_REQUESTLINE:
{
ret = parse_request_line( text );
if ( ret == BAD_REQUEST )
{
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:
{
ret = parse_headers( text );
if ( ret == BAD_REQUEST )
{
return BAD_REQUEST;
}
else if ( ret == GET_REQUEST )
{
return do_request();
}
break;
}
case CHECK_STATE_CONTENT:
{
ret = parse_content( text );
if ( ret == GET_REQUEST )
{
return do_request();
}
line_status = LINE_OPEN;
break;
}
default:
{
return INTERNAL_ERROR;
}
}
}
return NO_REQUEST;
}
//对http请求发出响应
http_conn::HTTP_CODE http_conn::do_request()
{
strcpy( m_real_file, doc_root );
int len = strlen( doc_root );
strncpy( m_real_file + len, m_url, FILENAME_LEN - len - 1 );
if ( stat( m_real_file, &m_file_stat ) < 0 )
{
return NO_RESOURCE;
}
if ( ! ( m_file_stat.st_mode & S_IROTH ) )
{
return FORBIDDEN_REQUEST;
}
if ( S_ISDIR( m_file_stat.st_mode ) )
{
return BAD_REQUEST;
}
int fd = open( m_real_file, O_RDONLY );
m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
close( fd );
return FILE_REQUEST;
}
//解除文件内存映射
void http_conn::unmap()
{
if( m_file_address )
{
munmap( m_file_address, m_file_stat.st_size );
m_file_address = 0;
}
}
//发送数据
bool http_conn::write()
{
int temp = 0;
int bytes_have_send = 0;
int bytes_to_send = m_write_idx;
if ( bytes_to_send == 0 )
{
modfd( m_epollfd, m_sockfd, EPOLLIN );
init();
return true;
}
while( 1 )
{
temp = writev( m_sockfd, m_iv, m_iv_count );
if ( temp <= -1 )
{
if( errno == EAGAIN )
{
modfd( m_epollfd, m_sockfd, EPOLLOUT );
return true;
}
unmap();
return false;
}
bytes_to_send -= temp;
bytes_have_send += temp;
if ( bytes_to_send <= bytes_have_send )
{
unmap();
if( m_linger )
{
init();
modfd( m_epollfd, m_sockfd, EPOLLIN );
return true;
}
else
{
modfd( m_epollfd, m_sockfd, EPOLLIN );
return false;
}
}
}
}
bool http_conn::add_response( const char* format, ... )
{
if( m_write_idx >= WRITE_BUFFER_SIZE )
{
return false;
}
va_list arg_list;
va_start( arg_list, format );
int len = vsnprintf( m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list );
if( len >= ( WRITE_BUFFER_SIZE - 1 - m_write_idx ) )
{
return false;
}
m_write_idx += len;
va_end( arg_list );
return true;
}
bool http_conn::add_status_line( int status, const char* title )
{
return add_response( "%s %d %s\r\n", "HTTP/1.1", status, title );
}
bool http_conn::add_headers( int content_len )
{
add_content_length( content_len );
add_linger();
add_blank_line();
}
bool http_conn::add_content_length( int content_len )
{
return add_response( "Content-Length: %d\r\n", content_len );
}
bool http_conn::add_linger()
{
return add_response( "Connection: %s\r\n", ( m_linger == true ) ? "keep-alive" : "close" );
}
bool http_conn::add_blank_line()
{
return add_response( "%s", "\r\n" );
}
bool http_conn::add_content( const char* content )
{
return add_response( "%s", content );
}
//处理写请求
bool http_conn::process_write( HTTP_CODE ret )
{
switch ( ret )
{
case INTERNAL_ERROR:
{
add_status_line( 500, error_500_title );
add_headers( strlen( error_500_form ) );
if ( ! add_content( error_500_form ) )
{
return false;
}
break;
}
case BAD_REQUEST:
{
add_status_line( 400, error_400_title );
add_headers( strlen( error_400_form ) );
if ( ! add_content( error_400_form ) )
{
return false;
}
break;
}
case NO_RESOURCE:
{
add_status_line( 404, error_404_title );
add_headers( strlen( error_404_form ) );
if ( ! add_content( error_404_form ) )
{
return false;
}
break;
}
case FORBIDDEN_REQUEST:
{
add_status_line( 403, error_403_title );
add_headers( strlen( error_403_form ) );
if ( ! add_content( error_403_form ) )
{
return false;
}
break;
}
case FILE_REQUEST:
{
add_status_line( 200, ok_200_title );
if ( m_file_stat.st_size != 0 )
{
add_headers( m_file_stat.st_size );
m_iv[ 0 ].iov_base = m_write_buf;
m_iv[ 0 ].iov_len = m_write_idx;
m_iv[ 1 ].iov_base = m_file_address;
m_iv[ 1 ].iov_len = m_file_stat.st_size;
m_iv_count = 2;
return true;
}
else
{
const char* ok_string = "<html><body></body></html>";
add_headers( strlen( ok_string ) );
if ( ! add_content( ok_string ) )
{
return false;
}
}
}
default:
{
return false;
}
}
m_iv[ 0 ].iov_base = m_write_buf;
m_iv[ 0 ].iov_len = m_write_idx;
m_iv_count = 1;
return true;
}
//处理http请求的线程调用的入口函数
void http_conn::process()
{
HTTP_CODE read_ret = process_read();
if ( read_ret == NO_REQUEST )
{
modfd( m_epollfd, m_sockfd, EPOLLIN );
return;
}
bool write_ret = process_write( read_ret );
if ( ! write_ret )
{
close_conn();
}
modfd( m_epollfd, m_sockfd, EPOLLOUT );
}
自己动手写http服务器——处理http连接(二)的更多相关文章
- 自己动手写RTP服务器——关于RTP协议
转自:http://blog.csdn.net/baby313/article/details/7353605 本文会带领着你一步步动手实现一个简单的RTP传输服务器,旨在了解RTP流媒体传输协议以及 ...
- 自己动手写RTP服务器——传输所有格式的视频
上一篇文章我们介绍了如何用一个简单的UDP socket搭建一个RTP服务器.我把这份80行的代码呈现到客户面前的时候,就有人不满意了. 还有人在参考的时候会问:“楼主你的TS格式的文件是哪里来的?应 ...
- 自己动手写RTP服务器——用RTP协议传输TS流
上一篇文章我们介绍了关于RTP协议的知识,那么我们现在就自己写一个简单的传输TS流媒体的RTP服务器吧. 预备知识 关于TS流的格式:TS流封装的具体格式请参考文档ISO/IEC 13818-1.这里 ...
- 自己动手写http服务器——主程序(三)
功能:目前只支持对资源的访问. 使用的模型:多线程加epoll,与传统的一个连接请求一个线程处理不同的是,这个模型只为那些需要服务的连接请求调用线程进行处理, 整个模型的大致流程 创建一个线程持对象, ...
- 自己动手写http服务器——线程池(一)
创建一个线程池,每有一个连接对象就将它添加到工作队列中,线程池中的线程通过竞争来取得任务并执行它(它是通过信号量实现的). //filename threadpool.h #ifndef THREAD ...
- Java自己动手写连接池四
Java自己动手写连接池四 测试: package com.kama.cn; import java.sql.Connection; public class Test { public static ...
- Java自己动手写连接池三
Java自己动手写连接池三,核心代码; package com.kama.cn; import java.sql.Connection;import java.util.ArrayList;impor ...
- Java自己动手写连接池一
自己动手写连接池,废话不多说,直接上代码,读取配置文件 package com.kama.cn; import java.io.IOException;import java.io.InputStre ...
- 利用html 5 websocket做个山寨版web聊天室(手写C#服务器)
在之前的博客中提到过看到html5 的websocket后很感兴趣,终于可以摆脱长轮询(websocket之前的实现方式可以看看Developer Works上的一篇文章,有简单提到,同时也说了web ...
随机推荐
- Problem X
Problem Description Recently, iSea went to an ancient country. For such a long time, it was the most ...
- CentOs7相对于CentOs6的常用命令变化
比如说防火墙在CentOS6中为 iptables,在CentOS7中变为 firewalld. service iptables stop/start/restart systemctl stop/ ...
- 关于easyui的datagrid属性出现乱码问题
今天遇到这个问题也是纠结了好久,经过在网上各种查询总结,得出以下经验: 1:网页字符集设置为UTF-8: <meta content="charset=UTF-8 " /&g ...
- arguments对象
改变函数arguments也会改变对应的参数,arguments会自动映射到对应的参数上 但是改变参数并不会同步改变arguments 它们并不公用内存 在strict模式,不能对arguments付 ...
- font-size:100% 原因
The browser default which is something like 16pt for Firefox, You can check by going into Firefox op ...
- liunx 系统调用 getopt() 函数
命令行参数解析函数 -- getopt() getopt()函数声明如下: #include <unistd.h>int getopt(int argc, char * const arg ...
- 如何实现websocket服务器-理论篇
WebSocket 服务器简单来说就是一个遵循特殊协议监听服务器任意端口的tcp应用.搭建一个定制服务器的任务通常会让让人们感到害怕.然而基于实现一个简单的Websocket服务器没有那么麻烦. 一个 ...
- 使用XML设计某大学主页站点地图--ASP.NET
一.使用XML设计某大学主页站点地图步骤如下 1.创建一个空网站,在项目文件上右击,然后[添加新项],选择[站点地图],新建一个可默认为Web.sitemap的文件. 2.在Web.sitemap里修 ...
- 是用Epplus生成Excel 图表
1. 前言 这是我最近项目刚要的需求,然后在网上找了半天的教材 但是很不幸,有关于Epplus的介绍真的太少了,然后经过了我的不断研究然后不断的采坑,知道现在看到Excel都想吐的时候,终于成功的 ...
- 通过hadoop + hive搭建离线式的分析系统之快速搭建一览
最近有个需求,需要整合所有店铺的数据做一个离线式分析系统,曾经都是按照店铺分库分表来给各自商家通过highchart多维度展示自家的店铺经营 数据,我们知道这是一个以店铺为维度的切分数据,非常适合目前 ...