系列入口:编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客
本文介绍各种功能的实现。大部分是特定内置入口。
目录
一、默认页
二、查看文件
三、关闭服务
四、下载页面
一、默认页
前面在已经介绍过重定向,默认页可以通过重定向转到指定的页面,因为我这里的主要目的是提供嵌入功能,所以提供了一个内置的默认页:
如果资源名称是这“/default.asp”或“/default.htm”,就执行默认功能,HTML页面的框架已经用OnPageStart和OnPageEnd构建,专用的代码放在doPageDefault里面:
bool ShowStopServer()
{
string str;
//str="<BR><BR><A href=\"/shell.asp?curdir="+m_root+"\" target=\"_blank\">Shell</A><P>\n";
//m_respond.AppendBody(str);
str="<BR><BR><FORM ACTION=\"/stopserver.asp\" METHOD=\"POST\" target=\"_blank\">\n"
"<CODE>关闭服务器需要口令:</CODE><BR><INPUT TYPE=\"password\" SIZE=\"30\" NAME=\"password\" >\n"
"<INPUT TYPE=SUBMIT VALUE=\"关闭服务器\" >\n"
"</FORM>\n";
m_respond.AppendBody(str);
return true;
}
bool doPageDefault()
{
ShowFunctionList(false);
ShowStopServer();
return true;
}
ShowFunctionList()是显示功能列表,是真正面向嵌入的C++程序的部分,后面会详细介绍。
ShowStopServer()是显示关闭服务器的功能的用户入口,就是一个HTML表单(我用到的HTML功能都是基于HTML4的),你需要学习一下HTML,不过只需要最基本的就可以了。
看完这个应该就明白了,所谓嵌入式web服务器,就是用C++代码生成HTML给浏览器而已。
上面的代码中注释掉的一部分是另一个功能的入口,"/shell.asp"是执行后台命令的接口,因为执行权限很大,所以一般隐藏入口。
二、查看文件
查看文件的入口代码:
else if("/ViewFile.asp"==m_request.GetResource())
{
char buf[2048];
sprintf(buf,"查看文件 %s ",m_request.GetParam("file").c_str());
OnPageStart(buf);
doPageViewFile();
OnPageEnd();
m_s.Close();//所有此类页面都可能无法预先确定输出长度
isKeepAlive=false;
}
以“file”为查询参数,也就是类似这样的请求:“http://ip:port/ViewFile.asp?file=文件名”。
输出是HTML,OnPageStart和OnPageEnd处理HTML文件的开始和结束。主体部分则由doPageViewFile()提供:
bool doPageViewFile()
{
ifstream f;
char * buf;
long bufsize=1024*1024;
long count,len,outcount;
string file=m_request.GetParam("file");
string autorefresh=m_request.GetParam("autorefresh");
long start=atol(m_request.GetParam("start").c_str());
long end=atol(m_request.GetParam("end").c_str());
long origin_start=start;//原始start
long maxcount=atol(m_request.GetParam("maxcount").c_str());
if(0==maxcount)maxcount=500;//默认值
if(maxcount<0 || maxcount>50000)maxcount=50000;//最大行数限制
while(true)
{
if(NULL==(buf=new char[bufsize]))
{
m_respond.AppendBody("内存不足<P>");
break;
}
if(file.size()==0)
{
m_respond.AppendBody("没有文件名<P>");
break;
}
m_respond.AppendBody("文件名: ");
m_respond.AppendBody(file);
m_respond.AppendBody("<BR>");
//打开文件
f.open(file.c_str(),ios::in);
if(!f.good())
{
m_respond.AppendBody("打开文件错误<P>");
break;
}
//获取长度
f.seekg(0,ios::end);
len=f.tellg();
if(end>0)
{
sprintf(buf,"文件长度: %ld,开始位置:%ld,结束位置: %ld,请求的总长度:%ld<BR>------------------------------------------<P>\n",len,start,end,end-start);
len=end;
}
else
{
sprintf(buf,"文件长度: %ld,开始位置:%ld,请求的总长度:%ld<BR>------------------------------------------<P>\n",len,start,len-start);
}
m_respond.AppendBody(buf);
if(!m_respond.Flush(m_s))return true;
//读取内容
if(start<0)start=len+start;//负值代表从结束开始往前
if(start<0)start=0;//如果还是负的说明参数错误
f.seekg(start,ios::beg);
outcount=0;
count=0;
while(f.good())
{
if(count>=maxcount)break;//超过输出量限制
f.getline(buf,bufsize-1);
buf[bufsize-1]='\0';
m_respond.AppendBody(LogToHtml(buf,false,true));
++count;
if(count%100==0)
{
m_respond.AppendBodyHtmlScroll();
if(!m_respond.Flush(m_s))break;
}
outcount=f.tellg()-start;
if(f.tellg()>=len)
{
break;
}
}
m_respond.Flush(m_s);
f.close();
m_respond.AppendBody("<P>------------------------------------------<P>");
sprintf(buf,"剩余字节数: %ld",len-start-outcount);
m_respond.AppendBody(buf);
break;
}
if(!m_respond.Flush(m_s))return true;
char buf2[10240];
sprintf(buf2,
"<FORM METHOD=\"GET\" name=\"form_view\">\n"
"<INPUT TYPE=\"hidden\" NAME=\"file\" VALUE=\"%s\" >\n"
"<INPUT TYPE=\"hidden\" NAME=\"autorefresh\" VALUE=\"%s\" >\n"
"起点(从0开始,负值代表从文件尾倒数):<INPUT TYPE=\"text\" SIZE=\"30\" NAME=\"start\" VALUE=\"%ld\"><BR>\n"
"终点(从0开始,负值代表从文件尾倒数):<INPUT TYPE=\"text\" SIZE=\"30\" NAME=\"end\" VALUE=\"%ld\"><BR>\n"
"最大输出行数: <INPUT TYPE=\"text\" SIZE=\"30\" NAME=\"maxcount\" VALUE=\"%ld\">\n"
"<INPUT TYPE=SUBMIT VALUE=\"执行\" >\n"
"</FORM>\n"
,file.c_str(),autorefresh.c_str(),(origin_start<0?origin_start:start+outcount),end,maxcount);
m_respond.AppendBody(buf2);
bool isAutoRefresh=(m_request.GetParam("autorefresh")=="true");
sprintf(buf2,
"<BUTTON NAME=\"autorefresh\" onclick=\"timer=setTimeout('window.form_view.submit()',10000);window.form_view.autorefresh.value='true';window.autorefresh.disabled=true;window.stoprefresh.disabled=false;\">自动刷新</BUTTON>\n"
"<button NAME=\"stoprefresh\" disabled onclick=\"clearTimeout(timer);window.form_view.autorefresh.value='';window.autorefresh.disabled=false;window.stoprefresh.disabled=true;\">停止刷新</BUTTON><BR>\n"
"%s<BR>\n"
,(isAutoRefresh?"<script language=\"JavaScript\">window.autorefresh.onclick()</script>":""));
m_respond.AppendBody(buf2);
if(!m_respond.Flush(m_s))return true;
delete[] buf;
return true;
}
嘿嘿,现在明白为什么需要一个专门的功能了吧。这个功能支持指定开始位置和结束位置,并且可以自动刷新、自动滚动,还给文件内容增加了格式处理(LogToHtml()函数给日志根据类型上色)
三、关闭服务
默认页提供了关闭服务的用户入口,用户入口会传到这个页面来:
else if("/stopserver.asp"==m_request.GetResource())
{
OnPageStart("stop server",true);
if(NULL!=pfCheckAdmin && !pfCheckAdmin(m_request.GetParam("password").c_str()))
{
m_respond.AppendBody("口令错误");
OnPageEnd();
}
else
{
(*pShutDown) = true;
m_respond.AppendBody("收到停止信号,服务正在停止......");
m_respond.Flush(m_s);
OnPageEnd();
}
isKeepAlive=false;
m_s.Close();
}
首先检查管理员密码对不对(普通用户不能关闭服务器),然后返回“服务正在停止”页面给用户,最后退出服务程序。
四、下载页面
前面已经介绍过下载的实际功能代码,这是入口点:
else if("/DownFile.asp"==m_request.GetResource())
{
if(doPageFile(m_request.GetParam("file").c_str()))
{
m_respond.Flush(s);
}
else
{
m_respond.Flush(s);
m_s.Close();//所有此类页面都可能无法预先确定输出长度
isKeepAlive=false;
}
}
也是以“file”为参数。实际功能入口和静态文件是一样的,区别是会设置头标指示是文件下载。
(这里是结束,但不是整个系列的结束)