一、Socket编程的客户端和服务端的主要步骤:
Java Socket编程:对于http传输协议
客户端:
1、创建新的socket,绑定服务器host和端口号
2、Socket创建成功后获得相应的输出流
3、将请求报文通过输出流传到服务器,记得flush()刷新缓存
4、创建该socket所对应的输入流,获取服务器的相应报文
服务端:
1、通过建立相应端口的socket实现监听某端口的socket请求
2、当有别的socket请求连接就开始监听socket的信息,接收到请求报文
3、根据对请求报文的解析,得到请求者的url、端口还有请求信息
4、将响应信息还有必要的头部连接形成响应报文,通过socket的输出流返回给请求客户端
二、HTTP代理服务器的基本原理:
代理服务器,即作为真实服务器的一个代理端,客户端的请求信息不是发送的真实请求的服务器而是发送的代理服务器,此时代理服务器是作为一个服务器,之后代理服务器通过解析客户端的请求信息,再向真实服务器发送请求报文,获得请求的信息,此时代理服务器是作为一个客户端。
使用代理服务器的好处是:
1、在请求客户端和真实服务器之间添加了一层,这样就可控的对于请求的响应报文做一些限制或者是改变,例如网站过滤、钓鱼网站等使得响应到客户端的信息是代理服务器处理过的;
2、还有就是请求报文先发送到代理服务器,这样代理服务器可以设立缓存,通过对请求报文解析后代理服务器可以通过查找本地缓存,如果有缓存好的,并且通过向服务器发送是否更新的信息后得到没有修改后就可以直接从代理服务器将响应报文返回给客户端,这样减少了服务端的负载,减少了流量。
三、HTTP代理服务器的程序流程图:
中间代理服务器可以设定对请求报文和响应报文做一些修改
四、实现HTTP代理服务器的关键技术及解决方案
1、关键技术:socket编程发送和接受报文
由于http的请求和响应报文都有特定的格式,所以一旦对于报文的格式理解错误就不能获得正确的响应,例如:对于请求报文每一行需要换行符,但是在编程的时候需要清楚理解换行符和回车符,如果在写请求报文时单单以\n作为换行组成的报文将得不到服务器的响应会产生400 bad request错误。
解决方案:每一行换行需要以回车符和换行符即\r \n 两个一起,这样才能得到正确的报文。在读取响应报文时也要注意会有两个符号作为一行的换行,所以在读取到\r时就表明一行已经读取完毕,而且下一行之前还有一个\n需要清除
2、关键技术:对于客户端、代理服务器、真正服务器之间的响应线程之间的正确顺序的组织;
解决方案:使用线程组织各部分之间的调度关系,代理服务器处于一直监听状态,当和客户端交互时处于服务器的角色,当和服务器交互时处于客户端的角色。
3、关键技术:对于请求报文信息的解析,包括正式请求的服务器的url、端口号、host等信息的正确获取
解决方案:按行提取信息,使用字符串处理函数提取有用的信息
4、关键技术:使用缓存的代理服务器,需要做到保存请求报文相应的响应报文,顺序不能有差错而且信息不能有缺漏
解决方案:使用日志,在每接受一次请求的时候,将请求的完整url保存到日志中,之后一旦得到相应信息直接保存在url下方,每次通过匹配url得知其下方的响应是否是所需的,这样方便查找和修改
五、HTTP代理服务器的实验验证过程以及实验结果
1、基本功能:代理上网
2、扩展功能:屏蔽网站
3、扩展功能:钓鱼网站
选择搜狗但是进入的是淘宝网
4、扩展功能:带有缓存处理
每一次都会在日志中找是否已有相应的缓存,已有信息则向服务器发送时间确认报文,后决定是否使用缓存中信息
六、HTTP代理服务器源代码(带有详细注释)
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 requestInfo = new ArrayList();
public static List cacheInfo;
Socket ssocket = null;
// cis为客户端输入流,sis为目标主机输入流
InputStream cis = null, sis = null;
BufferedReader cbr = null, sbr = null; // 转化为字符流读取便于比较
// cos为客户端输出流,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
// 与客户端相连的Socket
protected Socket csocket;
public MyHttpProxy(Socket cs) {
try {
csocket = cs;
cis = csocket.getInputStream(); // 代理服务器作为服务器接受客户端的请求
cbr = new BufferedReader(new InputStreamReader(cis));
cos = csocket.getOutputStream(); // 代理服务器作为服务器向客户端发出响应
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(); // 代理服务器作为客户端接受响应
sbr = new BufferedReader(new InputStreamReader(sis));
sos = ssocket.getOutputStream(); // 代理服务器作为客户端发出请求
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("向服务器发送请求:"+buffer);
buffer = cbr.readLine();
}
}
spw.write("\r\n");
writeLog("\r\n".getBytes(), 0, 2, 1);
spw.flush();
// 读取服务器的响应信息
int length;
byte bytes[] = new byte[BUFSIZ];
while (true) {
try {
if ((length = sis.read(bytes)) > 0) { // 读取客户端的请求转给服务器
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("向服务器发送确认修改时间请求:"+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("服务器发回的信息是:"+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) { // 读取客户端的请求转给服务器
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) {// 接受服务器的响应回传给请求的客户端
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启动代理服务器\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 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;
}
}