一、代理服务器原理当客户在浏览器中设置好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 四、附加功能的部分。 用户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 是否是钓鱼网站并且如何处理。 如果是钓鱼网站,首先输出提示信息,然后根据给定的参数重构请求报文,发送到被引导到的网站服务器,从中接收数据,转发给客户端,之后关闭套接字并返回。 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() |
实验结果: |
缓存如下:
为防止网站实时更新,我找到了一个较为稳定的网址www.qqkk.com,进行试验 再次访问www.qqkk.com,发现响应码为304,代理服务器从缓存中读取信息。
我们设置了禁止访问的网站,将注释符号删除后运行程序,发现该网站被禁止访问。
我设置了钓鱼网站的字典,将需要转发的网站转发到目的网址。
|
问题讨论: |
在实现功能中网站缓存时,发现不能在缓存中显示if-modified-since 语句,后来发现是将浏览器当作server,而不是主机当作server,使用sendall函数将报文发送给主机即可在缓存中显示if-modified-since语句 |
心得体会: |
本次实验是第一次上机实现网络协议,通过本次实验,对socket编程的过程与技术有了一个初步的了解,更加深入地理解了http协议和代理服务器的基本原理,同时也掌握了代理服务器的设计与编程的基本实现。 |
来源地址:https://blog.csdn.net/qq_53191991/article/details/127312764