编程实战:自己编写HTTP服务器(系列3:处理框架)

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


系列入口:编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客

        本文介绍处理框架。

目录

一、框架概述

二、保持连接

三、基本认证

四、静态文件和文件下载

4.1 主要代码

4.2 规则

4.3 添加内容类型头标的代码(应答类)

4.4 处理类型的代码

五、框架的完整代码

六、处理特定功能


一、框架概述

        处理框架针对的是一个连接,里面用了循环,支持HTTP1.1,如果不循环就是1.0了(1.1和1.0就这点区别)。

        这个框架具有下列功能:

  • 默认页,访问“/”会被重定向
  • 基本认证
  • 一系列的内置页面,后缀名为“asp”,嘿嘿,不要跟真正的asp混淆了
  • 静态网页,因为支持静态网页,所以可以让前端帮忙设计css

        因为主要目的是嵌入到已有的C++程序中,所以内置页面是我的重点,但作为普通服务器,有一个支持静态页面的功能就足够了。

        续:

         流程其实也是相当一目了然的。

二、保持连接

        保持连接是HTTP1.1的特征,通过头标“Connection=Keep-Alive”(=后面有没有空格注意一下,可以从前两篇如何构造和分解头标的代码确认)指示。如果不打算保持连接,发送完毕后直接关闭连接即可。

三、基本认证

        基本认证的主流程部分:

				//处理用户认证,登录用的目录不需要认证,登录目录可以用XMLHttp来实现整合登录//login目录下的内容无需认证,这要求login下引用的图片也必须在login目录下if(NULL!=this->pfCheckUser){char const * logondir="/login/";if(0!=strncmp(logondir,m_request.GetResource().c_str(),strlen(logondir))){string user;string password;if(m_request.GetAuthorization(user,password)){if(!pfCheckUser(user.c_str(),password.c_str())){m_respond.Send401(m_s,m_pServerDatas->m_realm.c_str());if(isKeepAlive)continue;else{m_s.Close();break;}}string cookie="logon_user";if(m_request.GetCookie(cookie)!=user)m_respond.AddCookie(cookie,user);}else{m_respond.Send401(m_s,m_pServerDatas->m_realm.c_str());if(isKeepAlive)continue;else{m_s.Close();break;}}}}

         pfCheckUser是个函数指针,指向检测用户名密码的函数,如果未设置就表示不需要认证。定义如下:

		bool (*pfCheckUser)(char const * _user,char const * _pass);//检查普通用户口令

        那么浏览器如何知道需要认证呢?这是通过401应答告知浏览器的,所以看一下应答类的Send401的代码:

		//需要认证bool Send401(CZBSocket & 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);}

        realm是认证的域,一般就是网站域名。浏览器收到401应答就会要求用户输入用户名和密码,然后放在请求头里面发过来,请求类的GetAuthorization从请求里面解析出用户名和密码:

		bool GetAuthorization(string & user, string & password)const{string AUTHORIZATION = "Authorization: Basic ";string::size_type pos_start;string::size_type pos_end;string base64;if (m_fullrequest.npos == (pos_start = m_fullrequest.find(AUTHORIZATION)))return false;if (m_fullrequest.npos == (pos_end = m_fullrequest.find("\r\n", pos_start)))return false;base64 = m_fullrequest.substr(pos_start + AUTHORIZATION.size(), pos_end - pos_start - AUTHORIZATION.size());char buf[2048];int len;if (0 > (len = CBase64::Base64Dec(buf, base64.c_str(), (int)base64.size()))){LOG << "base64解码错误" << ENDE;}buf[len] = '\0';DEBUG_LOG << buf << ENDI;CStringSplit st(buf, ":");if (st.size() != 2)return false;user = st[0];password = st[1];return true;}

         头标格式是:“Authorization: Basic 用户名:密码”,其中用户名和密码部分用Base64编码,所以要先解码然后拆成两个字符串。

        由于基本认证是明文传输用户名和密码(强调:Base64是明文编码,不是加密),所以浏览器可能会提出安全警告,如果连接是HTTPS的,那就没问题了。使用HTTPS只需要把socket发送接收替换成SSL的对应接口就可以了。

