距離開篇已通過了好久,期間完善了一下以前的版本,目前已經可以無缺運行,基本上該有的功能都有了,此外將原來的測試程序改成示例項目,新項目只需按照示例項目結構實現controller和view便可,詳情見: easy-httpserver、 demo-httpsrever。
此次咱們將首先來實現一個簡單版本,該版本只包括一些基本的功能,後續版本也將在此基礎上一步步改進。 javascript
俗話說的好,工欲善其事,必先利其器。咱們在開始開發以前應作好以下準備(真的很簡單):html
咱們動手寫代碼以前應該先肯定好項目的功能,並設計好項目的結構,雖然咱們目前須要實現的很簡單,可是仍是應該簡單的進行一番設計。項目計劃的功能以下:html5
看起來並無多少功能,可是其實僅僅這幾個功能咱們就能完成一個小型的動態網站了。在這裏須要提一點,若是你還一點都不瞭解web服務器的工做流程,能夠先看 這篇博客瞭解一下。這裏咱們先看一些本次實現的服務器的工做流程圖(用在線的 gliffy畫的,挺不錯):
java
如今功能方面已經清晰了,那麼咱們據此來分析一下項目結構的設計,對於http的請求和響應處理咱們目前直接使用jdk自帶的httpserver處理(httpserver使用);咱們須要實現一個比較核心的模塊實現各部分之間的銜接,根據httpserver咱們能夠實現一個EHHttpHandler來處理,其實現了HttpHandler接口,主要功能是接收http請求,判斷類型,解析參數,調用業務處理conroller,調用視圖處理Viewhandler,最後響應http請求。此外,爲了處理業務和視圖渲染咱們需實現controller和view相關的類。具體結構代碼見後邊。 git
首先咱們須要新建一個maven項目,首先創建以下結構: github
其中主要幾個文件夾和類的功能以下:web
好了,文件結構建好了,咱們接下來配置maven依賴,因爲主要使用的是jdk自帶的包,所以依賴只須要junit和common-log模塊,pom.xml以下:
apache
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>learn-1</groupId> <artifactId>learn-1</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>learn-1</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies> <build> <build> <finalName>easy-httpserver</finalName> <resources> <resource> <directory>${basedir}/src/main/view</directory> </resource> </resources> </build> </build> </project>
EHServer用來加載配置,初始化基本信息和啓動Server,其代碼以下:瀏覽器
/** * 主服務類 * @author guojing * @date 2014-3-3 */ public class EHServer { private final Log log = LogFactory.getLog(EHServer.class); /** * 初始化信息,並啓動server */ public void startServer() throws IOException { log.info("Starting EHServer......"); //設置路徑 Constants.CLASS_PATH = this.getClass().getResource("/").getPath(); Constants.VIEW_BASE_PATH = "page"; Constants.STATIC_RESOURCE_PATH = "static"; //設置端口號 int port = 8899; // 啓動服務器 HttpServerProvider provider = HttpServerProvider.provider(); HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(port), 100); httpserver.createContext("/", new EHHttpHandler()); httpserver.setExecutor(null); httpserver.start(); log.info("EHServer is started, listening at 8899."); } /** * 項目main */ public static void main(String[] args) throws IOException { new EHServer().startServer(); } }
能夠看到上邊代碼使用了httpserver,那麼接收的請求又是如何處理的呢?咱們能夠看到httpserver綁定了EHHttpserver類,而具體的處理就是在該類內完成的,下邊咱們就來看看該類的實現。服務器
EHHttpserver實現了HttpHandler,而HttpHandler是httpserver包提供的,實現其內的handle()方法後,在接收到請求後,httpserver將調用該方法進行處理。咱們將在該方法內判斷請求類型並進行相應處理,handle實現代碼以下:
public void handle(HttpExchange httpExchange) throws IOException { try { String path = httpExchange.getRequestURI().getPath(); log.info("Receive a request,Request path:" + path); // 根據後綴判斷是不是靜態資源 String suffix = path .substring(path.lastIndexOf("."), path.length()); if (Constants.STATIC_SUFFIXS.contains(suffix)) { byte[] bytes = IOUtil.readFileByBytes(Constants.CLASS_PATH + "static" + path); responseStaticToClient(httpExchange, 200, bytes); return; } // 調用對應處理程序controller ResultInfo resultInfo = invokController(httpExchange); // 返回404 if (resultInfo == null || StringUtil.isEmpty(resultInfo.getView())) { responseToClient(httpExchange, 200, "<h1>頁面不存在<h1>"); return; } // 解析對應view並返回 String content = invokViewHandler(resultInfo); if (content == null) { content = ""; } responseToClient(httpExchange, 200, content); return; } catch (Exception e) { httpExchange.close(); log.error("響應請求失敗:", e); } }
能夠看到首先根據url後綴判斷請求資源是否屬於靜態資源,若是是的話,則讀取對應資源並調用responseStaticToClient返回,若是不是則調用invokController進行業務處理,而invokController內部十分簡單,僅實例化一個IndexController(本次示例controller,直接寫死,之後將使用反射動態映射),調用其process方法。IndexController代碼以下:
/** * 主頁對應的contoller * @author guojing */ public class IndexController implements Controller{ public ResultInfo process(Map<String, Object> map){ ResultInfo result =new ResultInfo(); result.setView("index"); result.setResultMap(map); return result; } }
在controller中示例了一個ResultInfo對象,並設置view爲index(模板路徑爲page/index.page),並設置將請求參數直接賦值。而EHHttpserver在調用controller後,將ResultInfo傳遞給invokViewHandler處理。invokViewHandler和invokeController同樣,只是一個適配方法,其內部調用ViewHandler進行處理,ViewHandler將找到對應模板,並將其中替換符(這裏定義爲${XXXXX})替換爲對應參數的值,其代碼以下:
/** * 處理頁面信息 * @author guojing * @date 2014-3-3 */ public class ViewHandler { /** * 處理View模板,只提供建單變量(格式${XXX})替換,已廢棄 * @return */ public String processView(ResultInfo resultInfo) { // 獲取路徑 String path = analysisViewPath(resultInfo.getView()); String content = ""; if (IOUtil.isExist(path)) { content = IOUtil.readFile(path); } if (StringUtil.isEmpty(content)) { return ""; } // 替換模板中的變量,替換符格式:${XXX} for (String key : resultInfo.getResultMap().keySet()) { String temp = ""; if (null != resultInfo.getResultMap().get(key)) { temp = resultInfo.getResultMap().get(key).toString(); } content = content.replaceAll("\\$\\{" + key + "\\}", temp); } return content; } /** * 解析路徑(根據Controller返回ResultInfo的view),已廢棄 * @param viewPath * @return */ private String analysisViewPath(String viewPath) { String path = Constants.CLASS_PATH + (Constants.VIEW_BASE_PATH == null ? "/" : Constants.VIEW_BASE_PATH+"/") + viewPath + ".page"; return path; } }
在ViewHandler處理完後,就能夠返回數據了,由於處理不一樣,這裏把動態請求和靜態請求分開處理,代碼以下;
/** * 響應請求 * * @param httpExchange * 請求-響應的封裝 * @param code * 返回狀態碼 * @param msg * 返回信息 * @throws IOException */ private void responseToClient(HttpExchange httpExchange, Integer code, String msg) throws IOException { switch (code) { case 200: { // 成功 byte[] bytes = msg.getBytes(); httpExchange.sendResponseHeaders(code, bytes.length); OutputStream out = httpExchange.getResponseBody(); out.write(bytes); out.flush(); httpExchange.close(); } break; case 302: { // 跳轉 Headers headers = httpExchange.getResponseHeaders(); headers.add("Location", msg); httpExchange.sendResponseHeaders(code, 0); httpExchange.close(); } break; case 404: { // 錯誤 byte[] bytes = "".getBytes(); httpExchange.sendResponseHeaders(code, bytes.length); OutputStream out = httpExchange.getResponseBody(); out.write(bytes); out.flush(); httpExchange.close(); } break; default: break; } } /** * 響應請求,返回靜態資源 * * @param httpExchange * @param code * @param bytes * @throws IOException */ private void responseStaticToClient(HttpExchange httpExchange, Integer code, byte[] bytes) throws IOException { httpExchange.sendResponseHeaders(code, bytes.length); OutputStream out = httpExchange.getResponseBody(); out.write(bytes); out.flush(); httpExchange.close(); }
至此,咱們已經完成了以前預期的功能,如今咱們來測試一下到底可否運行。咱們在src/main/view/下本別創建以下文件:
其中index.page是動態模板頁,test.js是一個js文件,而tx.jpg是一張圖片。各代碼以下:
index.page <html> <head> <script type="text/javascript" src="/js/test.js"></script> </head> <body> <h1>Hello,${name}</h1> <img src="/pic/tx.jpg" title="tx" /> <script type="text/javascript"> hello(); </script> </body <html> test.js function hello(){ console.log("hello!") }
下邊咱們啓動項目,輸出以下:
能夠看到啓動成功,用瀏覽器打開:http://localhost:8899/index.page?name=guojing,發現響應頁面以下:
至此版本learn-1完成,基於該項目咱們已經可以實現一個簡單的網站,可是也就只是比純靜態網站多了頁面數據渲染,並不能真正的實現動態交互。那麼如何才能作到呢?答案就是提供session支持,下一版本咱們將加入session支持,是其更加完善。
最後附上源碼(github)地址:源代碼