一、计算机网络之HTTP代理服务器
注:参考哈工大计算机网络指导书
源码下载地址
实验的内容:
1.实现基本的HTTP代理服务器,可以在指定端口接收客户端http的请求并且根据其中个URL地址访问该地址所指向的HTTP服务器,接收服务器的响应报文,并将响应报文转发给对应的客户进行浏览。
2.设计并实现一个支持cache功能的HTTP代理服务器,要求能缓存原服务器响应的对象,并能够通过修改请求报文(添加 if-modified-since头行),向原服务器确认缓存对象是否是最新版本
3.扩展 HTTP 代理服务器,支持如下功能:
a、网站过滤:允许/不允许访问某些网站
b、用户过滤:支持/不支持某些用户访问外部网站
c、网站引导:将用户对某个网站的访问引导至一个模拟网站(钓鱼)
代理服务器,俗称 “翻墙软件” ,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。如图所示,为普通 Web 应用通信方式与采用代理服务器的通信方式的对比。
实现的是多用户代理服务器,实现线程的并行执行。 首先,代理服务器创建 HTTP 代理服务的 TCP 主套接字,通过该主套接字监听等待客户端的连接请求。当客户端连接之后,创建一个子线程,由子线程执行上述一对一的代理过程,服务结束之后子线程终止。与此同时,主线程继续接受下一个客户的代理服务。
实验环境配置
打开IE(windows)-> 工具 -> Internet选项 -> 连接 -> 局域网设置 -> 代理服务器
如下设置地址和端口号(端口号和程序中保持一致即可(最好使用较大的,低端口一般被占用))
基本的HTTP代理服务器,实现简单,不用处理直接转发接收到目的服务器的报文即可。因此不作介绍,主要讲解实现的扩展功能。
1网站过滤的实现
基本思想:通过解析客户端请求的buffer,获取请求的URL,然后设置关键字,再次使用c++中的一个函数strstr,判断关键字是否在其子串中
bool ForbiddenToConnect(char *httpheader)
{
char * forbiddernUrl = ".edu.cn"; //屏蔽的含有关键字的网址
if (strstr(httpheader, forbiddernUrl)!=NULL) //是否含有屏蔽的关键字
{
return false;
}
else return true;
}
2用户过滤的实现
基本思想:通过客户端请求,获取请求用户的IP,然后在程序运行时,设置一个IP禁止表,在IP禁止表中存入一些IP地址,在请求之后就去查询是否在IP禁止表中,如果是,则关闭socket即可,等待下一次的请求。
char client_IP[16];
//设置禁用IP,可以在此添加更多
memcpy(ForbiddenIP[IPnum++], "127.0.0.1", 16);
//设置访问哪些网站会被重定向到钓鱼网站
memcpy(fishUrl[fishUrlnum++], "http://www.asus.com.cn/",23);
memcpy(fishUrl[fishUrlnum++],"http://pku.edu.cn/",18);
while (true) {
int ff;
ff = sizeof(acceptAddr);
acceptSocket = accept(ProxyServer, (SOCKADDR*)&acceptAddr,&(ff));
printf("获取用户IP地址:%s\n",inet_ntoa(acceptAddr.sin_addr));
memcpy(client_IP, inet_ntoa(acceptAddr.sin_addr),16);//用户IP保存
//禁用用户IP访问
if (UserIsForbidden(client_IP))
{
printf("IP被禁用\n");
closesocket(acceptSocket);
//system("pause");
//break;
}
重点是
acceptSocket = accept(ProxyServer, (SOCKADDR*)&acceptAddr,&(ff));
获取请求IP的方法。如果最后一个参数设置的是0,则返回的IP一直是204.204.204.204,显然不是请求的IP地址,因此需要作上述处理,先获取acceptAddr的大小,然后其地址作为accept函数的地址
3钓鱼网站的实现(钓鱼网站)
基本思想:设置一个钓鱼网站的URL表,如果请求的URL在此URL表中,则丢弃其的请求报文,转成302报文发送,在302报文中定位到钓鱼网站的网址,从而实现访问请求网站自动重定向到钓鱼网站
//网站引导 访问pku.edu.cn asus.com 重定向到 today.hit.edu.cn
if (GotoFalseWebsite(httpHeader->url))
{ //302报文 对客户端的请求直接截断重定向到其他页面
//数据内容主要的就两条:
//"HTTP/1.1 302 Moved Temporarily"
//"Location: 路径"
//生成302报文,直接发送给客户端
char* pr;
int fishing_len = strlen("HTTP/1.1 302 Moved Temporarily\r\n"); //302报文
memcpy(FishBuffer, "HTTP/1.1 302 Moved Temporarily\r\n", fishing_len);
pr = FishBuffer + fishing_len;
//重定向到今日哈工大
fishing_len = strlen("Location: http://today.hit.edu.cn/\r\n\r\n"); //定向的网址
memcpy(pr, "Location: http://today.hit.edu.cn/\r\n\r\n", fishing_len);
//将302报文返回给客户端
ret = send(((ProxyParam*)lpParameter)->clientSocket, FishBuffer, sizeof(FishBuffer), 0);
goto error;
}
4cache的实现
基本思想:cache作为代理服务器的缓存管理。首先,客户端请求报文,需要在cache中查询,如果命中,则进行访问目标服务器,缓存是否过期,未过期则直接将缓存的报文转发给客户端,过期则将目标服务器返回的报文进行更新cache;未命中,则直接请求目标服务器,将目标服务器的返回报文更新到cache中,并发送给客户端。
//cache 实现
if (Have_cache) //请求有缓存
{
char cached_buffer[MAXSIZE];//缓存buffer
ZeroMemory(cached_buffer, MAXSIZE);
memcpy(cached_buffer, Buffer, recvSize);
//构造缓存的报文头
char* pr = cached_buffer + recvSize;
memcpy(pr, "If-modified-since: ", 19);
pr += 19;
int lenth = strlen(Cache[last_cache].last_modified);
memcpy(pr, Cache[last_cache].last_modified, lenth);
pr += lenth;
//将客户端发送的 HTTP 数据报文直接转发给目标服务器
ret = send(((ProxyParam *)lpParameter)->serverSocket, cached_buffer, strlen(cached_buffer) + 1, 0);
//等待目标服务器返回数据
recvSize = recv(((ProxyParam *)lpParameter)->serverSocket, cached_buffer, MAXSIZE, 0);
if (recvSize <= 0) {
goto error;
}
//解析包含缓存信息的HTTP报文头
CacheBuffer = new char[recvSize + 1];
ZeroMemory(CacheBuffer, recvSize + 1);
memcpy(CacheBuffer, cached_buffer, recvSize);
char last_status[4];//用于记录主机返回的状态字
char last_modified[30];//用于记录记住返回的页面修改的时间
ParseCache(CacheBuffer, last_status, last_modified);
delete CacheBuffer;
//分析cache的状态字
if (strcmp(last_status, "304") == 0) {//没有被修改
printf("页面没有修改过,缓存的url为:%s\n", Cache[last_cache].url);
//将缓存的数据直接转发给客户端
ret = send(((ProxyParam*)lpParameter)->clientSocket, Cache[last_cache].buffer, sizeof(Cache[last_cache].buffer), 0);
if (ret != SOCKET_ERROR)
{
printf("来自缓存++++++++++++++\n");
}
}
else if (strcmp(last_status, "200") == 0) {//已经修改了
//修改缓存中的内容
printf("页面已经被修改过,缓存的url为:%s\n", Cache[last_cache].url);
memcpy(Cache[last_cache].buffer, cached_buffer, strlen(cached_buffer));//新的buffer 存在缓存中
memcpy(Cache[last_cache].last_modified, last_modified, strlen(last_modified)); //修改时间存入缓存
//将目标服务器返回的数据直接转发给客户端
ret = send(((ProxyParam*)lpParameter)->clientSocket, cached_buffer, sizeof(cached_buffer), 0);
if (ret != SOCKET_ERROR)
{
printf("来自修改过的缓存-----------\n");
}
}
}
else //没有缓存过这个页面
{
//将客户端发送的 HTTP 数据报文直接转发给目标服务器
ret = send(((ProxyParam *)lpParameter)->serverSocket, Buffer, strlen(Buffer) + 1, 0);
if (ret != SOCKET_ERROR)
{
printf("成功发送给目标服务器的报文buffer \n \n");
}
//等待目标服务器返回数据
recvSize = recv(((ProxyParam *)lpParameter)->serverSocket, Buffer, MAXSIZE, 0);
if (recvSize == SOCKET_ERROR)
{
printf("目标服务器未返回数据\n");
goto error;
}
//将目标服务器返回的数据直接转发给客户端
ret = send(((ProxyParam*)lpParameter)->clientSocket, Buffer, sizeof(Buffer), 0);
if (ret != SOCKET_ERROR)
{
printf("来自服务器************\n成功发送给客户端的报文(目标服务器返回的)buffer ret = %d \n", ret);
}
}
//错误处理
error:
printf("关闭套接字\n");
Sleep(200);
closesocket(((ProxyParam*)lpParameter)->clientSocket);
closesocket(((ProxyParam*)lpParameter)->serverSocket);
delete lpParameter;
_endthreadex(0);
cache的报文头解析
//*************************
//Method: ParseHttpHead0
//FullName: ParseHttpHead0
//Access: public
//Returns: void
//Qualifier: 解析 TCP 报文中的 HTTP 头部
//Parameter: char *buffer
//Parameter: HttpHeader *httpHeader
//*************************
int ParseHttpHead0(char *buffer, HttpHeader *httpHeader) {
int flag = 0;//用于表示Cache是否命中,命中为1,不命中为0
char *p;
char *ptr;
const char *delim = "\r\n";//回车换行符
p = strtok_s(buffer, delim, &ptr);
if (p[0] == 'G') { //GET方式
memcpy(httpHeader->method, "GET", 3);
memcpy(httpHeader->url, &p[4], strlen(p) - 13);
printf("url:%s\n", httpHeader->url);//url
for (int i = 0; i < 1024; i++) {//搜索cache,看当前访问的url是否已经存在cache中了
if (strcmp(Cache[i].url, httpHeader->url) == 0) {//说明url在cache中已经存在
flag = 1; //只要存在,flag标识变量置为1
break;
}
}
if (!flag && cached_number != 1023) {//说明url没有在cache且cache没有满, 把这个url直接存进去
memcpy(Cache[cached_number].url, &p[4], strlen(p) - 13);
last_cache = cached_number;
}
else if (!flag && cached_number == 1023) {//说明url没有在cache且cache满了
//为了简单,替换第一个
memcpy(Cache[0].url, &p[4], strlen(p) - 13);
last_cache = 0;
}
}
else if (p[0] == 'P') { //POST方式
memcpy(httpHeader->method, "POST", 4);
memcpy(httpHeader->url, &p[5], strlen(p) - 14);
for (int i = 0; i < 1024; i++) {
if (strcmp(Cache[i].url, httpHeader->url) == 0) { //同上
flag = 1;
break;
}
}
if (!flag && cached_number != 1023) {
memcpy(Cache[cached_number].url, &p[5], strlen(p) - 14);
last_cache = cached_number;
}
else if (!flag && cached_number == 1023) {
memcpy(Cache[0].url, &p[4], strlen(p) - 13);
last_cache = 0;
}
}
p = strtok_s(NULL, delim, &ptr);
while (p) {
switch (p[0]) {
case 'H'://HOST
memcpy(httpHeader->host, &p[6], strlen(p) - 6);
if (!flag && cached_number != 1023) {
memcpy(Cache[last_cache].host, &p[6], strlen(p) - 6);
cached_number++;
}
else if (!flag && cached_number == 1023) {
memcpy(Cache[last_cache].host, &p[6], strlen(p) - 6);
}
break;
case 'C'://Cookie
if (strlen(p) > 8) {
char header[8];
ZeroMemory(header, sizeof(header));
memcpy(header, p, 6);
if (!strcmp(header, "Cookie")) {
memcpy(httpHeader->cookie, &p[8], strlen(p) - 8);
}
}
break;
//case '':
default:
break;
}
p = strtok_s(NULL, delim, &ptr);
}
return flag;
}