四、静态文件和文件下载

4.1 主要代码

        框架里的所有asp文件其实都是嵌入的特定C++功能,并非真实的文件,算是我故意搞鬼吧。

        最后的else里执行doPageFile()才是处理静态文件。代码如下:

		bool doPageFile(char const * file=NULL){fstream f;long bufsize=1024*1024;char * buf;long count,len;buf=new char[bufsize];if(NULL==buf){LOG<<"内存不足"<<ENDE;return true;}m_respond.AddHeaderExpires(time(NULL),60);//60秒过期string filename;if(NULL==file || strlen(file)==0){filename=m_request.GetResource();LOG<<m_pServerDatas->m_root.c_str()<<ENDI;if('/'==filename[filename.size()-1]){//对于目录要打开默认页filename+="default.htm";}if(0==m_pServerDatas->m_root.size())return false;if('/'==filename[0])filename.erase(0,1);filename=m_pServerDatas->m_root.c_str()+filename;//检查是否是目录
#ifndef _MS_VCstruct stat statbuf;if(0==stat(filename.c_str(),&statbuf)){if(S_ISDIR(statbuf.st_mode)){OnPageStart("404 NOT FOUND");m_respond.AppendBody("请求的资源是目录,请在资源最后增加\"/\"");OnPageEnd();return true;}}
#endif}else{filename=file;string filetitle;filetitle=filename.substr(filename.npos==filename.find_last_of("/")?0:filename.find_last_of("/")+1);m_respond.AddHeader("Content-disposition","attachment; filename="+filetitle);}LOG<<filename.c_str()<<ENDI;m_respond.AddHeaderContentTypeByFilename(filename.c_str());f.open(filename.c_str(),ios::in|ios::binary);if(!f.good()){OnPageStart("404 NOT FOUND");m_respond.AppendBody("<P>404 NOT FOUND<P>请求的资源不存在<P>");OnPageEnd();return false;}f.seekg(0,ios::end);len=f.tellg();m_respond.AddHeaderContentLength(len);f.seekg(0,ios::beg);if(!m_respond.Flush(m_s))return false;while(f.good()){if(len-f.tellg()>bufsize-1)count=bufsize-1;else count=len-f.tellg();if(0==count)break;f.read(buf,count);if(!m_s.Send(buf,count)){m_s.Close();break;}}f.close();delete[] buf;return true;}

        读取文件没什么好说的,标准编程。

4.2 规则

  • 如果资源以“/”结束,附加“default.htm”,也就是打开默认页,禁止目录浏览(当然你也可以允许目录浏览)
  • 如果是目录但不是以“/”结束,返回404并提醒需要加“/”(注意这里不是返回404应答,是200正常应答,很多网站都这么搞的,不然浏览器显示的是浏览器自己的404错误页,无法给用户显示有价值的信息)
  • 如果入口参数传递了文件名,作为附件发送,方法是添加一个特定的头标“Content-disposition”,内容为“attachment; filename=文件名”,不添加就是普通内容,浏览器会直接显示。文件下载一般是网站的网页里面通过传递参数给特定入口点来实现的,比如我用/DownFile.asp作为下载入口,文件名是参数,内部调用这个函数来实现具体功能
  • 如果文件读取成功,文件内容就是body,添加内容类型头标和内容长度头标

4.3 添加内容类型头标的代码(应答类)

		void AddHeaderContentTypeByFilename(char const * filename)//添加内容类型到应答头,如果已经存在则替换,根据文件名后缀判断{for (size_t i = 0; i < m_headers.size(); ++i){if (m_headers[i].first != "Content-Type")continue;m_headers[i].second = CMIMEType::GetMIMEType(filename);return;}m_headers.push_back(pair<string, string >("Content-Type", CMIMEType::GetMIMEType(filename)));}

4.4 处理类型的代码

char const * GetMIMEType(char const * filename){STATIC_C char const mimetype[][2][64]={{"htm","Text/html; charset=UTF-8"},{"html","Text/html; charset=UTF-8"},{"txt","Text/plain; charset=UTF-8"},{"log","Text/plain; charset=UTF-8"},{"xml","Text/xml; charset=UTF-8"},{"sh","Text/plain; charset=UTF-8"},{"css","Text/css; charset=UTF-8"},{"url","application/x-www-form-urlencoded"},{"",""}};STATIC_C char const * defaulttype="application/octet-stream";//最后方案,找不到就用这个long pos=strlen(filename)-1;while(pos>=0 && filename[pos]!='.' && filename[pos]!='/')--pos;if(pos<0 || filename[pos]=='/')return defaulttype;char const * ext=filename+pos+1;char const (* p)[2][64]=mimetype;while(strlen((*p)[0])!=0){if(strcmp((*p)[0],ext)==0)return (*p)[1];++p;}return defaulttype;}

五、框架的完整代码

		//处理一个已经建立的连接virtual bool SocketProcess(CSocket & s, bool * pShutDown, long * pRet, long i_child, SocketServerControlBlock::T_CHILD_DATA * pThisProcessData){m_s=s;m_i_child = i_child;m_pThisChildData = pThisProcessData;//支持HTTP1.1,一个连接处理多个请求while(m_s.IsConnected() && !*pShutDown){bool isReady = false;pThisProcessData->SetHttpProcessInfo("wait...");if (!m_s.IsSocketReadReady(1, isReady)){LOG<<"socket error"<<ENDE;m_s.Close();return true;}if (!isReady){//DEBUG_LOG << "socekt not ready On HTTP Process" << ENDI;continue;}bool isKeepAlive=false;m_request.Clear();m_respond.Init();pThisProcessData->SetHttpProcessInfo("RecvRequest...");if(!m_request.RecvRequest(m_s)){if(m_s.IsConnected()){pThisProcessData->SetHttpProcessInfo("错误的请求");LOG << getpid() << "错误的请求:" << m_request.GetFullRequest() << ENDE;doPageBedRequest();m_s.Close();}else{pThisProcessData->SetHttpProcessInfo("连接已关闭");LOG<<getpid()<<"连接已关闭"<<ENDE;}break;}++pThisProcessData->request_count;LOG<<getpid()<<"接收到请求,接连信息:\n"<<m_s.debuginfo()<<ENDI;LOG<<getpid()<<"接收到请求,请求信息:\n"<<m_request.GetFullRequest()<<ENDI;pThisProcessData->SetHttpProcessInfo(m_request.GetResource().c_str());//检查是否需要保持连接if(m_request.GetHeader("Connection")=="Keep-Alive"){LOG<<getpid()<<"保持连接"<<ENDI;isKeepAlive=true;}else{LOG<<"不保持连接"<<ENDI;isKeepAlive=false;}if("/"==m_request.GetResource()){m_respond.Send302(m_s,"/login/login.htm");if(isKeepAlive)continue;else{m_s.Close();break;}}//处理用户认证,登录用的目录不需要认证,登录目录可以用XMLHttp来实现整合登录//login目录下的内容无需认证,这要求login下引用的图片也必须在login目录下if(NULL!=this->pfCheckUser){char const * logondir="/login/";if(0!=strncmp(logondir,m_request.GetResource().c_str(),strlen(logondir))){string user;string password;if(m_request.GetAuthorization(user,password)){if(!pfCheckUser(user.c_str(),password.c_str())){m_respond.Send401(m_s,m_pServerDatas->m_realm.c_str());if(isKeepAlive)continue;else{m_s.Close();break;}}string cookie="logon_user";if(m_request.GetCookie(cookie)!=user)m_respond.AddCookie(cookie,user);}else{m_respond.Send401(m_s,m_pServerDatas->m_realm.c_str());if(isKeepAlive)continue;else{m_s.Close();break;}}}}if("/default.asp"==m_request.GetResource() || "/default.htm"==m_request.GetResource()){OnPageStart("default");doPageDefault();OnPageEnd(false);}else if("/functionlist.asp"==m_request.GetResource()){OnPageStart("Function List");doPageFunctionList();OnPageEnd(false);}else if("/admin.asp"==m_request.GetResource()){doPageAdmin(pShutDown);}else if("/RegistSlave.asp"==m_request.GetResource()){doPageRegistSlive(pShutDown);}else if("/shell.asp"==m_request.GetResource()){OnPageStart("shell");doPageShell();OnPageEnd();m_s.Close();//所有此类页面都可能无法预先确定输出长度isKeepAlive=false;}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;}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;}}else if(m_request.GetResource().substr(0,5)=="/bin/" || m_request.GetResource().substr(0,7)=="/admin/"){//执行用户功能string resourcetype = m_request.GetResourceType();if (resourcetype == "asp" || resourcetype == "aspx" || resourcetype == "asmx"){doPageFunction();//内置页面}else{doPageCGI();//动态链接库实现的用户功能}}else{doPageFile();m_respond.Flush(s);}//客户指定不保持连接或应答不支持保持连接则关闭连接if(!isKeepAlive || !m_respond.isCanKeepAlive()){m_s.Close();break;}}return true;}

        中间用不上的部分都可以删掉。

六、处理特定功能

        待续


(这里是结束,但不是整个系列的结束)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/15707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TypeScript学习日志-第三十二天(infer关键字)

infer关键字 一、作用与使用 infer 的作用就是推导泛型参数&#xff0c;infer 声明只能出现在 extends 子语句中&#xff0c;使用如下&#xff1a; 可以看出 已经推导出类型是 User 了 二、协变 infer 的 协变会返回联合类型&#xff0c;如图&#xff1a; 三、逆变 infer…

pikachu靶场中的CSRF、SSRF通关

目录 1、CSRF介绍 2、CSRF&#xff08;get&#xff09; 3、CSRF&#xff08;post&#xff09; 4、CSRF Token 5、SSRF介绍 6、SSRF&#xff08;curl&#xff09; 7、SSRF&#xff08;file_get-content&#xff09; 8、CSRF与SSRF的区别 最近在学习CSRF、SSRF漏洞&#…

Haskell 的高阶函数(Higher-order functions)

本节继续介绍Haskell的知识&#xff0c;本节内容介绍的是高阶函数&#xff08;Higher-order functions&#xff09;的概念和应用。高阶函数是指能够接受其他函数作为参数&#xff0c;或者返回函数作为结果的函数。 Functions as values&#xff08;函数作为值&#xff09;&…

Boyer-Moore投票算法

摩尔投票法&#xff0c;又称为博耶-摩尔多数投票算法&#xff0c;是一种用于在一组数据中寻找多数元素(出现次数超过一半的元素)的算法。该算法的效率非常高&#xff0c;时间复杂度为O(n)&#xff0c;空间复杂度为O(1)&#xff0c;适合处理大数据量的情况。 步骤 首先定义两个…

飞机大战游戏实现揭秘

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、游戏概览与核心玩法 二、游戏模块详解 1. 游戏主循环模块 2. 创建初始化模块 三、关…

【pytorch】关于OpenCV和PIL.Image读取图片的区别

数据使用方角度 首先从pytorch出发&#xff0c;torchvision.transforms()要求传入的图像是PIL.Image格式&#xff08;通道要求是RGB格式的&#xff09;&#xff0c;另外模型处理输入要转换为[1,channel,H,W]&#xff1b; 所以最终导入torchvision.transforms()的图像格式需要转…

跟我学C++中级篇——内存屏障内存栅栏和编译器屏障以及相关

一、低级同步常见的技术术语 在一些操作系统或者计算机接口等比较原理化的书籍中&#xff0c;经常提到一些低级的同步术语&#xff0c;或者说一些同步的抽象的说法。最典型的就是内存内存屏障。不同的平台和语言有不同的叫法&#xff0c;有的叫内存栅栏或者屏障指令。它的主要…

在3090卡服务器上面进行funasr模型微调

文本记录了在3090卡上对实时asr模型进行微调的过程&#xff0c;包括数据准备、模型微调、验证微调后的模型。 一、参考文档&#xff1a; https://github.com/alibaba-damo-academy/FunASR/blob/main/examples/industrial_data_pretraining/paraformer_streaming/README_zh.md…

帝国CMS跳过选择会员类型直接注册方法

国CMS因允许多用户组注册&#xff0c;所以在注册页面会有一个选择注册用户组的界面&#xff0c;即使网站只用了一个用户组也会出现。 如果想去掉这个页面&#xff0c;直接进入注册页面&#xff0c;那么可按以下办法修改 打开 e/class/user.php 文件 查找&#xff1a; $chan…

TCP通信实现(服务端与客户端)

TCP通信实现&#xff08;服务器端) 案例 // TCP 通信的服务器端#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h>int main() {// 1.创建socket(用于监听的套接字)int lfd socket(AF_…

Linux_CentOS7.6防火墙常用相关命令汇总(防火墙关闭与开启)

CentOS 防火墙是操作系统自带的一款网络安全工具&#xff0c;可以用于限制和管理网络流量。以下是一些 CentOS 防火墙相关的常用命令&#xff08;不常用的&#xff0c;太多了就不写了&#xff09;&#xff1a; 在centos7中用firewalld代替以前的iptables 一&#xff0c;查看防…

人脑与电脑有什么不同

人脑和计算机都是信息处理装置&#xff0c;目前人类对自己大脑的原理了解甚少。然而它们仍然有一些相似之处&#xff0c;例如都需要记忆来存储信息。人脑有些功能特性是计算机所没有的&#xff0c;且很难模拟出来&#xff0c;所以两者在原理上并不完全相同。 计算机由人类发明&…

美军配备人工智能武器的机器狗引发伦理争议

近日&#xff0c;美国海军陆战队特种作战司令部&#xff08;MARSOC&#xff09;的一项测试引发了全球关注&#xff1a;他们正在评估一种由“幽灵机器人”公司研发的最新型机器狗&#xff0c;并考虑为其配备“玛瑙工业”公司提供的武器系统。这一消息犹如在平静的湖面投下一颗石…

weixin209基于微信小程序投票评选系统的设计与实现ssm-手把手调试

weixin209基于微信小程序投票评选系统的设计与实现ssm-手把手调试 weixin209基于微信小程序投票评选系统的设计与实现ssm-手把手调试

Python 之 日志巡检脚本

脚本说明 使用Paramiko库进行SSH连接的自动化脚本&#xff0c;用于检查、配置和排除设备故障。说明如下&#xff1a; 导入所需的库&#xff1a;paramiko、json、logging和concurrent.futures。定义配置文件路径&#xff08;devices.json&#xff09;和日志文件路径&#xff0…

阿里巴巴最新研究突破:自我演化大模型,打破性能天花板

获取本文论文原文PDF&#xff0c;请在公众号【AI论文解读】留言&#xff1a;论文解读AI论文解读 原创作者 | 柏企 引言&#xff1a;自我进化的新篇章 在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的发展正迎来一场革命性的变革。传统的训练模式依赖…

006、API_单线程

Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库 服务&#xff0c;本节首先通过多个客户端命令调用的例子说明Redis单线程命令处理 机制&#xff0c;接着分析Redis单线程模型为什么性能如此之高&#xff0c;最终给出为什么理 解单线程模型是使用和运维Redis的…

一战成电失败,二战上岸复旦!

这个系列会邀请往届学长学姐进行经验分享~ 本篇是复旦大学957来自专业课134分上岸同学的经验分享。 经验分享 大家好&#xff0c;大伙能点进这个帖子倍感荣幸。 先说一下个人情况吧&#xff0c;鼠鼠本科武汉大学物院&#xff0c;总共四年混了四年&#xff0c;绩点低&#x…

数据赋能(99)——概念:数据服务、数据产品

此文为本人学习与提高能力的笔记。 数据服务&#xff08;数据服务目录&#xff09;和数据产品是两个不同的概念&#xff0c;尽管它们都涉及到数据的利用和应用&#xff0c;但在定义和功能上存在一些差异。 在探讨“数据服务”、“数据产品”术语时&#xff0c;我们将从定义的…

WordPress国外超人气主题Vikinger汉化版

WordPress国外超人气主题Vikinger汉化版 前言效果图安装教程领取主题下期更新预报 前言 我们在上一个教程已经学过如何安装WordPress&#xff0c;所以现在不用多说。 效果图 安装教程 下载后先本地解压&#xff0c;找到vikinger.zip文件&#xff0c;上传安装并启用主题。 访…