计算机网络--http代理server的设计与实现

一、Socket编程的client和服务端的主要步骤:

Java Socket编程:对于http传输协议

client:

1、创建新的socket,绑定serverhost和port号

2Socket创建成功后获得对应的输出流

3、将请求报文通过输出流传到server,记得flush()刷新缓存

4、创建该socket所相应的输入流,获取server的相应报文

服务端:

1、通过建立对应port的socket实现监听某port的socket请求

2、当有别的socket请求连接就開始监听socket的信息,接收到请求报文

3、依据对请求报文的解析,得到请求者的url、port还有请求信息

4、将响应信息还有必要的头部连接形成响应报文。通过socket的输出流返回给请求client

二、HTTP代理server的基本原理:

代理server,即作为真实server的一个代理端。client的请求信息不是发送的真实请求的server而是发送的代理server,此时代理server是作为一个server。之后代理server通过解析client的请求信息,再向真实server发送请求报文,获得请求的信息。此时代理server是作为一个client。

使用代理server的优点是:

1、在请求client和真实server之间加入了一层,这样就可控的对于请求的响应报文做一些限制或者是改变。比如站点过滤、钓鱼站点等使得响应到client的信息是代理server处理过的。

2、还有就是请求报文先发送到代理server。这样代理server能够设立缓存,通过对请求报文解析后代理server能够通过查找本地缓存。假设有缓存好的,而且通过向server发送是否更新的信息后得到没有改动后就能够直接从代理server将响应报文返回给client,这样降低了服务端的负载,降低了流量

三、HTTP代理server的程序流程图:

 

 

中间代理server能够设定对请求报文和响应报文做一些改动

 

四、实现HTTP代理server的关键技术及解决方式

1、关键技术:socket编程发送和接受报文

 因为http的请求和响应报文都有特定的格式,所以一旦对于报文的格式理解错误就不能获得正确的响应,比如:对于请求报文每一行须要换行符,可是在编程的时候须要清楚理解换行符和回车符,假设在写请求报文时单单以\n作为换行组成的报文将得不到server的响应会产生400 bad request错误。

解决方式:每一行换行须要以回车符和换行符即\r \n 两个一起。这样才干得到正确的报文。在读取响应报文时也要注意会有两个符号作为一行的换行,所以在读取到\r时就表明一行已经读取完成。并且下一行之前另一个\n须要清除

2、关键技术:对于client、代理server、真正server之间的响应线程之间的正确顺序的组织;

解决方式:使用线程组织各部分之间的调度关系。代理server处于一直监听状态。当和client交互时处于server的角色,当和server交互时处于client的角色。

3、关键技术:对于请求报文信息的解析,包含正式请求的server的url、port号、host等信息的正确获取

解决方式:按行提取信息,使用字符串处理函数提取实用的信息

4、关键技术:使用缓存的代理server,须要做到保存请求报文对应的响应报文。顺序不能有差错并且信息不能有缺漏

解决方式:使用日志,在每接受一次请求的时候。将请求的完整url保存到日志中,之后一旦得到对应信息直接保存在url下方,每次通过匹配url得知其下方的响应是否是所需的。这样方便查找和改动

 

五、HTTP代理server的实验验证过程以及实验结果

1、基本功能:代理上网

 



 

2、扩展功能:屏蔽站点

 

 

 

3、扩展功能:钓鱼站点

选择搜狗可是进入的是淘宝网

 

4、扩展功能:带有缓存处理

每一次都会在日志中找是否已有对应的缓存,已有信息则向server发送时间确认报文,后决定是否使用缓存中信息

 

 

六、HTTP代理server源码(带有具体凝视)

package test;

import java.io.*;
import java.net.*;
import java.util.*;

