编写HTTP协议代理的一些知识(源码)

早期上网经常需要使用代理服务器,现在用的比较少了,大家更耳熟能详的反而是"反向代理"如Nginx。

代理服务器一般用作局域网上网,而反向代理则是把来自互联网的连接转发到局域网上,作用刚好相反。

HTTP协议自身就带有对代理服务器的支持。HTTP协议目前主要有多个版本,0.9太简单,基本不见了,1.0只支持一个连接一个请求,1.1则支持长连接,2.0极大复杂化了传输过程,支持多路复用。协议版本这么多,但是代理服务器作为中间商,可以选择一个较低的版本,用户的客户端和服务器一般都有能力适应多个版本。

代理服务器可以选择比较简单的HTTP1.0版本,一个连接就是一个请求,只需要在连接建立之后做处理,处理完请求就是简单的数据转发了。

HTTP1.0协议对代理服务器的支持基本就是两点:

  1. 请求行对使用绝对URL
  2. 专用于代理服务器的Proxy-XXXX头标

代理服务器要做的事情是:

  1. 取出请求行的服务器域名和端口并擦除(擦除后与直接请求的请求行相同)
  2. 将协议版本降低为自己支持的版本
  3. 根据proxy-XXXX头标处理并擦除
  4. 像直接请求一样访问服务器
  5. 转发数据给用户

前面说的"擦除"是把后面的数据前移而不是设置为空格,设置为空格并不符合HTTP协议,服务器一般不能理解。

原则上代理服务器可以支持客户端和服务器是不同的协议版本,比如客户端是1.0而服务器是1.1,这将极大地影响程序复杂度。

虽然HTTP的BODY与代理服务器处理无关,只需要接受完头部就可以处理,但是最好整个请求完整发送,因为有些服务器不能处理请求头和BODY分开的情形。

代理服务器认证

代理服务器通过Proxy-XXXX头标进行认证,这个认证是代理服务器的认证而不是用户要访问的服务器的认证。代理服务器认证完后就应该删除这些头标,因为这些头标对目标服务器毫无意义。

隧道请求CONNECT

CONNECT是个不常用的头标,专门用于代理。代理服务器取得目标服务器后直接连上去就可以了,然后就是双向转发数据。

代码示例

下面的代码就是一个HTTP1.0代理的协议处理部分的代码,没有认证(因为用的是IP地址认证,在进入这个代码之前就已经处理过了):

cpp 复制代码
//servicethreadhttp.cpp

#include "stdafx.h"
#include "mystd.h"
#include "Proxy.h"
#include "httpresp.h"

extern CProxyApp theApp;

