编程实战:自己编写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_VC
				struct 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;
		}

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

六、处理特定功能

        待续


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

需求分析部分图形工具

描述复杂的事物时,图形远比文字叙述优越得多,它形象直观容易理解。前面已经介绍了用于建立功能模型的数据流图、用于建立数据模型的实体-联系图和用于建立行为模型的状态图,本节再简要地介绍在需求分析阶段可能用到的另外3种图形工具。 1 层次方框图 层次方框图用树形结…

LaTex 模板 - 东北师范大学申研申博推荐信

文章目录 NENU-Letter-Template项目地址示例特性项目结构如何使用main.texletterContent.tex 如何编译方式 1 &#xff1a;在线编译方式 2 &#xff1a;本地编译 参考 NENU-Letter-Template NENU’s recommendation letter template. 东北师范大学推荐信模板 项目地址 GitHu…

Spring框架学习笔记(五):JdbcTemplate 和 声明式事务

基本介绍&#xff1a;通过 Spring 框架可以配置数据源&#xff0c;从而完成对数据表的操作。JdbcTemplate 是 Spring 提供的访问数据库的技术。将 JDBC 的常用操作封装为模板方法 1 JdbcTemplate 使用前需进行如下配置 1.1 在maven项目的pom文件加入以下依赖 <dependencies…

Windows安装并启动Redis服务端(zip包)

一、Redis简介 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的基于内存的 Key - Value结构的数据库&#xff0c;遵守 BSD 协议&#xff0c;它提供了一个高性能的键值&#xff08;key-value&#xff09;存储系统&#xff0c;常用于缓存、消息队列、会话存储…

c# sqlite使用

