基于JDK http包實現的簡單代理服務器
利用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(); } }
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(); } } }
在上述代碼中,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()));
在上述的代碼中,將請求的文件的路徑進行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; } }
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; } }
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();
四、結果展示
設置好firefox的代理后進行網絡訪問
在程序不運行的情況下,用firefox訪問的結果如下:
圖3 不運行代理服務器的時候火狐請求網頁結果
接下來,運行代理服務器,進行在進行數據的訪問:
圖4 代理服務器運行時請求數據
接下來對交大bbs進行訪問,得到如下的結果:
圖4 訪問交大BBS的結果
后臺運行后緩存的數據截圖如下:
圖5 訪問緩存數據的主機文件夾
主機下的具體的內容如下:
圖6 進入百度主機文件夾下的文件(MD5處理后的文件名)
PS:大家在實現的時候注意:
讀寫圖片文件的時候,reader和stream的結果是完全不同的,reader取出來 的圖片一般式不能正確顯示的,而stream才是正確的處理方式,是因為reader是按字符讀取的,而stream是按照字節讀取的。
整個工程的下載鏈接在百度網盤,有需要學習交流的可以下載:
鏈接: http://pan.baidu.com/s/1dDoc8QP 密碼: rrpm
來自:http://my.oschina.net/zhangwenwen/blog/539393