一个简单http_server的实现(3)

http请求

在可以进行网络通信之后,我们需要知道浏览器和我的服务器之间通信的规则
首先浏览器在和我的服务器建立通信之后会发出一个http请求到我的服务器
一个http请求的组成为:一个请求行,后面跟随零个或多个请求报头,然后是一个空的文本行来结束报头
后面接着是请求体
然后每一行的结尾都是\r\n,空行就是只有\r\n

请求行

请求行的格式为<method> <url> <version>method就是请求的方法包括GET,POST,OPTIONS,HEAD,PUT,
DELETE,TRACE等,这次就先实现最基本的GET方法,报头就先忽略
url就是资源定位符
version就是http版本

http响应

在服务器接受请求并进行一系列处理之后会构造一个http响应给浏览器,一个http响应的组成和http请求很像,一个响应行,后面接若干行响应报头,然后是一个终止报头的空行,再跟随一个响应体

响应行

响应行的格式为<version> <status code> <status message>
version是http版本
status code是状态码,参考
status message是状态的英文描述

响应报头

最重要的两个报头就是Content-Type它告诉客户端响应主体中的内容的类型,以及Content-Length报头用来告诉客户端响应主体的字节大小

响应体

响应体就是被请求的内容

代码实现

读取一行

首先我们需要分析请求,因为http请求的每一行都是\r\n结尾的,我们可以写一个函数来一行一行的读入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
http_conn::READ_LINE_STATUS http_conn::HttpConn::readLine()//读取一行请求
{
text.clear();
char c;
http_conn::READ_LINE_STATUS statu=LINE_OPEN;
while(statu==LINE_OPEN)
{
int isread=rio.rio_readnb(&riobuffer,&c,1);
switch (isread)
{
case -1:
statu=LINE_BAD;
break;
case 1:
statu=LINE_OPEN;
if (c=='\n')
{
if (*text.rbegin()=='\r')
statu=LINE_OK;
else
statu=LINE_BAD;//出现单独的'\n'
}else
{
if(*text.rend()=='\r')
statu=LINE_BAD;//出现单独的'\r'
}
text.append(1,c);
break;
}
}
return statu;
}

READ_LINE_STATUS是读取一行的状态,分别有三个为LINE_OK代表成功读取一行,LINE_OPEN代表正在读取一行,LINE_BAD代表读取错误即不是以\r\n结尾
在成功读取一行之后我们要对它进行处理

处理请求

处理请求也有三种状态,处理请求行,处理请求头,处理请求体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
http_conn::HTTP_CODE http_conn::HttpConn::parse_request()//处理http请求
{
while((readlinestatus=readLine())==LINE_OK)//成功读取到一行
{
switch(requestreadstatus)
{
case REQUEST_LINE:
{
HTTP_CODE ret=parse_requestline();
if(ret!=SUCCESS)
{
return ret;
}
break;
}
case REQUEST_HEADER:
{
HTTP_CODE ret=parse_requesthead();
if (ret==BAD_REQUEST)
return BAD_REQUEST;
else if (ret==GET_REQUEST)
{
if(is_static)
return parse_static_request();
else
return parse_dynamic_request();
}
break;
}
case REQUEST_BODY:
HTTP_CODE ret=parse_requestbody();
if (ret==BAD_REQUEST)
return BAD_REQUEST;
else if(ret==GET_REQUEST)
{
if(is_static)
return parse_static_request();
else
return parse_dynamic_request();
}
break;

}
}
return BAD_REQUEST;
}

requestreadstatus就是处理请求的三个状态,在每一个状态处理完之后会进行状态转移
这是处理整个请求的函数,其中有处理请求行的函数parse_requestline()

处理请求行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
http_conn::HTTP_CODE http_conn::HttpConn::parse_requestline()//处理请求行
{
if (text.empty())
return NO_REQUEST;
std::stringstream ss(text);
ss>>method>>uri>>version;
transform(method.begin(), method.end(), method.begin(), ::toupper);
if (method.compare("GET")!=0&&method.compare("POST")!=0)
{
return NOT_IMPLEMENTED;
}
is_static=parse_uri();
requestreadstatus=REQUEST_HEADER;
return SUCCESS;
}