安装包 使用 const string strconn "Data Sourcedata.db"; using (SQLiteConnection conn new SQLiteConnection(strconn)) {conn.Open();var cmd conn.CreateCommand();cmd.CommandText "select 1";var obj cmd.ExecuteScalar();MessageBox.Show(ob…

Python小游戏——打砖块

文章目录 打砖块游戏项目介绍及实现项目介绍环境配置代码设计思路代码设计详细过程 难点分析源代码代码效果 打砖块游戏项目介绍及实现 项目介绍 打砖块游戏是一款经典的街机游戏&#xff0c;通过控制挡板来反弹小球打碎屏幕上的砖块。该项目使用Python语言和Pygame库进行实现…

鲁教版七年级数学上册-笔记

文章目录 第一章 三角形1 认识三角形2 图形的全等3 探索三角形全等的条件4 三角形的尺规作图5 利用三角形全等测距离 第二章 轴对称1 轴对称现象2 探索轴对称的性质4 利用轴对称进行设计 第三章 勾股定理1 探索勾股定理2 一定是直角三角形吗3 勾股定理的应用举例 第四章 实数1 …

【实际项目精选源码】ehr人力资源管理系统实现案例(java,vue)

一、项目介绍 一款全源码可二开&#xff0c;可基于云部署、私有部署的企业级数字化人力资源管理系统&#xff0c;涵盖了招聘、人事、考勤、绩效、社保、酬薪六大模块&#xff0c;解决了从人事招聘到酬薪计算的全周期人力资源管理&#xff0c;符合当下大中小型企业组织架构管理运…

心链2---前端开发(整合路由,搜索页面,用户信息页开发)

心链——伙伴匹配系统 接口调试 说书人&#x1f4d6;&#xff1a;上回书说到用了两种方法查询标签1.SQL查询&#xff0c;2.内存查询&#xff1b;两种查询效率是部分上下&#xff0c;打的是难解难分&#xff0c;是时大地皴裂&#xff0c;天色聚变&#xff0c;老祖斟酌再三最后决…

(十一)统计学基础练习题五(50道选择题)

本文整理了统计学基础知识相关的练习题&#xff0c;共50道&#xff0c;适用于想巩固统计学基础或备考的同学。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-统计学二&#xff09;。序号之前的题请看往期文章。 201&#xff09; 202&#xff09; 203&#xff09; 2…

指纹识别概念解析

目录 1. 指纹是物证之首 1.1 起源于中国 1.2 发展于欧洲 1.3 流行于全世界 2. 指纹图像 3. 指纹特征 4. 指纹注册 5. 指纹验证 6. 指纹辨识 1. 指纹是物证之首 指纹识别技术起源于中国、发展于欧洲、流行于全世界。自20世纪以来&#xff0c;指纹在侦破刑事案件、解决诉…

《图解支付系统设计与实现》电子书_V20240525

相较于上次公开发布的V20240503版本&#xff0c;变更内容如下&#xff1a; 根据掘金网友zz67373&#xff08;李浩铭&#xff09;的勘误建议&#xff0c;优化了部分描述。增加&#xff1a;金额处理规范&#xff0c;低代码报文网关实现完整代码&#xff0c;分布式流控等内容。扩…

CSS语法介绍

文章目录 前言一、CSS引入方式1.行内操作2.内部操作3.外部操作 二、常用选择器1.标签选择器2.类选择器3.id选择器4.群组选择器5.后代选择器 三、字体常用设置1.字体类型2.字体大小3.字体样式4.字体粗细 四、div盒子模型1.盒子边框2.外边距3.内边距4.浮动 综合实战案例 前言 以…

每日一题 求和

1.题目解析 求和_牛客题霸_牛客网 (nowcoder.com) 这一题&#xff0c;主要描述的就是求满足和为m的子序列&#xff0c;对与子序列的问题可以使用决策树。 2.思路分析 决策树如下图所示: 递归结束条件&#xff1a; 当当前和 sum 等于目标和 m 时&#xff0c;说明找到了一个满…

Java+Swing+Mysql实现飞机订票系统

一、系统介绍 1.开发环境 操作系统&#xff1a;Win10 开发工具 &#xff1a;Eclipse2021 JDK版本&#xff1a;jdk1.8 数据库&#xff1a;Mysql8.0 2.技术选型 JavaSwingMysql 3.功能模块 4.数据库设计 1.用户表&#xff08;users&#xff09; 字段名称 类型 记录内容…

aws 接入awsIOT平台的证书签发逻辑

参考资料 https://aws.amazon.com/cn/blogs/china/certification-vending-machine-intelligent-device-access-aws-iot-platform-solution/ IoT 设备与 AWS IoT Core 的 MQTT 通信使用基于证书的 TLS 1.2双向认证体系。所谓的双向认证&#xff0c;即意味着 IoT 设备端需安装 …

Redis 性能管理

一、Redis 性能管理 #查看Redis内存使用 172.168.1.11:6379> info memory 1. 内存碎片率 操作系统分配的内存值 used_memory_rss 除以 Redis 使用的内存总量值 used_memory 计算得出。内存值 used_memory_rss 表示该进程所占物理内存的大小&#xff0c;即为操作系统分配给…

谈谈你对 vue 的理解 ?

1.谈谈你对 vue 的理解 ? 官方: Vue是一套用于构建用户界面的渐进式框架,Vue 的核心库只关注视图层 2. 声明式框架 Vue 的核心特点,用起来简单。那我们就有必要知道命令式和声明式的区别! 早在 JQ 的时代编写的代码都是命令式的,命令式框架重要特点就是关注过程 声明…

13个PyTorch深度学习案例简介

本文整理《PyTorch深度学习与企业级项目实战》中项目案例所使用的模型&#xff0c;方便大家在学习、研究深度学习过程中做训练使用&#xff0c;这些案例也适合作为课程论文、毕业论文的素材&#xff0c;值得收藏和推荐。 第6章 迁移学习花朵识别项目实战 花朵数据集 ResNet…

Unity射击游戏开发教程:(26)创建绕圈跑的效果

unity游戏 在本文中,我将介绍如何为敌人创建圆周运动。gif 中显示的确切行为是敌人沿着屏幕向下移动,直到到达某个点,一旦到达该点,它就会绕圈移动。