HTTP 代理服务器的设计与实现

写在前面

花了好几天才把计算机网络的实验一搞定,在此记录一下这个实验的流程。

本实验的要求也是比较简单明了的:

(1) 设计并实现一个基本 HTTP 代理服务器。要求在指定端口(例如 8080)接收来自客户的 HTTP 请求并且根据其中的 URL 地址访问该地址 所指向的 HTTP 服务器(原服务器),接收 HTTP 服务器的响应报文,并 将响应报文转发给对应的客户进行浏览。
(2) 设计并实现一个支持 Cache 功能的 HTTP 代理服务器。要求能缓 存原服务器响应的对象,并能够通过修改请求报文(添加 if-modified-since 头行),向原服务器确认缓存对象是否是最新版本。(选作内容,加分项 目,可以当堂完成或课下完成)
(3) 扩展 HTTP 代理服务器,支持如下功能: (选作内容,加分项目, 可以当堂完成或课下完成)
a) 网站过滤:允许/不允许访问某些网站;
b) 用户过滤:支持/不支持某些用户访问外部网站;
c) 网站引导:将用户对某个网站的访问引导至一个模拟网站(钓 鱼)。

而且实验指导书上还给出了 200 来行的代码作为参考,可以说是很贴心了。但关键问题不是不知道原理,而是对 socket 编程是相当地陌生,还好代码大部分都能看懂,看不懂的查询一下也能搞定。本实验基本功能还是很好做的,主要就是 cache 的实现,我大部分时间就在搞这个,最后东拼西凑的,也算是搞出来了(虽然外部存储有时会乱码)。

实验代码在我 github 上,想参考的话,可以点击这里 ,欢迎来提各种建议,虽然我也不一定会去改,但还是希望这个代码会越来越好。

实验配置问题

首先要说的就是环境问题,由于我是使用的 CodeBlocks 进行编译的,因此,有时候会出现一些莫名的问题。这里简单介绍一下。

  1. 最大的问题就是静态链接问题,也就是这段代码 #pragma comment(lib,"Ws2_32.lib") ,在 VS 里可以很好地运行,但是在 CodeBlocks 中就失去作用了。这段代码也很简单,就是说要链接一个库,但是Codeblocks 使用的是 MingGW 来编译,MingGW不支持 #pragma comment(lib,"Ws2_32.lib") 的写法
    解决方法也是很简单,由于该命令是静态链接 Ws2_32.lib 库,因此可以在设置里,加上 -lws2_32 或 -lwsock32,具体怎么加,这里就不讲了。

  2. 第二个问题也是编译器的问题,由于版本问题,这里并不支持 int _tmain(int argc, _TCHAR* argv[]) 的写法,需要改成 int main(int argc, char* argv[]) 或者直接写成 int main() ,其实没有什么区别。具体原因,参考 main()和_tmain(int argc, _TCHAR* argv[]) 的详细区别c/c++ int _tmain(int argc, _TCHAR* argv[])

  3. 再就是 goto 语句的问题了,代码一直报 goto 语句的问题,不常用这个,我也是很懵啊,不过,还好前辈们有经验分享,具体原因可以参考这个: g++编译goto语句出现:[error:jump to label XXX],简单地说,就是你的 goto 语句之后不能再定义新的变量

  4. 再就是关于strtok_s的问题了,可以参考这篇 stackoverflow :关于strtok_s的问题,就是说,只要改成 strtok() 这个函数就可以了。再去掉最后一个参数,因为这个函数只需要俩参数。虽然这个函数并不安全,但它可以用啊。

  5. 大点的问题就这些,还有一些小的问题,比如 VS 里专用的 #include "stdafx.h" ,要去掉,可以参考 为什么要加#include “stdafx.h” ,剩下的,大都没有详细说的必要了

好了,bug 就算是修复完了,现在就可以正常访问网站了:

运行程序 --> 打开浏览器 --> 设置代理 --> 设置 127.0.0.1 和端口号 1240

这样就实现了一个基本的代理服务器,其实现在就已经完成第一个要求了。但你还不知道它的原理是什么,所以,下面看一下它的原理。

实现一个基本的代理服务器

在继续往下看之前,你最好对这几个函数有一定的了解:

  • bind() : 将一本地地址与一套接字捆绑,在 connect() 或 listen() 调用前使用
  • listen() : 监听套接字的连接请求,将套接字设为监听模式
  • connect() : 用于建立与指定 socket 的连接
  • accept() : 在一个套接字处,接受一个连接
  • send() : 发送数据(客户端向服务器发送请求,服务器端向客户端发送应答)
  • recv() : 接收数据

更详细的可以自行去百度查找,这里就不多介绍了。先来看代理服务器的原理

  1. 首先初始化一个套接字,利用 blind() 函数将该套接字与服务器 host 地址绑定,地址设为 “127.0.0.1”;同时,也要绑定端口号,这里就按照指导书上的要求设置为 “10240”。然后,利用 listen() 函数对该端口进行监听。
  2. 通过设置 accept() 函数,对每个到来的请求进行接收和相应,为了提供效率,对每个请求都创建一个新的线程来处理。
  3. 利用 recv() 和 send() 函数,接收来自客户端的 HTTP 请求,并通过这个代理服务器将该请求转发给服务器;同时,服务器也将获得的响应发给代理服务器,然后代理服务器再将该响应发送给客户端。在这里,代理服务器相当于一个中介,提供一个代理的服务,所有的请求和响应都经过它
  4. 处理完成后,等待 200 ms 后,关闭该线程,并清理缓存,然后继续接收并处理下一个请求。对于客户端而言,它只要将正常发送的请求发给代理服务器,就可以接收到对应的响应。