处理请求头

处理请求头的函数parse_requesthead()还没有具体实现,只是读进来

1
2
3
4
5
6
7
8
9
10
http_conn::HTTP_CODE http_conn::HttpConn::parse_requesthead()//处理请求头
{
if(text=="\r\n")
{
requestreadstatus=REQUEST_BODY;
if (method=="GET")
return GET_REQUEST;
}
return SUCCESS;
}

处理请求体

处理请求体的函数parse_requestbody()同样还没具体实现

1
2
3
4
5
6
7
8
9
10
http_conn::HTTP_CODE http_conn::HttpConn::parse_requestbody()//处理请求体
{
if (text.empty())
return GET_REQUEST;
else
{

return SUCCESS;
}
}

分析url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int http_conn::HttpConn::parse_uri()//解析请求的资源
{
if(uri.find("cgi-bin")==std::string::npos)//静态资源
{
cgiargs="";//没有参数
filename="./static";//默认网站根目录味当前目录
filename+=uri;//加上请求的目录
if(*uri.rbegin()=='/')
filename+="index.html";//加上默认访问页面
return 1;//是静态请求
}else
{
int pos=uri.find("?");
if(pos!=std::string::npos)//有程序参数
{
cgiargs=uri.substr(pos+1);
uri=uri.substr(0,pos);
}else
cgiargs="";
filename=".";
filename+=uri;
return 0;//是动态请求
}
}

其中cgiargs是处理动态资源的时候会用到,现在还用不到,默认静态资源放在程序同级目录的static文件夹下,动态资源放在cgi-bin文件夹下