//HTTP协议处理线程
DWORD ServiceThreadHttp(LPDWORD lpdwParam)
{
//--线程参数处理------------------------
	int cdindex;//连接数据索引
	struct ServiceData * servicedata;

	cdindex=((struct ThreadUserData *)lpdwParam)->index;
	servicedata=((struct ThreadUserData *)lpdwParam)->servicedata;
//--------------------------------------
	struct ConnectionData * cd;
	struct LogStruct * logs;

	cd=&servicedata->connectiondataarray.pconnectiondata[cdindex];
	if(-1!=cd->log)
	{
		logs=servicedata->memlogfile.logstruct+cd->log;
	}
	else
	{
		logs=NULL;
	}
//----------------------------------------
	struct ConfigInfo * pci;
	pci=&servicedata->serviceconfigfiledata.configarray[cd->serviceindex];

	int headlen;
	int port;
	char host[256];
	char uri[256];
	unsigned long addr;
	SOCKADDR_IN sa;
	BOOL isTunnel=FALSE;//是否是隧道请求
	char tunnelresponse[]="HTTP/1.0 200 Connection established\x0d\x0a"
		"Proxy-agent: FreeProxy 1.0\x0d\x0a\x0d\x0a";

	//退出?
	if(CONNECTIONDATA_CMD_QUIT==cd->cmd)
	{
		closesocket(cd->sdc.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-1;
	}

	//接收请求
	cd->sdc.bufcount=RecvHttpRequest(cd->sdc.s,cd->sdc.buf,BUFFERSIZE,
		&cd->cmd,&headlen,pci->islimitpost,1000*pci->maxpost);
	if(0>cd->sdc.bufcount)
	{
		//DebugMessage("RecvHttpRequest失败");
		closesocket(cd->sdc.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-1;
	}

	//分析请求
/*	char tracertfile[256];
	if(-1!=mymemindex(cd->sdc.buf,cd->sdc.bufcount,"says=%2Fnick",strlen("says=%2Fnick")))
	{
		strcpy(tracertfile,"tracert_");
		itoa(cdindex,tracertfile+strlen(tracertfile),10);
		if(-1!=cd->log)WriteTracertFile(tracertfile,logs->username,strlen(logs->username));
		WriteTracertFile(tracertfile,cd->sdc.buf,cd->sdc.bufcount);
	}*/
	if(0>GetHttpURL(cd->sdc.buf,&cd->sdc.bufcount,headlen+4,host,256,&port,uri,256))
	{
		if(pci->isenableconnect && 0<=GetTunnelURL(cd->sdc.buf,&cd->sdc.bufcount,headlen+4,host,256,&port,uri,256))
		{//是隧道请求
			isTunnel=TRUE;
			if(-1!=cd->log)
			{
				strcpy(logs->domainname,host);
			}
		}
		else
		{
			send(cd->sdc.s,httpresp400,strlen(httpresp400),0);
			closesocket(cd->sdc.s);
			if(-1!=cd->log)
			{
				logs->state=LOGSTRUCT_STATE_NOUSE;
			}
			cd->state=CONNECTION_NOUSE;
			return (DWORD)-2;
		}
	}
	else
	{
		if(-1!=cd->log)
		{
			strcpy(logs->domainname,host);
		}
	}
	ClearProxyInfo(cd->sdc.buf,&cd->sdc.bufcount);

	//检查目标许可
	if(IsForbidden(&theApp.bandata,host,uri))
	{
		send(cd->sdc.s,httpresp403,strlen(httpresp403),0);
		closesocket(cd->sdc.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-1;
	}

	//退出?
	if(CONNECTIONDATA_CMD_QUIT==cd->cmd)
	{
		closesocket(cd->sdc.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-1;
	}

	//记录日志,计时开始
	if(-1!=cd->log)
	{
		time(&logs->timestart);
	}

	//域名解析
	if(1!=GetAddrByHost(addr,host))
	{
		send(cd->sdc.s,httpresp600,strlen(httpresp600),0);
		closesocket(cd->sdc.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-3;
	}

	memcpy(&(sa.sin_addr.S_un.S_addr),&addr,4);
	sa.sin_family=AF_INET;
	sa.sin_port=htons((unsigned short)port);

	//建立SOCKET
	if(INVALID_SOCKET==(cd->sdr.s=socket(AF_INET,SOCK_STREAM,0)))
	{
		send(cd->sdc.s,httpresp601,strlen(httpresp601),0);
		closesocket(cd->sdc.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-4;
	}

	//退出?
	if(CONNECTIONDATA_CMD_QUIT==cd->cmd)
	{
		closesocket(cd->sdc.s);
		closesocket(cd->sdr.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-1;
	}
	
	//连接
	if(SOCKET_ERROR==connect(cd->sdr.s,(struct sockaddr *)&sa,sizeof(sa)))
	{
		send(cd->sdc.s,httpresp602,strlen(httpresp602),0);
		closesocket(cd->sdc.s);
		closesocket(cd->sdr.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-5;
	}
	else
	{
		if(-1!=cd->log)
		{
			strcpy(logs->domainname,uri);
		}
	}

	//退出?
	if(CONNECTIONDATA_CMD_QUIT==cd->cmd)
	{
		closesocket(cd->sdc.s);
		closesocket(cd->sdr.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-1;
	}
	
	//发送请求
	if(isTunnel)
	{
		if(SOCKET_ERROR==send(cd->sdc.s,tunnelresponse,strlen(tunnelresponse),0))
		{
			send(cd->sdc.s,httpresp603,strlen(httpresp603),0);
			closesocket(cd->sdc.s);
			closesocket(cd->sdr.s);
			if(-1!=cd->log)
			{
				logs->state=LOGSTRUCT_STATE_NOUSE;
			}
			cd->state=CONNECTION_NOUSE;
			return (DWORD)-6;
		}
	}
	if(SOCKET_ERROR==send(cd->sdr.s,cd->sdc.buf,cd->sdc.bufcount,0))
	{
		send(cd->sdc.s,httpresp603,strlen(httpresp603),0);
		closesocket(cd->sdc.s);
		closesocket(cd->sdr.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-6;
	}
	//记录字节数
	if(-1!=cd->log)
	{
		logs->bytecount+=cd->sdc.bufcount;
	}
	///
	TraceData(servicedata->isDataTrace,&servicedata->memlogfile.logdatatrace[cd->log].dc,cd->sdc.buf,cd->sdc.bufcount);

	//退出?
	if(CONNECTIONDATA_CMD_QUIT==cd->cmd)
	{
		closesocket(cd->sdc.s);
		closesocket(cd->sdr.s);
		if(-1!=cd->log)
		{
			logs->state=LOGSTRUCT_STATE_NOUSE;
		}
		cd->state=CONNECTION_NOUSE;
		return (DWORD)-1;
	}
	
	//接收数据并发给客户
	TransData(cd->sdr.s,cd->sdc.s,cd->sdr.buf,BUFFERSIZE,&cd->cmd,&cd->sdr.bufcount,servicedata,cd);
	//记录字节数
	if(-1!=cd->log)
	{
		logs->bytecount+=cd->sdr.bufcount;
	}

	closesocket(cd->sdc.s);
	closesocket(cd->sdr.s);
	if(-1!=cd->log)
	{
		time(&logs->timeend);
		logs->state=LOGSTRUCT_STATE_USED;
	}
	cd->state=CONNECTION_NOUSE;
	return 1;
}

//接收HTTP请求(如果出错,不执行closesocket())
int RecvHttpRequest(SOCKET s,char * buf,int buflen,int * cmd,int* headlen,BOOL islimitpost,int maxpost)
{
	maxpost+=1;

	const char CRLF[]="\x0d\x0a";
	const char CRLFCRLF[]="\x0d\x0a\x0d\x0a";
	const char CONTENTLENGTH[]="Content-Length:";
	
	int recvcount=0;
	int temp;
	int recvall=0;

	BOOL tempbool;
	struct timeval timeout;
	timeout.tv_sec=0;
	timeout.tv_usec=100000;


	for(;1;)
	{
		//退出?
		if(CONNECTIONDATA_CMD_QUIT==*cmd)
		{
			return -1;
		}
		if(1!=IsSocketReadReady(s,timeout,tempbool))
		{
			return -2;
		}
		if(tempbool)
		{
			recvcount=recv(s,buf+recvall,buflen-recvall,0);
		}
		else
		{
			continue;
		}
		if(SOCKET_ERROR==recvcount)
		{
			return -3;
		}
		else if(0==recvcount)
		{
			return -4;
		}
		recvall+=recvcount;
		//在使用后面代码段时使用CRLFCRLF,4,否则使用CRLF,2
		temp=mymemindex(buf,recvall,(char*)CRLFCRLF,4);
		if(-1!=temp)
		{
			*headlen=temp;
			break;
		}
	}

	if(islimitpost && -1!=(temp=mymemindex(buf,*headlen,(char*)CONTENTLENGTH,15)))
	{
		long i;
		char len[10];
		
		if(-1==(i=mymemindex(buf+temp,buflen-temp,(char*)CRLF,2)))
		{
			return -5;
		}
		i-=strlen(CONTENTLENGTH);
		if(i>9)
		{
			return -6;
		}
		memcpy(len,buf+temp+strlen(CONTENTLENGTH),i);
		len[i]='\0';
		i=atoi(len);
		if(i>maxpost)
		{
			return -7;
		}
	}

	return recvall;
}

//取得URL
int GetHttpURL(char* buf,int * buflenall,int buflen,char * host,int hostbuflen,int * port,char * uri,int uribuflen)
{
	const char CRLF[]="\x0d\x0a";

	int urlstart,urlend;
	int hoststart,hostend,hostlen;
	int portstart,portend,portlen;
	int pos;
	char str[10];

	urlend=mymemindex(buf,buflen,(char*)CRLF,2);
	if(-1==(urlstart=mymemindex(buf,urlend,"http://",7)))
	{
		return -2;	
	}
	if(urlend-urlstart>=uribuflen)
	{
		memcpy(uri,buf+urlstart,uribuflen-1);
		uri[uribuflen-1]='\0';
	}
	else
	{
		memcpy(uri,buf+urlstart,urlend-urlstart);
		uri[urlend-urlstart]='\0';
	}

	//得到主机名起始位置
	hoststart=urlstart+7;
	
	if(-1==(pos=mymemindex(buf+hoststart,urlend-hoststart,"/",1)))
	{
		return -3;
	}
	portend=pos+hoststart;
	pos=mymemindex(buf+hoststart,portend-hoststart,":",1);
	if(-1!=pos)//有端口
	{
		portstart=pos+hoststart+1;//得到端口起始位置
		hostend=pos+hoststart;
		portlen=portend-portstart;
		memcpy(str,buf+portstart,portlen);
		str[portlen]='\0';
		if(0==portlen) *port=80;//若端口长度为零,实际上无端口
		{
			if(0==(*port=atoi(str)))
				return -4;
		}
	}
	else//无端口
	{
		*port=80;
		hostend=portend;
	}
	hostlen=hostend-hoststart;
	if(hostlen>=hostbuflen)
		return -5;
	memcpy(host,buf+hoststart,hostlen);
	host[hostlen]='\0';
	
	//HTTP请求处理
	long i;

	//降版本1.1为1.0
	if('1'==buf[urlend-1])
	{
		buf[urlend-1]='0';
	}
	//擦去URL
	i=portend-urlstart;
	memmove(buf+urlstart,buf+portend,*buflenall-portend);
	*buflenall-=i;

	return hostlen;
}

//取得隧道请求
int GetTunnelURL(char* buf,int * buflenall,int buflen,char * host,int hostbuflen,int * port,char * uri,int uribuflen)
{
	const char CRLF[]="\x0d\x0a";

	int urlstart,urlend;
	int hoststart,hostend,hostlen;
	int portstart,portend,portlen;
	int pos;
	char str[10];

	urlend=mymemindex(buf,buflen,(char*)CRLF,2);
	if(buflen<8 || 0!=memcmp(buf,"CONNECT",7))
		return -2;
	if(' '!=buf[7])
		return -2;
	for(urlstart=8;urlstart<buflen;urlstart++)
	{
		if(' '!=buf[urlstart])
			break;
	}

	if(urlend>=uribuflen)
	{
		memcpy(uri,buf,uribuflen-1);
		uri[uribuflen-1]='\0';
	}
	else
	{
		memcpy(uri,buf,urlend);
		uri[urlend]='\0';
	}

	//得到主机名起始位置
	hoststart=urlstart;
	
	if(-1==(pos=mymemindex(buf+hoststart,urlend-hoststart,"/",1)))
	{
		return -3;
	}
	portend=pos+hoststart;
	pos=mymemindex(buf+hoststart,portend-hoststart,":",1);
	if(-1!=pos)//有端口
	{
		portstart=pos+hoststart+1;//得到端口起始位置
		hostend=pos+hoststart;
		portlen=portend-portstart;
		memcpy(str,buf+portstart,portlen);
		str[portlen]='\0';
		if(0==portlen) *port=80;//若端口长度为零,实际上无端口
		{
			if(0==(*port=atoi(str)))
				return -4;
		}
	}
	else//无端口
	{
		*port=80;
		hostend=portend;
	}
	hostlen=hostend-hoststart;
	if(hostlen>=hostbuflen)
		return -5;
	memcpy(host,buf+hoststart,hostlen);
	host[hostlen]='\0';
	
	//HTTP请求处理
	
	*buflenall=0;

	return hostlen;
}

//清除代理信息
int ClearProxyInfo(char * buf,int * buflenall)
{
	const char PROXYCONNECTION[]="Proxy-Connection";
	const char CRLF[]="\x0d\x0a";
	int i,j;

	if(2>(i=mymemindex(buf,*buflenall,PROXYCONNECTION,strlen(PROXYCONNECTION))))return 1;//前面至少应有一个CRLF
	if(0!=memcmp(buf+i-2,CRLF,2))return 1;
	if(-1==(j=mymemindex(buf+i+strlen(PROXYCONNECTION),(*buflenall)-i-strlen(PROXYCONNECTION),CRLF,2)))
	{
		j=(*buflenall)-i-strlen(PROXYCONNECTION);
	}
	//擦去代理信息
	memmove(buf+i-2,buf+i+strlen(PROXYCONNECTION)+j,(*buflenall)-(i+strlen(PROXYCONNECTION)+j));
	*buflenall-=2+strlen(PROXYCONNECTION)+j;
	return 1;
}

主要就是这么几件事:取出目标地址和端口,擦除目标信息,降低版本为1.0,擦除Proxy-XXXX头标,连接目标,双向转发数据。

这个代码是从实际项目中截取出来的。

(这里是结束)

相关推荐
l1x1n035 分钟前
网络安全概述:从认知到实践
网络
鄃鳕35 分钟前
HTTP【网络】
网络·网络协议·http
蜡笔小新星1 小时前
Python Kivy库学习路线
开发语言·网络·经验分享·python·学习
读心悦2 小时前
如何在 Axios 中封装事件中心EventEmitter
javascript·http
小白爱电脑2 小时前
WIFI网速不够是不是光猫的“路由模式”和“桥接模式”配置错了?
网络·智能路由器·桥接模式
CXDNW3 小时前
【网络篇】计算机网络——应用层详述(笔记)
服务器·笔记·计算机网络·http·web·cdn·dns
qxqxa3 小时前
cfg80211是怎么配置无线设备的AP的?
网络·驱动开发
秋夫人5 小时前
http cache-control
网络·网络协议·http
叶北辰CHINA5 小时前
nginx反向代理,负载均衡,HTTP配置简述(说人话)
linux·运维·nginx·http·云原生·https·负载均衡
不灭锦鲤6 小时前
ssrf学习(ctfhub靶场)
网络·学习·安全