计算机网络之HTTP代理服务器
2016-05-22 22:22
417 查看
一、计算机网络之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; }
重点是
c++ 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; }
重复一遍,源码地址
相关文章推荐
- HTTP长连接与短连接
- 网络I/O模型
- 网络端口号的划分
- 《网络攻防技术与实践》第十二周学习总结
- APP开发实战36-HTTPS的握手方式
- APP开发实战35-HTTPS的运作方式
- CAS使用HTTPs功能遇到的问题
- APP开发实战34-HTTPS介绍
- C# Socket 模拟http服务器帮助类
- APP开发实战33-HTTP 消息长度的总结
- Android自定义控件之入门篇---整理网络上的资源
- APP开发实战32-HTTP Keep-Alive模式介绍
- Android中使用HttpConnection发送中文到服务器端乱码解决办法
- 初学socket网络编程
- 基于TCP协议下的socket编程
- Java---网络编程-C/S-B/S基础知识
- Java---基于TCP协议的相互即时通讯小程序
- HTML---网络编程基础知识(2)
- HTTP协议
- Python Socket 网络编程