有些事情可能其实没那么困难,比如写一个可用的web服务器。
当然我不是说写一个能够和apache或者IIS或者tomcat之类竞争的web服务器,我说的只是给自己的程序添加一个内置的HTTP服务,以便能做一点特别的事情。
我没打算从HTTP协议讲起,所以你要先学习一下HTTP协议。我直接从代码开始。
系列索引:
编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客(本文)
编程实战:自己编写HTTP服务器(系列2:请求)-CSDN博客
编程实战:自己编写HTTP服务器(系列3:处理框架)-CSDN博客
编程实战:自己编写HTTP服务器(系列4:查看文件、下载等一般功能)-CSDN博客
编程实战:自己编写HTTP服务器(系列5:执行后台shell命令)-CSDN博客
编程实战:自己编写HTTP服务器(系列6:调用用户功能)-CSDN博客
编程实战:自己编写HTTP服务器(系列7:用户功能接口)-CSDN博客
编程实战:自己编写HTTP服务器(系列8:流水输出和无刷新动态显示)-CSDN博客
目录
一、概述
二、处理应答
2.1 代码总览
2.2 成员变量
2.3 发送应答 Flash()
2.4 应答的全部代码
三、处理请求
作为一个TCP服务,首先要基于一个普通的socket服务。socket服务并没有什么特别,不过是bind+accept而已,然后就是普通的recieve和send。
我本来想贴socket服务代码出来的,一看旧代码晕了,太多私货了。你还是自己写个服务吧。作为技术验证,不需要多线程多进程,最简单的单进程阻塞循环就可以了。
虽然HTTP2.0挺复杂,但是仅支持1.0也没关系,客户端都能支持。HTTP1.0很简单,一个连接一个请求一个应答,也就是说,服务端收到一串信息,然后返回一串信息,然后就关闭连接。
所以服务器可以很简单,我写了一个类用来分析请求,又写了一个类处理应答,再加上一点别的处理,一个基本的HTTP服务器就可以工作了。
为了能快速见到效果,我们先处理应答。为什么先处理应答呢?有了应答起码能从浏览器知道基本机制已经在运作了,然后再考虑根据请求输出。
处理应答的类:
其实相当的简单,SendXXX()系列函数直接发送特定应答。例如返回没有内容的200响应:
//返回无内容的成功
bool Send200(CSocket & s)
{
m_status_line = "HTTP/1.1 200 OK";
AddHeaderContentLength();
return Flush(s);
}
实际返回的就是状态行和一个内容长度头标。
private:
string m_status_line;//状态行
vector > m_headers;//应答头
map m_cookies;
string m_body;//消息体
long m_content_length;//设置的内容长度,未设置为-1
long m_content_length_sended;//实际发送的内容长度
bool m_isCgi;
前四个成员变量直接对应了HTTP应答的四个部分:状态行、头标、cookies、body。当然,cookies也是一个头标,但是是一个很特别的头标,值得特殊处理。
另外三个是内部用途。与CGI有关的可以无视。
Flash函数根据成员变量发送应答:
bool Flush(CSocket & s)
{
if (m_isCgi)return CgiFlush();
string str;
vector >::size_type i;
//仅在状态行不为空的情况下发送应答头
if (m_status_line.size() != 0)
{
str = m_status_line + "\r\n";
for (i = 0; i < m_headers.size(); ++i)
{
str += m_headers[i].first + ": " + m_headers[i].second + "\r\n";
}
map::const_iterator it;
for (it = m_cookies.begin(); it != m_cookies.end(); ++it)
{
str += "Set-Cookie: ";
str += it->first + "=" + it->second + "\r\n";
}
str += "\r\n";
}
str += m_body;
m_content_length_sended += m_body.size();
m_status_line = "";
m_headers.clear();
m_body = "";
DEBUG_LOG << "发送应答" << endl << str << ENDI;
if (!s.Send(str))
{
s.Close();
return false;
}
return true;
}
看了这个这个函数应该对应答有了充分了解了,很简单的嘛。
真的没什么好多说的,代码都是一目了然的。
//HTTP应答
//AddHeaderXXXX系列函数添加应答头,必须在Flush之前使用
class CHttpRespond
{
private:
string m_status_line;//状态行
vector > m_headers;//应答头
map m_cookies;
string m_body;//消息体
long m_content_length;//设置的内容长度,未设置为-1
long m_content_length_sended;//实际发送的内容长度
bool m_isCgi;
public:
//判断是否可以保持连接,依据是发送了内容长度并且实际发送长度匹配
bool isCanKeepAlive()const
{
return m_content_length >= 0 && m_content_length == m_content_length_sended;
}
string GetBody()const { return m_body; }
//返回无内容的成功
bool Send200(CSocket & s)
{
m_status_line = "HTTP/1.1 200 OK";
AddHeaderContentLength();
return Flush(s);
}
//重定向
bool Send302(CSocket & s, string const & location)
{
m_status_line = "HTTP/1.1 302 Found";
AddHeader("Location", location);
AddHeaderContentLength();
return Flush(s);
}
//错误的请求
bool Send400(CSocket & s)
{
m_status_line = "HTTP/1.1 400 BadRequest";
AddHeaderContentLength();
return Flush(s);
}
//需要认证
bool Send401(CSocket & s, string const & realm)
{
m_status_line = "HTTP/1.1 401 Unauthorized";
string str = "Basic realm=\"" + realm + "\"";
AddHeader("WWW-Authenticate", str);
AddHeaderContentLength();
return Flush(s);
}
//禁止访问
bool Send403(CSocket & s)
{
m_status_line = "HTTP/1.1 403 Forbidden";
AddHeaderContentLength();
return Flush(s);
}
//没有找到
bool Send404(CSocket & s)
{
m_status_line = "HTTP/1.1 404 NotFound";
AddHeaderContentLength();
return Flush(s);
}
//内部错误
bool Send500(CSocket & s)
{
m_status_line = "HTTP/1.1 500 Internal Server Error";
AddHeaderContentLength();
return Flush(s);
}
//内部错误,返回第一行为错误码然后是错误信息,错误信息为全局错误信息
bool Send500WithMessage(CSocket & s)
{
m_status_line = "HTTP/1.1 200 Internal Server Error";
AddHeaderContentTypeByFilename("*.txt");
char buf[256];
sprintf(buf, "%ld\n", G_GET_ERROR);
AppendBody(buf);
AppendBody(G_ERROR_MESSAGE().str());
G_CLEAR_ERROR;
AddHeaderContentLength();
return Flush(s);
}
void Clear()
{
m_status_line = "";
m_body = "";
m_content_length = -1;
m_content_length_sended = 0;
m_headers.clear();
m_cookies.clear();
m_isCgi = false;
}
void Init(bool _cgi = false)//初始化应答,设置通用部分
{
Clear();
m_isCgi = _cgi;
m_status_line = "HTTP/1.1 200 OK";
AddHeader("Server", "WWW-Server/1.0 by ZB++");
//AddCookie("user","unknown");
//AddCookie("id","unknown");
}
void AddHeader(string const & header, string const & value)//添加应答头,除了"Server"其它都可以用,但有一些可以更方便地使用AddHeaderXXXX
{
if ("Server" != header)m_headers.push_back(pair(header, value));
}
void AddHeader(string const & header, long value)
{
char buf[256];
sprintf(buf, "%ld", value);
AddHeader(header, buf);
}
void AddCookie(string const & name, string const & value)
{
m_cookies[name] = value;
}
void AppendBody(long n)//添加消息体
{
char buf[64];
sprintf(buf, "%ld", n);
m_body += buf;
}
void AppendBody(string const & str)//添加消息体
{
m_body += str;
//LOG<<"HTML 输出:"<
{
m_body += "\r\n"
"\r\n"
"\r\n"
"\r\n"
"\r\n"
;
if (NULL != title)
{
m_body += "";
m_body += title;
m_body += " \r\n";
}
m_body += "\r\n\r\n";
}
void AppendBodyHtmlEnd()//添加HTML文档结束,从开始
{
m_body += "\r\n