一、代理服务器原理 当客户在浏览器中设置好Proxy Server后,你使用浏览器访问所有WWW站点的请求都不会直接发给目的主机,而是先发给代理服务器,代理服务器接受了客户的请求以后,由代理服务器向目的主机发出请求,并接受目的主机的数据,存于代理服务器的硬盘中,然后再由代理服务器将客户要求的数据发给客户。 代理服务器是为了减少长距离的传送而诞生的。它不仅可以代理客户端向服务器端提出请求,也可以代理服务器传给客户端所需要的数据。 当客户端对服务器端提出请求时,此请求会被送到代理服务器,然后代理服务器会检查本身是否有客户端所需要的数据。如果有,代理服务器便代替服务器将数据传给客户端。而代理服务器一般都是设置距自己传输距离较近的某台代理服务器,所以它传数据给客户端的速度会比从远程服务器传数据要快。 如果代理服务器没有客户端所请求的数据,它会去服务器获取所需的数据。在代理服务器从服务器端取得数据传给客户端时,自己保存一份,待下次如果有用户提出相同的请求时,便可以将数据直接传过去,而不需要再去服务器端获取了。可见,代理服务器改善网络数据传输阻塞的功能是显而易见的。 本实验中代理服务器的流程图:
我们在firefox设置里查找网络代理设置,然后选择手动设置代理,适用代理服务器,并设置好地址和端口号,保存。
首先我们设置实验中需要用到的参数 PARAMETERS = { ??? 'HOST': '127.0.0.1', ??? 'PORT': 9999, ??? 'MAX_LISTEN': 50, ??? 'MAX_LENGTH': 4096, ??? 'CACHE_SIZE': 1000 } 接下来我们初始化socket并且在循环中监听指定端口,当接收到客户端的请求则创建一个新线程进行处理。其中transmission函数是代理服务器的核心函数。 # 初始化socket ??? s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ??? # 绑定IP地址和端口号 ??? s.bind((PARAMETERS['HOST'], PARAMETERS['PORT'])) ??? # socket的排队个数 ??? s.listen(PARAMETERS['MAX_LISTEN']) ??? # 创建cache目录 ??? if not os.path.exists(cache_dir): ??????? os.mkdir(cache_dir) ??? print('初始化完成.') ??? print('server将一直等待连接...') ??? while True: ??????? # 在循环中监听9999端口,接收到客户端请求则创建一个新线程处理 ??????? sock, address = s.accept() ??? ????threading.Thread(target=transmission, args=(sock, address)).start() 接下来接收来自客户端的http请求报文,解码报文,获取请求行并格式化,分析字符串得到url地址,并且通过传入参数获得主机IP。 # 接受来自客户端的http请求报文 ??? message = so.recv(PARAMETERS['MAX_LENGTH']) ??? if len(message) == 0: ??????? return ??? message = message.decode('utf-8', 'ignore')? # 对报文进行解码,忽略错误 ??? request_line = message.split('\r\n')[0].split()? # 获得请求行,去掉前后空格 ??? url = urlparse(request_line[1])? # 获得URL ??? hostIP = address[0]? # 获得主机IP 四、附加功能的部分。 1. 用户IP是否被过滤 如果用户IP被过滤,则直接输出提示信息,然后关闭套接字并返回。 ?? ?if hostIP in Blocked_User:? # 用户IP被过滤 ??????? print('用户 '+str(hostIP)+' 被禁止访问.') ??????? so.close() ??????? return
- 主机名是否禁止访问
如果主机名禁止访问,则直接输出提示信息,然后关闭套接字并返回。 ??? if url.hostname in No_Access_url:? # 主机名被禁止访问 ??????? print(str(url.hostname) + ' 被禁止访问.') ??????? so.close() ??????? return 3. 是否是钓鱼网站并且如何处理。 如果是钓鱼网站,首先输出提示信息,然后根据给定的参数重构请求报文,发送到被引导到的网站服务器,从中接收数据,转发给客户端,之后关闭套接字并返回。 ??? if url.hostname in fishing:? # 主机名为钓鱼网站 ??????? print('即将从 ' + str(url.hostname) + ' 跳转到 ' + str(fishing[url.hostname])) ??????? new_hostname = fishing[url.hostname]? # 新的目标主机名 ??????? message = message.replace(request_line[1], 'http://'+new_hostname+'/') ??????? message = message.replace(url.hostname, new_hostname)? # 将报文重构 ??????? # print(message) ??????? fish_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)? # 初始化钓鱼网站的socket ??????? fish_socket.connect((new_hostname, 80))? # 与钓鱼网站的服务器建立连接 ??????? fish_socket.sendall(message.encode())? # 将报文编码发送到钓鱼网站服务器 ??????? while True: ??????? ????# 从服务器接收数据,转发给客户端 ??????????? buff = fish_socket.recv(PARAMETERS['MAX_LENGTH']) ??????????? if not buff: ??????????????? fish_socket.close() ??????????????? break ??????????? so.sendall(buff) ??????? so.close() ??????? fish_socket.close() ??????? return 五、cache功能模块。 首先确定缓存路径和文件名,并初始化标记为未修改。 接下来进行判断,如果本地已经存在该文件,此时需要查看最后一次缓存之后网站内容是否有发生变化,并更新最后一次缓存的时间。我们通过向服务器发送报文,解析返回数据的方式进行判断。 如果返回数据的响应码为304,则表示发生未变化,直接从本地文件中读取信息发送到客户端。如果响应码不为304,则表示发生了变化,直接将标记修改为已修改。 接下来进行判断,如果本地缓存中不存在该文件或者标记为已修改,则表示需要更新缓存。向服务器发送数据,获取服务器返回的数据,并且写入到缓存中,然后将数据转发给客户端,最后关闭套接字。 当本地缓存已经存在相应文件,判断网页是否被修改时的逻辑实现: path = cache_dir + url.hostname? # 缓存路径和文件名 ??? modified = False? # 第一次标记为未修改 ??? if os.path.exists(path):? # 当已经存在该文件,需要判断服务器是否修改过此网页 ??????? modified_time = os.stat(path).st_mtime? # 缓存文件最后修改的时间 ??????? headers = str('If-Modified-Since: ' + time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(modified_time))) ??????? # 把modified-time按报文要求格式化 ??????? message = message[:-2] + headers + '\r\n\r\n'? # 把If-Modified-Since字段加入到请求报文中 ??????? # 向服务器发送报文 ??????? server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ??????? server_socket.connect((url.hostname, 80)) ??????? server_socket.sendall(message.encode()) ??????? data = server_socket.recv(PARAMETERS['MAX_LENGTH']).decode('utf-8', 'ignore') ??????? # print(data) ??????? server_socket.close() ??????? if data[9:12] == '304':? # 响应码为304,表示网页未变化,从cache中读取网页 ??????????? print('网页未更新,将从缓存中读取网页.') ??????????? with open(path, "rb") as f: ??????????????? so.sendall(f.read()) ??????? else:? # 网页变化,标记为已修改 ??????????? modified = True ??? if not os.path.exists(path) or modified:? # 如果没有该网页的缓存或者网页已被修改 ??????? # 向服务器发送数据,才能接收到服务器发回来的数据 ??????? server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ??????? server_socket.connect((url.hostname, 80)) ??????? server_socket.sendall(message.encode()) ??????? print('更新缓存.') ??????? f = open(path, 'wb')? # 重写缓存 ??????? while True: ??????????? buff = server_socket.recv(PARAMETERS['MAX_LENGTH']) ??????????? if not buff: ??????????????? # print(buff) ??????????????? f.close() ??????????????? server_socket.close() ??????????????? break ??????????? f.write(buff)? # 将接收到的数据写入缓存 ??????????? so.sendall(buff)? # 将接收到的数据转发给客户端 ??????? so.close() |