public class MyHttpProxy extends Thread {
	public static int CONNECT_RETRIES = 5; // 尝试与目标主机连接次数
	public static int CONNECT_PAUSE = 5; // 每次建立连接的间隔时间
	public static int TIMEOUT = 8000; // 每次尝试连接的最大时间
	public static int BUFSIZ = 1024; // 缓冲区最大字节数
	public static boolean logging = false; // 是否记录日志
	public static OutputStream log_S = null; // 日志输出流
	public static OutputStream log_C = null; // 日志输出流
	public static OutputStream log_D = null; // 响应报文日志
	public static int count = -1;
	public static List<String> requestInfo = new ArrayList<String>();
	public static List<String> cacheInfo;
	Socket ssocket = null;
	// cis为client输入流。sis为目标主机输入流
	InputStream cis = null, sis = null;
	BufferedReader cbr = null, sbr = null; // 转化为字符流读取便于比較
	// cos为client输出流。sos为目标主机输出流
	OutputStream cos = null, sos = null;
	PrintWriter cpw = null, spw = null;// 转化为字符流
	String buffer = ""; // 读取请求头
	String URL = ""; // 读取请求URL
	String host = ""; // 读取目标主机host
	int port = 80; // 默认端口80
	String findUrl = "";//在缓存中查找的url
	// 与client相连的Socket
	protected Socket csocket;

