#include "codec.h"
#include "socket_httpserver.h"
//构造函数
CHttpReq::CHttpReq(STRCPP rootdir):m_rootdir(rootdir)
{
m_rbuff.clear();
m_sbuff.clear();
m_reqhead.clear();
m_reqbody.clear();
m_reqinfo.clear();
m_reqstat = 1;
m_reqsize = 0;
m_reqfile.clear();
m_method.clear();
m_requrl.clear();
m_query.clear();
}
//打印http请求内容
VOID CHttpReq::show()
{
cout << "********************************************" << endl;
cout << m_reqhead;
cout << m_reqbody;
cout << endl;
cout << "********************************************" << endl;
cout << "- method: " << m_method << endl;
cout << "- requrl: " << m_requrl << endl;
cout << "- reqfile:" << m_reqfile << endl;
cout << "- version:" << m_version << endl;
cout << "--------------------------------------------" << endl;
}
//深copy
VOID* CHttpReq::clone()
{
CHttpReq* req = new CHttpReq("");
*req = *this;
return req;
}
/* 接受一次http请求
请求行 reqline 方法 空格 URL 空格 协议版本
请求头部 headers 头字段key 冒号: 值value
空行 "" CRLF
请求消息体 reqbody
*/
BOOL CHttpReq::receive()
{
STRCPP line;
if(readinfo(m_rbuff))
{
while(1 == m_reqstat && getline(m_rbuff, line))
{
get_reqline(line);
get_headers(line);
get_reqstat(line);
}
while(2 == m_reqstat)
{
if(0 < m_reqsize)
{
return getsize(m_rbuff, m_reqbody, m_reqsize);
}
return TRUE;
}
}
return FALSE;
}
//分割请求行 分成 方法 URL 协议版本
BOOL CHttpReq::get_reqline(STRCPP& line)
{
if(0 == m_method.size() || 0 == m_requrl.size())
{
STRVEC strvec;
strsplit(line, " ", strvec);
if(strvec.size() > 2)
{
m_method = strvec[0];
m_requrl = url_decode(strvec[1]);
m_version = strvec[2];
//URL结构 <scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
STRVEC urlvec;
STRCPP urlinfo;
strsplit(m_requrl, "#", urlvec);
urlinfo = urlvec[0];
strsplit(urlinfo, "?", urlvec);
if(urlvec.size() > 0)
{
m_reqfile = m_rootdir + urlvec[0];
}
if(urlvec.size() > 1 && m_method == "GET")
{
m_query = urlvec[1];
}
}
}
return TRUE;
}
BOOL CHttpReq::get_headers(STRCPP& line)
{
ULONG splitpos = line.find(": ");
m_reqhead += line + "\n";
if(string::npos != splitpos)
{
STRCPP key = line.substr(0, splitpos);
STRCPP val = line.substr(splitpos+2);
m_reqinfo[key] = val;
if(key == "Content-Length")
{
m_reqsize = atoi(val.c_str());
}
}
return TRUE;
}
//收到空行 则接收请求头完成 切换状态为 接收请求体
BOOL CHttpReq::get_reqstat(STRCPP& line)
{
if("" == line || "\r\n" == line || "\n" == line)
{
m_reqstat = 2;
}
return TRUE;
}
VOID CHttpReq::response_info(const STRCPP& code, const STRCPP& info)
{
OSTREAM buffer;
buffer << HTTP_HEAD_VERSION << code << " " << CHttpInfo::respinfo(code) << "\r\n";
buffer << "Server: " << HTTP_SERVER_NAME + "\r\n";
buffer << "Date: " << getgmttime(time(NULL)) + "\r\n";
buffer << "Content-Type: " << "text/html" << "\r\n";
buffer << "Content-Length: " << info.size() << "\r\n";
buffer << "\r\n";
sendinfo(buffer.str());
sendinfo(info);
}
// 响应文件
// 如果包含If-Modified-Since 并且和文件的最后修改时间相同 则应答304 不带文件内容 否则应答文件内容 并设置Last-Modified
VOID CHttpReq::response_file(const STRCPP& filename, const FILEST& state)
{
IFSTREAM filestream;
SSTREAM headstream;
STRCPP firstline;
SSTREAM filebuff;
filestream.open(filename.c_str(), ios::in | ios::binary);
headstream << HTTP_HEAD_VERSION << "200" << " " << CHttpInfo::respinfo("200") << "\r\n";
headstream << "Server: " << HTTP_SERVER_NAME + "\r\n";
headstream << "Date: " << getgmttime(time(NULL)) + "\r\n";
headstream << "Content-Type: " << CHttpInfo::mimetype(filename) << "\r\n";
filebuff << filestream.rdbuf();
headstream << "Content-Length: " << filebuff.str().size() << "\r\n";
headstream << "\r\n";
sendinfo(headstream.str());
sendinfo(filebuff.str());
}
VOID CHttpReq::response_path(const STRCPP& path)
{
STRCPP fileList;
DIR* dir = NULL;
DIRENT* item = NULL;
dir = opendir(path.c_str());
while(NULL != (item = readdir(dir)))
{
fileList += "<a href='";
fileList += item->d_name;
fileList += "'>";
fileList += item->d_name;
fileList += "</a></br>";
}
closedir(dir);
response_info("200", fileList);
}
BOOL CHttpReq::process()
{
show();
if("GET" == m_method && m_query == "")
{
process_GET();
}
else if(("GET" == m_method && m_query != "") || ("POST" == m_method))
{
process_POST();
}
else
{
response_info("501", m_method + "Not Implement");
}
return TRUE;
}
VOID CHttpReq::process_GET()
{
STRCPP path;
FILEST state;
if(-1 == stat(m_reqfile.c_str(), &state))
{
return response_info("404", m_reqfile + " NOT FOUND");
}
if(S_ISDIR(state.st_mode))
{
path = m_reqfile;
m_reqfile = path + "/" + "index.html";
if(0 == stat(m_reqfile.c_str(), &state) && !S_ISDIR(state.st_mode))
{
return response_file(m_reqfile, state);
}
return response_path(path);
}
else
{
return response_file(m_reqfile, state);
}
}
VOID CHttpReq::process_POST()
{
process_CGI();
}
VOID CHttpReq::process_CGI()
{
FILEFD input[2] = {0};
FILEFD output[2] = {0};
PID pid;
//创建管道和子进程
if(pipe(input) < 0 || pipe(output) < 0 || (pid = fork()) < 0)
{
return response_info("501", "POST_webcgi pipe or fork failed");
}
//子进程执行cgi脚本
if(0 == pid)
{
dup2(input[FDREAD], STDIN);
dup2(output[FDWRITE], STDOUT);
close(input[FDWRITE]);
close(output[FDREAD]);
process_CGI_setallenv();
process_CGI_runcgi();
exit(0);
}
//父进程读取cgi的输出 应答客户端
else
{
PSTAT st;
close(input[FDREAD]);
close(output[FDWRITE]);
process_CGI_writereqbody(input[FDWRITE], m_reqbody);
process_CGI_readresponse(output[FDREAD]);
close(input[FDWRITE]);
close(output[FDREAD]);
waitpid(pid, &st, 0);
}
}
//需要循环写 待实现
VOID CHttpReq::process_CGI_writereqbody(FILEFD fd, const STRCPP& reqbody)
{
write(fd, reqbody.c_str(), reqbody.size());
}
VOID CHttpReq::process_CGI_readresponse(FILEFD fd)
{
CHAR ch;
while(0 < read(fd, &ch, 1))
{
cout << ch;
sendinfo(STRCPP(&ch, 1));
}
}
VOID CHttpReq::process_CGI_setoneenv(const STRCPP& key, const WORD32& val)
{
SSTREAM valstr;
valstr << val;
setenv(key.c_str(), valstr.str().c_str(), 1);
}
VOID CHttpReq::process_CGI_setoneenv(const STRCPP& key, const STRCPP& val)
{
setenv(key.c_str(), val.c_str(), 1);
}
VOID CHttpReq::process_CGI_setallenv()
{
/*
CGI环境变量名称 说明
REQUEST_METHOD 请求类型,如“GET”或“POST”
CONTENT_TYPE 被发送数据的类型
CONTENT_LENGTH 客户端向标准输入设备发送的数据长度,单位为字节
QUERY_STRING 查询参数,如“id=10010&sn=liigo”
SCRIPT_NAME CGI脚本程序名称
PATH_INFO CGI脚本程序附加路径
PATH_TRANSLATED PATH_INFO对应的绝对路径
REMOTE_ADDR 发送此次请求的主机IP
REMOTE_HOST 发送此次请求的主机名
REMOTE_USER 已被验证合法的用户名
REMOTE_IDENT WEB服务器的登录用户名
AUTH_TYPE 验证类型
GATEWAY_INTERFACE 服务器遵守的CGI版本,如:CGI/1.1
SERVER_NAME 服务器主机名、域名或IP
SERVER_PORT 服务器端口号
SERVER_PROTOCOL 服务器协议,如:HTTP/1.1
DOCUMENT_ROOT 文档根目录
SERVER_SOFTWARE 服务器软件的描述文本
HTTP_ACCEPT 客户端可以接收的MIME类型,以逗号分隔
HTTP_USER_AGENT 发送此次请求的web浏览器
HTTP_REFERER 调用此脚本程序的文档
HTTP_COOKIE 获取COO