利用Java 自带的Http Api 实现的一个简易的代理服务器,实现对常用的在服务器上的静态内容进行缓存,减少网络带宽的浪费。
一、原理
本次实现的代理缓存服务器的主要的功能是当客户端访问相关的网页的时候,首先检测本地是否存在相关的数据,若存在,则直接发送头部具有IF-Modified-Then的http请求,检测数据本地数据是否是最新的,若不是最新的,则对本地数据进行更新并向客户端返回相关数据;若不存在,则向主机请求相关的数据,存储在本地并向客户端返回数据。详细的流程见图1:
图 1 代理缓存服务器的工作流程
二、具体步骤
代理服务器的创建及firefox代理的配置
代理服务器监听的端口为本地的9999端口,运行起来后就可以将firefox设置代理进行访问。
建立代理服务器的设置代码如下:
public static void main(String[] args) {
// TODO
HttpServer server = null;
try {
MyServerInit();
System.out.println("[INFO]创建服务器.....");
server = HttpServer.create(new InetSocketAddress(9999), 0);
System.out.println("[INFO]设置服务器的处理程序代码....");
server.createContext("/", new MyProxyServerHandler());
server.setExecutor(null);
System.out.println("[INFO]服务器启动中....成功!");
server.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
// TODO
HttpServer server = null;
try {
MyServerInit();
System.out.println("[INFO]创建服务器.....");
server = HttpServer.create(new InetSocketAddress(9999), 0);
System.out.println("[INFO]设置服务器的处理程序代码....");
server.createContext("/", new MyProxyServerHandler());
server.setExecutor(null);
System.out.println("[INFO]服务器启动中....成功!");
server.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Firefox的设置如下图2:
图2 火狐浏览器的代理配置
MyServerInit的实现如下:
private static void MyServerInit() {
// TODO Auto-generated method stub
File f = new File(ServerConf.ERROR_HTML);
if (!f.exists()) {
f.getParentFile().mkdirs();
try {
f.createNewFile();
System.out.println("[INFO初始化服务器....]");
System.out.println("[INFO]创建全局错误文件html页面...");
// 文件中写入错误的信息数据
BufferedWriter bufferedWriter = new BufferedWriter(
new FileWriter(f));
String content = "<font color='#ff0000'>请求发生错误,请仔细URL是否正确或者是代理服务器网络发生故障,<br>了解更多可以联系管理员</font>";
bufferedWriter.write(content);
bufferedWriter.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private static void MyServerInit() {
// TODO Auto-generated method stub
File f = new File(ServerConf.ERROR_HTML);
if (!f.exists()) {
f.getParentFile().mkdirs();
try {
f.createNewFile();
System.out.println("[INFO初始化服务器....]");
System.out.println("[INFO]创建全局错误文件html页面...");
// 文件中写入错误的信息数据
BufferedWriter bufferedWriter = new BufferedWriter(
new FileWriter(f));
String content = "<font color='#ff0000'>请求发生错误,请仔细URL是否正确或者是代理服务器网络发生故障,<br>了解更多可以联系管理员</font>";
bufferedWriter.write(content);
bufferedWriter.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在上述代码中,class MyProxyServerHandler implements HttpHandler主要用来对请求进行处理,下面对其进行详细的说明。
三、MyProxyServerHandler的逻辑处理
1,接收来自客户端的数据请求的主机以及uri,生成返回的html页面的本地路径,代码如下:
// 得到请求的头部
Headers headers = httpExchange.getRequestHeaders();
// 得到请求主机的名称
String host = headers.get("Host").get(0);
// 得到请求的uri
URI uri = httpExchange.getRequestURI();
String html_file = ServerConf.DOCUMENT_DIR + host + "/"
+ DigestUtils.md5Hex(ContentUtils.parseFile(uri.toURL().getFile()));
// 得到请求的头部
Headers headers = httpExchange.getRequestHeaders();
// 得到请求主机的名称
String host = headers.get("Host").get(0);
// 得到请求的uri
URI uri = httpExchange.getRequestURI();
String html_file = ServerConf.DOCUMENT_DIR + host + "/"
+ DigestUtils.md5Hex(ContentUtils.parseFile(uri.toURL().getFile()));
在上述的代码中,将请求的文件的路径进行MD5处理是为了防止有的文件路径名过长,抛出异常,所用的工具包是apache的commons-codec-1.10.
2,检测请求的主机的文件夹是否存在本地,若不存在则创建,若存在,则继续检测请求的文件是否存在本地,相关的代码如下:
public boolean IsHostExist(String host) {
File host_dir = new File(root + host);
if (host_dir.exists() && host_dir.isDirectory()) {
return true;
} else {
if (host_dir.mkdirs()) {
System.out.println("主机文件:" + host + "创建成功!");
} else {
System.err.println("主机文件:" + host + "创建失败!");
}
return false;
}
}
public boolean IsHostExist(String host) {
File host_dir = new File(root + host);
if (host_dir.exists() && host_dir.isDirectory()) {
return true;
} else {
if (host_dir.mkdirs()) {
System.out.println("主机文件:" + host + "创建成功!");
} else {
System.err.println("主机文件:" + host + "创建失败!");
}
return false;
}
}
3,检测所请求的本地文件是否存在,若存在,则发送含有IF-Modified-Then的http请求,若不存在则直接发送http请求并接收得到的数据进行分析处理;代码如下:
// 检查请求的文件是否存在
if (responseHtml.exists()) {
// 发送http的head操作,得到文档的状态信息
String timestamp = new Date(responseHtml.lastModified())
.toGMTString();
HttpGet getCheck = new HttpGet(uri.toURL().toString());
getCheck.addHeader("If-Modified-Since", timestamp);
CloseableHttpResponse responseCheck = client.execute(getCheck);
// 得到返回的code
int respcode = responseCheck.getStatusLine().getStatusCode();
// 根据respcode进行合理的动作
switch (respcode) {
case 304:
// 本地文件就是需要请求的文件
break;
case 200:
// 对本地文件进行更新
ContentUtils.outputFile(responseHtml, responseCheck.getEntity());
break;
case 404:
// 发生错误
responseHtml = new File(ServerConf.ERROR_HTML);
break;
default:
break;
}
//设置最终的返回状态码
responsecode = respcode;
} else {
int code = getDirectly(client, uri, responseHtml);
responsecode=code;
}
//getDirectly的定义如下:
private int getDirectly(CloseableHttpClient client2, URI uri,
File responseHtml) {
// TODO Auto-generated method stub
HttpGet get = null;
try {
get = new HttpGet(uri.toURL().toString());
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
int code = 200;
try {
CloseableHttpResponse response = client2.execute(get);
code = response.getStatusLine().getStatusCode();
// 进行分析返回的code
switch (code) {
case HttpStatus.SC_OK:
// 将数据写出文件
ContentUtils.outputFile(responseHtml, response.getEntity()
);
break;
case HttpStatus.SC_NOT_FOUND:
// 没有找到文件
System.err.println("没有找到相关的网页");
break;
default:
break;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return 0;
}
}
// 检查请求的文件是否存在
if (responseHtml.exists()) {
// 发送http的head操作,得到文档的状态信息
String timestamp = new Date(responseHtml.lastModified())
.toGMTString();
HttpGet getCheck = new HttpGet(uri.toURL().toString());
getCheck.addHeader("If-Modified-Since", timestamp);
CloseableHttpResponse responseCheck = client.execute(getCheck);
// 得到返回的code
int respcode = responseCheck.getStatusLine().getStatusCode();
// 根据respcode进行合理的动作
switch (respcode) {
case 304:
// 本地文件就是需要请求的文件
break;
case 200:
// 对本地文件进行更新
ContentUtils.outputFile(responseHtml, responseCheck.getEntity());
break;
case 404:
// 发生错误
responseHtml = new File(ServerConf.ERROR_HTML);
break;
default:
break;
}
//设置最终的返回状态码
responsecode = respcode;
} else {
int code = getDirectly(client, uri, responseHtml);
responsecode=code;
}
//getDirectly的定义如下:
private int getDirectly(CloseableHttpClient client2, URI uri,
File responseHtml) {
// TODO Auto-generated method stub
HttpGet get = null;
try {
get = new HttpGet(uri.toURL().toString());
} catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
int code = 200;
try {
CloseableHttpResponse response = client2.execute(get);
code = response.getStatusLine().getStatusCode();
// 进行分析返回的code
switch (code) {
case HttpStatus.SC_OK:
// 将数据写出文件
ContentUtils.outputFile(responseHtml, response.getEntity()
);
break;
case HttpStatus.SC_NOT_FOUND:
// 没有找到文件
System.err.println("没有找到相关的网页");
break;
default:
break;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return 0;
}
}
4,向客户端返回请求的数据
// 对客户端做出响应
OutputStream outputStream = httpExchange.getResponseBody();
// 读取文件响应客户端的请求
byte[] buffer = ContentUtils.getContent(responseHtml);
httpExchange.sendResponseHeaders(responsecode, buffer.length);
//httpExchange.getResponseHeaders().add("ContentTyep","text/html");
System.out.println(httpExchange.getResponseHeaders().values());
//System.out.println(new String(buffer));
outputStream.write(buffer);
outputStream.close();
// 对客户端做出响应
OutputStream outputStream = httpExchange.getResponseBody();
// 读取文件响应客户端的请求
byte[] buffer = ContentUtils.getContent(responseHtml);
httpExchange.sendResponseHeaders(responsecode, buffer.length);
//httpExchange.getResponseHeaders().add("ContentTyep","text/html");
System.out.println(httpExchange.getResponseHeaders().values());
//System.out.println(new String(buffer));
outputStream.write(buffer);
outputStream.close();
四、结果展示
设置好firefox的代理后进行网络访问
在程序不运行的情况下,用firefox访问的结果如下:
图3 不运行代理服务器的时候火狐请求网页结果
接下来,运行代理服务器,进行在进行数据的访问:
图4 代理服务器运行时请求数据
接下来对交大bbs进行访问,得到如下的结果:
图4 访问交大BBS的结果
后台运行后缓存的数据截图如下:
图5 访问缓存数据的主机文件夹
主机下的具体的内容如下:
图6 进入百度主机文件夹下的文件(MD5处理后的文件名)
PS:大家在实现的时候注意:
读写图片文件的时候,reader和stream的结果是完全不同的,reader取出来 的图片一般式不能正确显示的,而stream才是正确的处理方式,是因为reader是按字符读取的,而stream是按照字节读取的。
整个工程的下载链接在百度网盘,有需要学习交流的可以下载:
链接: http://pan.baidu.com/s/1dDoc8QP 密码: rrpm