	public MyHttpProxy(Socket cs) {
		try {
			csocket = cs;
			cis = csocket.getInputStream(); // 代理server作为server接受client的请求
			cbr = new BufferedReader(new InputStreamReader(cis));
			cos = csocket.getOutputStream(); // 代理server作为server向client发出响应
			cpw = new PrintWriter(cos);
			start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void writeLog(int c, int browser) throws IOException {
		if (browser == 1)
			log_C.write((char) c);
		else if (browser == 2)
			log_S.write((char) c);
		else
			log_D.write((char) c);
	}

	public void writeLog(byte[] bytes, int offset, int len, int browser)
			throws IOException {
		for (int i = 0; i < len; i++)
			writeLog((int) bytes[offset + i], browser);
	}

	public void run() {  
			try { 
				csocket.setSoTimeout(TIMEOUT);
				System.out.println("到了读取第一行");
				buffer = cbr.readLine(); // 获取首部行
				System.out.println("buffer:" + buffer);

				URL = getRequestURL(buffer);
				System.out.println(URL);
				if(URL.equals("http://www.sogou.com/")){
					URL = "http://www.taobao.com/";
					buffer = "GET "+URL+" HTTP/1.1"; 
					requestInfo.add("Accept: text/html, application/xhtml+xml, */*"); 
					requestInfo.add("Accept-Language: zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3"); 
					requestInfo.add("User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.2; WOW64; Trident/6.0)");
					requestInfo.add("Accept-Encoding: gzip, deflate");
					requestInfo.add("Proxy-Connection: Keep-Alive");
					requestInfo.add("DNT: 1");
					requestInfo.add("Host: www.taobao.com");
					requestInfo.add("Cookie: thw=cn; isg=0BC4B5EFD7C7FCFEB73317770EA7F3F5; l=AeVoHE44ZTsle7DjpW8fBSV7pbSl-2U7; cna=GCHeDZQAVwkCAdvZ9Apwg8rH; t=1a1386bec550ab78d1aaf5ad5b90e044; mt=ci%3D-1_0; _med=dw:1366&dh:768&pw:1366&ph:768&ist:0");
				}
				else if(URL.equals("http://www.qq.com/")) {
					URL = "";
				}
				int n;
				// 抽取host
				n = URL.indexOf("//");
				if (n != -1)
					host = URL.substring(n + 2); // www.baidu.com/
				n = host.indexOf('/');
				if (n != -1)
					host = host.substring(0, n);// www.baidu.com
				n = URL.indexOf('?

'); if(n != -1) findUrl = URL.substring(0,n); else findUrl = URL; // 分析可能存在的端口号 n = host.indexOf(':'); if (n != -1) { port = Integer.parseInt(host.substring(n + 1)); host = host.substring(0, n); } int retry = CONNECT_RETRIES; while (retry-- != 0 && !host.equals("")) { try { System.out.println("端口号:" + port + "主机:" + host); System.out.println("第一行是 " + retry + ":" + buffer); ssocket = new Socket(host, port); // 尝试建立与目标主机的连接 break; } catch (Exception e) { e.printStackTrace(); } // 等待 Thread.sleep(CONNECT_PAUSE); } if (ssocket != null) { ssocket.setSoTimeout(TIMEOUT); sis = ssocket.getInputStream(); // 代理server作为client接受响应 sbr = new BufferedReader(new InputStreamReader(sis)); sos = ssocket.getOutputStream(); // 代理server作为client发出请求 spw = new PrintWriter(sos); String modifTime = findCache(findUrl);// 在缓存中寻找是否之前已经缓存过这个url的信息 System.out.println("上一次改动的时间为:" + modifTime);// writeLog(buffer.getBytes(), 0, buffer.length(), 1); writeLog(buffer.getBytes(), 0, buffer.length(), 3); writeLog("\r\n".getBytes(), 0, 2, 3); // 之前没有缓存 if (modifTime == null) { while (!buffer.equals("")) { buffer += "\r\n"; if(buffer.contains("www.taobao.com")) { //屏蔽人人网,假设是淘宝就发送淘宝的报文 int k = 0; while(requestInfo.size() - k > 0) { spw.write(buffer); buffer = requestInfo.get(k++); buffer += "\r\n"; } break; } else{ spw.write(buffer); writeLog(buffer.getBytes(), 0, buffer.length(), 1); System.out.print("向server发送请求:"+buffer); buffer = cbr.readLine(); } } spw.write("\r\n"); writeLog("\r\n".getBytes(), 0, 2, 1); spw.flush(); // 读取server的响应信息 int length; byte bytes[] = new byte[BUFSIZ]; while (true) { try { if ((length = sis.read(bytes)) > 0) { // 读取client的请求转给server cos.write(bytes, 0, length); if (logging) { writeLog(bytes, 0, length, 1); writeLog(bytes,0,length,3); } } else if (length < 0) break; } catch (SocketTimeoutException e) { } catch (InterruptedIOException e) { System.out.println("\nRequest Exception:"); e.printStackTrace(); } } if(count == 0) { System.out.println(cbr.readLine()); } cpw.write("\r\n"); writeLog("\r\n".getBytes(), 0, 2, 3); writeLog("\r\n".getBytes(), 0, 2, 2); cpw.flush(); } else { buffer += "\r\n"; spw.write(buffer); System.out.print("向server发送确认改动时间请求:"+buffer); String str1 = "Host: " + host + "\r\n"; spw.write(str1); String str = "If-modified-since: " + modifTime + "\r\n"; spw.write(str); spw.write("\r\n"); spw.flush(); System.out.print(str1); System.out.print(str); String info = sbr.readLine(); System.out.println("server发回的信息是:"+info); if (info.contains("Not Modified")) { int j = 0; System.out.println("使用缓存中的数据"); while (j < cacheInfo.size()) { info = cacheInfo.get(j++); info += "\r\n"; System.out.print(info); cpw.write(info); } cpw.write("\r\n"); cpw.flush(); } else { System.out.println("有更新,使用新的数据"); while (!info.equals("")) { info += "\r\n"; System.out.print("新的数据是:" + info); cpw.write(info); info = sbr.readLine(); } cpw.write("\r\n"); cpw.flush(); } } } } catch (Exception e) { e.printStackTrace(); } } public String getRequestURL(String buffer) { String[] tokens = buffer.split(" "); String URL = ""; if (tokens[0].equals("GET")) for (int index = 0; index < tokens.length; index++) { if (tokens[index].startsWith("http://")) { URL = tokens[index]; break; } } return URL; } public void pipe(InputStream cis, InputStream sis, OutputStream sos, OutputStream cos) { try { int length; byte bytes[] = new byte[BUFSIZ]; while (true) { try { if ((length = cis.read(bytes)) > 0) { // 读取client的请求转给server sos.write(bytes, 0, length); if (logging) writeLog(bytes, 0, length, 1); } else if (length < 0) break; } catch (SocketTimeoutException e) { } catch (InterruptedIOException e) { System.out.println("\nRequest Exception:"); e.printStackTrace(); } try { if ((length = sis.read(bytes)) > 0) {// 接受server的响应回传给请求的client cos.write(bytes, 0, length); // 由于是按字节读取,所以将回车和换行符也传递过去了 if (logging) { writeLog(bytes, 0, length, 1); writeLog(bytes, 0, length, 3); } } } catch (SocketTimeoutException e) { } catch (InterruptedIOException e) { System.out.println("\nResponse Exception:"); e.printStackTrace(); } } } catch (Exception e0) { System.out.println("Pipe异常: " + e0); } } public static void startProxy(int port, Class clobj) { try { ServerSocket ssock = new ServerSocket(port); while (true) { Class[] sarg = new Class[1]; Object[] arg = new Object[1]; sarg[0] = Socket.class; try { java.lang.reflect.Constructor cons = clobj .getDeclaredConstructor(sarg); arg[0] = ssock.accept(); System.out.println("启动线程:"+count++); cons.newInstance(arg); // 创建HttpProxy或其派生类的实例 } catch (Exception e) { Socket esock = (Socket) arg[0]; try { esock.close(); } catch (Exception ec) { } } } } catch (IOException e) { System.out.println("\nStartProxy Exception:"); e.printStackTrace(); } } // 測试用的简单main方法 static public void main(String args[]) throws FileNotFoundException { System.out.println("在端口8888启动代理server\n"); OutputStream file_S = new FileOutputStream(new File("log_s.txt")); OutputStream file_C = new FileOutputStream(new File("log_c.txt")); OutputStream file_D = new FileOutputStream("log_d.txt",true); MyHttpProxy.log_S = file_S; MyHttpProxy.log_C = file_C; MyHttpProxy.log_D = file_D; // 直接存储相关URl相应的响应报文 MyHttpProxy.logging = true; MyHttpProxy.startProxy(8888, MyHttpProxy.class); } public String findCache(String head) { cacheInfo = new ArrayList<String>(); String resul = null; int count = 0; try { // 直接在存有url和相应信息的文件里查找 InputStream file_D = new FileInputStream("log_d.txt"); String info = ""; while (true) { int c = file_D.read(); if (c == -1) break; // -1为结尾标志 if (c == '\r') { file_D.read(); break;// 读入每一行数据 } if (c == '\n') break; info = info + (char) c; } System.out.println("第一次得到:" + info); System.out.println("要找的是:" + head); int m = 0; while ((m = file_D.read()) != -1 && info!=null) { //System.out.println("在寻找:"+info); // 找到同样的,那么它以下的就是响应信息。找上次改动的时间 if (info.contains(head)) { String info1; do { System.out.println("找到同样的了:" + info); info1 = ""; if(m!='\r' && m != '\n') info1 += (char) m; while (true) { m = file_D.read(); if (m == -1) break; if (m == '\r') { file_D.read(); break; } if (m == '\n') { break; } info1 += (char) m; } System.out.println("info1是:"+info1); if (info1.contains("Last-Modified:")) { resul = info1.substring(16); } cacheInfo.add(info1); if(info1.equals("")){ System.out.print("我是空"); return resul; } } while (!info1.equals("") && info1 != null && m != -1); } info = ""; while (true) { if (m == -1) break; if (m == '\r') { file_D.read(); break; } if (m == '\n') break; info += (char) m; m = file_D.read(); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return resul; } }



posted @ 2016-02-20 17:22  lcchuguo  阅读(1054)  评论(0编辑  收藏  举报