处理静态请求(动态请求还未实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
http_conn::HTTP_CODE http_conn::HttpConn::parse_static_request()//处理静态请求
{
struct stat sbuf;
std::string filetype,buf;
if(stat(filename.c_str(),&sbuf)<0)//获取文件的信息
{
return NOT_FOUND;
}
if(!(S_ISREG(sbuf.st_mode))||!(S_IRUSR&sbuf.st_mode))//没读取权限或不是一般文件
{
return Forbidden;
}
//获取文件类型
if(filename.find(".html")!=std::string::npos)
filetype="text/html";
else if(filename.find(".gif"))
filetype="image/gif";
else if(filename.find(".jpg"))
filetype="image/jpeg";
else
filetype="text/plain";
std::stringstream ss;
//构造http响应
ss<<"HTTP/1.0 200 OK\r\n";
ss<<"Server: Time Web Server\r\n";
ss<<"Content-length: "<<sbuf.st_size<<"\r\n";
ss<<"Content-type: "<<filetype<<"\r\n\r\n";
buf=ss.str();
rio.rio_writen(connfd,(char*)buf.c_str(),sbuf.st_size);
int filefd=open(filename.c_str(),O_RDONLY,0);
char *usrbuf=(char*)malloc(sbuf.st_size+2);
RIO::rio_t readbuf(filefd);
rio.rio_readn(filefd,usrbuf,sbuf.st_size);
rio.rio_writen(connfd,usrbuf,sbuf.st_size);
free(usrbuf);
close(filefd);
return SUCCESS;
}

处理错误信息

对于失败的请求我们要返回错误信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void http_conn::HttpConn::parse_error()//处理错误信息
{
std::stringstream ss;
std::string errnum;std::string errmsg,longmsg;
switch(requeststatuscode)
{
case SUCCESS:
case NO_REQUEST:
case GET_REQUEST:
break;
case BAD_REQUEST:
errnum="400";
errmsg="Bad Request";
longmsg="The request could not be understood by the server due to malformed syntax";
break;
case Forbidden:
errnum="403";
errmsg="Forbidden";
longmsg="The server understood the request, but is refusing to fulfill it";
break;
case NOT_FOUND:
errnum="404";
errmsg="NOT_FOUND";
longmsg="The server has not found anything matching the Request-URI";
break;
case NOT_IMPLEMENTED:
errnum="501";
errmsg="NOT_IMPLEMENTED";
longmsg="The server does not support the functionality required to fulfill the request";
break;
}
//构造响应
std::string body="<html><title>Server Error</title>";
body+="<body bgcolor=""ffffff"">\r\n";
body+=errnum+": "+errmsg+"\r\n";
body+="<p>"+longmsg+": "+filename+"\r\n";
body+="<hr><em>The Time Web Server</em>\r\n";
ss<<"HTTP/1.0 "<<errnum<<" "<<errmsg<<"\r\n";
ss<<"Content-type: "<<"text/html\r\n";
ss<<"Content-length: "<<body.size()<<"\r\n\r\n";
ss<<body;
body=ss.str();
rio.rio_writen(connfd,(char *)body.c_str(),body.size());
}

初始化

我们最后实现初始化这个类HttpConn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void http_conn::HttpConn::init()
{
requestreadstatus=REQUEST_LINE;
requeststatuscode=NO_REQUEST;

listenfd=M_SOCKET::MySocket::openListenfd(8888);//在8888端口打开一个监听描述符
struct sockaddr_in clientaddr;
unsigned int clientlen=sizeof(clientaddr);
connfd=accept (listenfd, (sockaddr*) &clientaddr,&clientlen);
rio.rio_readinitb(&riobuffer,connfd);
requeststatuscode=parse_request();
if(requeststatuscode!=SUCCESS)
{
parse_error();
}
close(connfd);
}

为了测试先这样写,之后肯定会改的
整个类的定义放到最后吧

测试

我没放文件返回了404

类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ifndef __HTTP__CONN_H__
#define __HTTP__CONN_H__
#include <http_server.h>
#include "rio.h"
#include "my_socket.h"
#include "http_server.h"
#include <sstream>
#include <algorithm>
#define MAXLINE 1024
namespace http_conn
{
enum HTTP_CODE {NO_REQUEST=0,GET_REQUEST=1,SUCCESS=200,BAD_REQUEST=400,Forbidden=403,NOT_FOUND=404,NOT_IMPLEMENTED=501};//http状态码
enum READ_LINE_STATUS {LINE_OK=1,LINE_BAD=-1,LINE_OPEN=0};//读取一行的状态,分别为读取成功,读取出错,正在读取
enum REQUEST_READ_STATUS {REQUEST_LINE,REQUEST_HEADER,REQUEST_BODY};//响应处理到的状态
class HttpConn
{
private:
RIO::rio_t riobuffer;//读取缓冲区
RIO::Rio rio;//文件IO类
HTTP_CODE requeststatuscode;
READ_LINE_STATUS readlinestatus;
REQUEST_READ_STATUS requestreadstatus;
std::string method,uri,version,filename,cgiargs;//请求的方法,uri,http版本,文件名,动态资源的参数
std::string text;//一行请求信息
int is_static;//判断是否是一个静态请求
int listenfd;
int connfd;//已连接描述符
READ_LINE_STATUS readLine();//读取一行请求
HTTP_CODE parse_requestline();//处理请求行
HTTP_CODE parse_requesthead();//处理请求头
HTTP_CODE parse_requestbody();//处理请求体
HTTP_CODE parse_request();//处理http请求
HTTP_CODE parse_static_request();//处理静态请求
HTTP_CODE parse_dynamic_request();//处理动态请求
void parse_error();//处理错误信息
int parse_uri();//解析请求的资源
public:
void init();
};
}

#endif//__HTTP__CONN_H__

本文标题:一个简单http_server的实现(3)

文章作者:

发布时间:2020年07月14日 - 19:07

最后更新:2020年07月14日 - 21:07

原始链接:http://startcraft.cn/post/75409a65.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------The End-------------