流程图可以表示为:

代理服务器的流程图

我个人觉得,这张流程图非常容易理解,基本上就是这段代码的逻辑,对于理解这段代码很有帮助。

扩展功能

对于这三个扩展功能,只要看懂了代码是如何解析并存储的 HTTP 头部信息,写这三个功能还是很简单的。不需要增加多少代码,只需进行 if 判断即可。

屏蔽网站

对请求过来的 HTTP 报文头部进行检测,提取出其中的访问地址 url ,检测其是否为要被屏蔽的网址,如果是,则直接跳转到代码中的 erro 部分,即关闭套接字,断开此次连接。代码片段如下:

if (strcmp (httpHeader->url, INVILID_WEBSITE) == 0) {
    printf("\n=====================================\n\n");
    printf("-------------Sorry!!!该网站已被屏蔽----------------\n");
    goto error;
}

屏蔽用户

更改套接字绑定的主机地址,这样的话,只要不是从该地址访问代理服务器的客户端,都会被该代理服务器屏蔽,部分代码如下:

//屏蔽用户
//ProxyServerAddr.sin_addr.S_un.S_addr = INADDR_ANY;
ProxyServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//仅本机用户可访问服务器
//ProxyServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.2");  //屏蔽用户

其实,就是更改套接字绑定的代理服务器的 IP 地址,这样的话,就会屏蔽掉从其他接口进行访问客户端,从而实现用户屏蔽。

钓鱼

检测请求过来的 HTTP 报文头部,如果发现访问的网址是要被钓鱼的网址,则将该网址引导到其他网站(钓鱼网址),通过更改 HTTP 头部字段的 url (访问网址)和 host 主机名来实现,部分代码如下:

if (strstr(httpHeader->url, FISHING_WEB_SRC) != NULL) {
	printf("\n=====================================\n\n");
	printf("-------------已从源网址:%s 转到 目的网址 :%s ----------------\n", FISHING_WEB_SRC,FISHING_WEB_DEST);
	memcpy(httpHeader->host, FISHING_WEB_HOST, strlen(FISHING_WEB_HOST) + 1);
    memcpy(httpHeader->url, FISHING_WEB_DEST, strlen(FISHING_WEB_DEST));
}

cache 实现

cache 可以说是这个实验最精髓的地方了,原理很简单,比较容易理解,但代码写起来还是比较长的,起码比前几个实现起来要复杂。我也是参考了很多前辈们的代码才写出来的,这里就简单介绍一下原理吧,代码自己去看我的实现吧,前面已经给了链接,这里再补充一下:实验一

基本原理

  1. 客户端第一次请求服务器中的数据时,代理服务器将该请求返回的响应缓存下来,存到本地的文件下。
  2. 当客户端第二次访问该数据时,代理服务器检查本地是否有该请求的响应,如果没有,则继续缓存;如果有,则向服务器发送一个请求,该请求需要增加 “If-Modified-Since” 字段,通过此字段,告知服务器缓存资源最后修改的时间(可以将 “Date” 字段进行解析),服务器通过对比最后修改时间来判断缓存是否过期,如果没过期,服务器返回状态码304,代理服务器直接将本地缓存发送给客户端;如果缓存过期,服务器返回状态码200,同时返回一个更新过的响应,代理服务器接收后,将该响应发回给客户端,并更新本地缓存

这一部分的代码虽然代码稍微多一些,但其实也没多少,而且原理很简单,不需要害怕,大胆去写就好了。

总结

这次实验对理解 HTTP 代理服务器还是很有帮助的,真正体会到了代理服务器的作用。虽然调试的时候会出来一堆莫名的 bug, 但是改好后的感觉还是相当不错的。

  • 24
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
HTTP代理服务器是一种充当客户端和服务器之间的中介的服务器。它接收客户端发送的请求并将其转发到目标服务器,然后将服务器响应返回给客户端。这种中介设计的好处是可以提高网络传输效率,增强网络安全性,还可以实现访问控制等功能。 下面是HTTP代理服务器设计实现步骤: 1. 建立套接字:HTTP代理服务器需要建立一个监听套接字来接收客户端请求。 2. 接收客户端请求:当客户端向HTTP代理服务器发送请求时,服务器需要使用accept()函数接收客户端的连接请求。 3. 解析请求HTTP代理服务器需要解析客户端请求,包括请求地址、请求方法等信息。 4. 转发请求HTTP代理服务器需要将解析后的客户端请求转发到目标服务器,并等待服务器响应。 5. 接收服务器响应:当目标服务器响应HTTP代理服务器时,服务器需要使用recv()函数接收服务器响应。 6. 解析响应:HTTP代理服务器需要解析服务器响应,包括响应状态码、响应头等信息。 7. 返回响应:HTTP代理服务器需要将服务器响应返回给客户端。 8. 断开连接:HTTP代理服务器需要在完成请求和响应后断开与客户端和服务器的连接。 另外,HTTP代理服务器还可以通过缓存技术来提高访问效率,还可以通过访问控制技术来限制用户访问某些网站等。 总之,HTTP代理服务器是一种非常重要的网络中介服务,它可以提高网络传输效率、增强网络安全性、实现访问控制等功能。
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值