今天咱們來寫一個相似於Tomcat的簡易服務器。可供你們深刻理解一下tomcat的工做原理,本文僅供新手參考,請各位大神指正!
首先咱們要準備的知識是:
Socket編程
HTML
HTTP協議
服務器編寫
反射
XML解析
有了上面的知識,咱們能夠開始寫咱們的代碼了~~
一、首先咱們要應用Socket編程寫一個簡單的服務端用來接收服務器端發來的請求:html
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { Server server = new Server(); //一、建立一個服務器端並開啓
server.start(); } public void start(){ try { ServerSocket ss = new ServerSocket(8888); //二、接收來自瀏覽器的請求
this.recevie(ss); } catch (IOException e) { e.printStackTrace(); } } private void recevie(ServerSocket ss){ try { Socket client = ss.accept(); //三、未來自瀏覽器的信息打印出來
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); StringBuilder httpMessage = new StringBuilder(); while(br.read()!=-1){ httpMessage.append(br.readLine()); httpMessage.append("\r\n"); } System.out.println(httpMessage.toString()); } catch (IOException e) { e.printStackTrace(); } } }
如下是瀏覽器訪問上述服務器時產生的請求內容:HTTP知識補充:java
如下是瀏覽器訪問上述服務器時產生的請求內容:
POST請求內容:(遠不止這些,你們能夠經過wireshark來抓包分析請求協議格式)
POST / HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 25
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
username=ss&password=aaaa
GET請求內容:
GET /?username=aa&password=ssss HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
下面給出一個服務器響應內容:
通用頭域包含請求和響應消息都支持的頭域,通用頭域包含Cache-Control、 Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via。
HTTP/1.1 200 OK
Cache-Control:private, max-age=0, no-cache:
Control說明:
Cache-Control指定請求和響應遵循的緩存機 制。
Public指示響應可被任何緩存區緩存。
Private指示對於單個用戶的整個或部分響應消息,不能被共享緩存處理。這容許服務器僅僅描述當用戶的部分響應消息,此響應消息對於其餘用戶的請求無效。
no-cache指示請求或響應消息不能緩存
no-store用於防止重要的信息被無心的發佈。在請求消息中發送將使得請求和響應消息都不使用緩存。
max-age指示客戶機能夠接收生存期不大於指定時間(以秒爲單位)的響應。
min-fresh指示客戶機能夠接收響應時間小於當前時間加上指定時間的響應。
max-stale指示客戶機能夠接收超出超時期間的響應消息。若是指定max-stale消息的值,那麼客戶機能夠接收超出超時期指定值以內的響應消息。
web
Content-Type:image/gif
Content-Type說明:
Content-Type實體頭用於向接收方指示實體的介質類型,指定HEAD方法送到接收方的實體介質類型,或GET方法發送的請求介質類型Content-Range實體頭
apache
Date:Sat, 08 Aug 2015 03:23:23 GMT
Date說明:
Date頭域表示消息發送的時間,時間的描述格式由rfc822定義。 編程
Pragma:no-cache
Pragma說明:
Pragma頭域用來包含實現特定的指令,最經常使用的是Pragma:no-cache。在HTTP/1.1協議中,它的含義和Cache-Control:no-cache相同。 瀏覽器
Server:apache
Server說明:
Server響應頭包含處理請求的原始服務器的軟件信息。此域能包含多個產品標識和註釋,產品標識通常按照重要性排序。
緩存
Content-Length:43
重點補充:
Status-Code的第一個數字定義響應的類別,後兩個數字沒有分類的做用。第一個數字可能取5個不一樣的值:
1xx:信息響應類,表示接收到請求而且繼續處理
2xx:處理成功響應類,表示動做被成功接收、理解和接受
3xx:重定向響應類,爲了完成指定的動做,必須接受進一步處理
4xx:客戶端錯誤,客戶請求包含語法錯誤或者是不能正確執行
5xx:服務端錯誤,服務器不能正確執行一個正確的請求tomcat
以上補充有利於項目排錯哦~~很重要哦~~服務器
二、咱們根據上面的服務器相應內容來寫一個針對瀏覽器客戶端的響應:多線程
public class Server { private static final String ENTER = "\r\n"; private static final String SPACE = " "; public static void main(String[] args) { Server server = new Server(); //一、建立一個服務器端並開啓
server.start(); } public void start(){ try { ServerSocket ss = new ServerSocket(8888); //二、接收來自瀏覽器的請求
this.recevie(ss); } catch (IOException e) { e.printStackTrace(); } } private void recevie(ServerSocket ss){ BufferedReader br = null; try { Socket client = ss.accept(); //三、未來自瀏覽器的信息打印出來
br = new BufferedReader(new InputStreamReader(client.getInputStream())); StringBuilder httpRequest = new StringBuilder(); String meg = null; while(!(meg = br.readLine().trim()).equals("")){ httpRequest.append(meg); httpRequest.append("\r\n"); } System.out.println(httpRequest); this.httpResponse(client); } catch (IOException e) { e.printStackTrace(); }finally{ try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } private void httpResponse(Socket client){ BufferedWriter bw = null; try { bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); StringBuilder contextText = new StringBuilder(); contextText.append("<html><head></head><body>This is my page</body></html>"); StringBuilder sb = new StringBuilder(); /*通用頭域begin*/ sb.append("HTTP/1.1").append(SPACE).append("200").append(SPACE).append("OK").append(ENTER); sb.append("Server:myServer").append(SPACE).append("0.0.1v").append(ENTER); sb.append("Date:Sat,"+SPACE).append(new Date()).append(ENTER); sb.append("Content-Type:text/html;charset=UTF-8").append(ENTER); sb.append("Content-Length:").append(contextText.toString().getBytes().length).append(ENTER); /*通用頭域end*/ sb.append(ENTER);//空一行
sb.append(contextText);//正文部分
System.out.println(sb.toString()); bw.write(sb.toString());//寫會
bw.flush(); } catch (IOException e) { e.printStackTrace(); }finally{ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
三、封裝response和request
3.1封裝response
步驟:
A:構建報文頭
B:構建響應的HTML正文內容
C:將報文頭和HTML正文內容發送給客戶端(瀏覽器)
import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.Date; public class Response { private static final String ENTER = "\r\n"; private static final String SPACE = " "; //存儲頭信息
private StringBuilder headerInfo ; //二、存儲正文信息
private StringBuilder textContent; //三、記錄正文信息長度
private int contentLength ; //四、構建輸出流
private BufferedWriter bw ; public Response() { headerInfo = new StringBuilder(); textContent = new StringBuilder(); contentLength = 0; } public Response(OutputStream os) { this(); bw = new BufferedWriter(new OutputStreamWriter(os)); } /** * 建立頭部信息 html報文 * @param code */
private void createHeader(int code){ headerInfo.append("HTTP/1.1").append(SPACE).append(code).append(SPACE); switch (code) { case 200: headerInfo.append("OK").append(ENTER); break; case 404: headerInfo.append("NOT FOUND").append(ENTER); break; case 500: headerInfo.append("SERVER ERROR").append(ENTER); break; default: break; } headerInfo.append("Server:myServer").append(SPACE).append("0.0.1v").append(ENTER); headerInfo.append("Date:Sat,"+SPACE).append(new Date()).append(ENTER); headerInfo.append("Content-Type:text/html;charset=UTF-8").append(ENTER); headerInfo.append("Content-Length:").append(contentLength).append(ENTER); headerInfo.append(ENTER); } /** * 響應給瀏覽器解析的內容(html正文) * @param content * @return
*/
public Response htmlContent(String content){ textContent.append(content).append(ENTER); contentLength += (content+ENTER).toString().getBytes().length; return this; } /** * 發送給瀏覽器端 * @param code */
public void pushToClient(int code){ createHeader(code); try { bw.append(headerInfo.toString()); System.out.println(headerInfo.toString()); bw.append(textContent.toString()); System.out.println(textContent.toString()); bw.flush(); } catch (IOException e) { e.printStackTrace(); }finally{ try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
3.2封裝request
步驟:
A:接受瀏覽器發送的請求
B:解析瀏覽器發送來的請求
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class Request { private static final String ENTER = "\r\n"; //接收請求
private BufferedReader request ; //儲存接受信息
private String requestHeader; //經過解析頭信息獲得請求方法
private String method ; //經過解析頭信息獲得請求url
private String action ; //經過解析頭信息獲得傳過來的請求參數 ,可能存在一Key多Value的狀況因此用list
private Map<String, List<String>> parameter; //獲得瀏覽器發過來的頭信息
public Request() { requestHeader = ""; method = ""; action = ""; parameter = new HashMap<String, List<String>>(); } public Request(InputStream inputStream) { this(); request = new BufferedReader(new InputStreamReader(inputStream)); //接收到頭部信息
try { String temp; while(!(temp=request.readLine()).equals("")){ requestHeader += (temp+ENTER); } System.out.println(requestHeader); } catch (IOException e) { e.printStackTrace(); } //解析頭部信息
parseRequestHeader(); } /** * 解析頭信息 */
public void parseRequestHeader(){ //聲明一個字符串,來存放請求參數
String parameterString = ""; //讀取都頭信息的第一行
String firstLine = requestHeader.substring(0, requestHeader.indexOf(ENTER)); //開始分離第一行 //splitPoint分割點1
int splitPointOne = firstLine.indexOf("/"); method = firstLine.substring(0, splitPointOne).trim(); //splitPoint分割點2
int splitPointTwo = firstLine.indexOf("HTTP/"); String actionTemp = firstLine.substring(splitPointOne,splitPointTwo).trim(); if(method.equalsIgnoreCase("post")){ //此處代碼爲獲得post請求的參數字符串,哈哈哈哈,讀者本身想一想該怎麼寫哦~~
this.action = actionTemp; }else if(method.equalsIgnoreCase("get")){ if(actionTemp.contains("?")){ parameterString = actionTemp.substring((actionTemp.indexOf("?")+1)).trim(); this.action = actionTemp.substring(0, actionTemp.indexOf("?")); }else{ this.action = actionTemp; } //將參數封裝到Map中哦
parseParameterString(parameterString); } } /** * 解析參數字符串,將參數封裝到Map中 * @param parameterString */
private void parseParameterString(String parameterString) { if("".equals(parameterString)){ return; }else{ String[] parameterKeyValues = parameterString.split("&"); for (int i = 0; i < parameterKeyValues.length; i++) { String[] KeyValues = parameterKeyValues[i].split("="); //可能會出現有key沒有value的狀況
if(KeyValues.length == 1){ KeyValues = Arrays.copyOf(KeyValues, 2); KeyValues[1] = null; } String key = KeyValues[0].trim(); String values = null == KeyValues[1] ? null : decode(KeyValues[1].trim(),"UTF-8"); //將key和values封裝到Map中
if(!parameter.containsKey(key)){//若是不存在key,就建立一個
parameter.put(key, new ArrayList<String>()); } List<String> value = parameter.get(key); value.add(values); } } } /** * 反解碼:使用指定的編碼機制對 application/x-www-form-urlencoded 字符串解碼。 * @param string * @param encoding * @return
*/
public String decode(String string,String encoding){ try { return URLDecoder.decode(string, encoding); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } /** * 根據名字獲得多個值 * @param name * @return
*/
public String[] getParamterValues(String name){ List<String> values = parameter.get(name); if(values == null){ return null; }else{ return values.toArray(new String[0]); } } /** * 根據名字返回單個值 * @param name * @return
*/
public String getParamter(String name){ String[] value = getParamterValues(name); if(value == null){ return null; }else{ return value[0]; } } public String getAction() { return action; } public void setAction(String action) { this.action = action; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } }
3.3 此時咱們能夠將代碼優化一下
新建Servlet類來轉碼處理請求和響應的業務
/** * 專門處理請求和響應 * @author SNOOPY * */
public class Servlet { public void service(Request request, Response response){ String username = request.getParamter("user"); response.htmlContent("<html><head></head><body>This is my page<br><br>"); response.htmlContent("歡迎:"+username+" 來到個人地盤</body></html>"); } }
此時咱們的服務器端能夠簡化爲:
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * * @author SNOOPY * @version 02 */
public class Server02 { public void start(){ try { ServerSocket serverSocket = new ServerSocket(8888); //二、接收來自瀏覽器的請求
this.recevie(serverSocket); } catch (IOException e) { e.printStackTrace(); } } private void recevie(ServerSocket serverSocket){ try { Socket client = serverSocket.accept(); Servlet servlet = new Servlet(); Request request = new Request(client.getInputStream()); Response response = new Response(client.getOutputStream()); servlet.service(request, response); response.pushToClient(200); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Server02 server = new Server02(); //一、建立一個服務器端並開啓
server.start(); } }
四、多線程實現
寫到這裏咱們會發現,此時的代碼只能處理一個請求,而現實中的狀況是每每會有不少不少的客戶端發請求,因此這裏咱們要用多線程來實現
所以咱們把與客戶端瀏覽器的通訊封裝到一個線程當中。
import java.io.IOException; import java.net.Socket; /** * 分發 * @author SNOOPY * */
public class Dispatch implements Runnable{ private Socket client; private Request request; private Response response; private int code = 200; public Dispatch(Socket client) { this.client = client; try { request = new Request(client.getInputStream()); response = new Response(client.getOutputStream()); } catch (IOException e) { //e.printStackTrace();
code = 500; return ; } } @Override public void run() { Servlet servlet = new Servlet(); servlet.service(request, response); response.pushToClient(code); try { client.close(); } catch (IOException e) { e.printStackTrace(); } } }
此時咱們的服務器只能應對單一的請求與響應,結束後會自動關閉,而現實中的服務器是一直開啓等待客戶端的請求。
因此咱們的服務器端能夠優化爲:
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * * @author SNOOPY * @version 03 */
public class Server03 { private boolean isShutDown = false; /** * 啓動服務器 */
public void start(){ start(8888); } /** * 指定服務器端口 * @param port */
public void start(int port){ try { ServerSocket serverSocket = new ServerSocket(port); //二、接收來自瀏覽器的請求
this.recevie(serverSocket); } catch (IOException e) { //e.printStackTrace();
stop(); } } /** * 關閉服務器 */
private void stop() { isShutDown = true; } /** * 接受客戶端信息 * @param serverSocket */
private void recevie(ServerSocket serverSocket){ try { while(!isShutDown){ Socket client = serverSocket.accept(); new Thread(new Dispatch(client)).start(); } } catch (IOException e) { e.printStackTrace(); //若是這裏面有問題直接關閉服務器
isShutDown = true; } } public static void main(String[] args) { Server03 server = new Server03(); //一、建立一個服務器端並開啓
server.start(); } }
到此時咱們會發現咱們發過來的請求會有不少,也就意味着咱們應該會有不少的servlet,例如:RegisterServlet、LoginServlet等等還有不少其餘的訪問。
那麼咱們要用到相似於工廠模式的方法處理,來隨時產生不少的servlet,來知足不一樣的功能性的請求。
那麼咱們要抽象servlet。
在咱們抽象servlet以前,咱們先來思考一個問題:
咱們會寫不少的servlet,那麼咱們怎麼將請求與各類的servlet相匹配呢?
接下來咱們要先寫一個關於servlet的上下文,來封裝servlet與請求。說到這裏你是否是想到了什麼呢??
import java.util.HashMap; import java.util.Map; /** * servlet的上下文 * @author SNOOPY * */
public class servletContext { //經過類名建立servlet對象
private Map<String, Servlet> servlet ; //經過請求名找到對應的servlet類名
private Map<String , String> mapping ; public servletContext() { servlet = new HashMap<String,Servlet>(); mapping = new HashMap<String,String>(); } public Map<String, Servlet> getServlet() { return servlet; } public void setServlet(Map<String, Servlet> servlet) { this.servlet = servlet; } public Map<String, String> getMapping() { return mapping; } public void setMapping(Map<String, String> mapping) { this.mapping = mapping; } }
其實說白了servletContext這個類就是一個容器,用來存放請求與對應的Servlet的。
那麼接下來咱們模擬一下,往這個容器裏面存放請求與對應的Servlet,可是在這以前咱們須要有不一樣的servlet,因此咱們接下來要把Servlet進行抽象,
以便於咱們能夠隨意產生不一樣的servlet。
/** * 專門處理請求和響應 * @author SNOOPY * */
public abstract class Servlet { public void service(Request request, Response response) throws Exception{ String method = request.getMethod(); if(method.equalsIgnoreCase("post")){ this.doPost(request, response); }else if(method.equalsIgnoreCase("get")){ this.doGet(request, response); } } public void doGet(Request request, Response response) throws Exception{ } public void doPost(Request request, Response response) throws Exception{ } }
上面的抽象其實很簡單,那麼當咱們抽象結束後,咱們寫一個WebApp類來存儲一些咱們會用到的servlet上下文,而且提供獲取他們的方法:
import java.util.HashMap; import java.util.Map; /** * servlet的上下文 * @author SNOOPY * */
public class servletContext { //經過對應的servlet類名建立servlet對象
private Map<String, Servlet> servlet ; //經過請求名(action)找到對應的servlet類名
private Map<String , String> mapping ; public servletContext() { servlet = new HashMap<String,Servlet>(); mapping = new HashMap<String,String>(); } public Map<String, Servlet> getServlet() { return servlet; } public void setServlet(Map<String, Servlet> servlet) { this.servlet = servlet; } public Map<String, String> getMapping() { return mapping; } public void setMapping(Map<String, String> mapping) { this.mapping = mapping; } }
接下來咱們要修改一下分發的程序了:
import java.io.IOException; import java.net.Socket; /** * 分發 * @author SNOOPY * */
public class Dispatch implements Runnable{ private Socket client; private Request request; private Response response; private int code = 200; public Dispatch(Socket client) { this.client = client; try { request = new Request(client.getInputStream()); response = new Response(client.getOutputStream()); } catch (IOException e) { //e.printStackTrace();
code = 500; return ; } } @Override public void run() { try{ String action = request.getAction(); Servlet servlet = WebApp.getServlet(action); if(servlet == null){ this.code = 404; response.pushToClient(code); return; } servlet.service(request, response); } catch (Exception e) { e.printStackTrace(); code = 500; } response.pushToClient(code); try { client.close(); } catch (IOException e) { e.printStackTrace(); } } }
這樣一個簡易的原理過程就出來了,別急,尚未結束呢。
咱們如今寫的代碼用來存儲servlet上下文的時候用的是Servlet抽象類吧?就是說咱們每次存儲的時候都要存儲一個類,這樣是否是有點消耗內存呢?若是換成字符串呢?
還有一點就是若是咱們每次修改這個類是否是都要重啓一下服務器?是否是很麻煩??因此。。。你想到了什麼?
隨你便啦~~其實若是你寫過非框架的web應用,那麼應該接觸過配置文件。那麼應該想到的就是web.xml
接下來說一下配置文件吧,哈哈哈哈哈哈
一說到配置文件,確定是要解析配置文件的,一解析配置又牽扯到類與對象,少不了的技術就是反射機制哦!!
首先讓咱們稍微修改一下ServletContext類;
import java.util.HashMap; import java.util.Map; /** * servlet的上下文 * @author SNOOPY * */
public class ServletContext { //經過對應的servlet類名建立servlet對象 //private Map<String, Servlet> servlet ;
private Map<String, String> servlet ; //經過請求名(action)找到對應的servlet類名
private Map<String , String> mapping ; public ServletContext() { servlet = new HashMap<String,String>(); mapping = new HashMap<String,String>(); } public Map<String, String> getServlet() { return servlet; } public void setServlet(Map<String, String> servlet) { this.servlet = servlet; } public Map<String, String> getMapping() { return mapping; } public void setMapping(Map<String, String> mapping) { this.mapping = mapping; } }
那麼接下來咱們要作的就是解析XML文件而且將解析好的值放入servlet上下文中。
解析xml文件以前咱們先要用實體類來封裝xml文件
/** * * @author SNOOPY * */
public class XmlServlet { private String servlet_name; private String servlet_class; public String getServlet_name() { return servlet_name; } public void setServlet_name(String servlet_name) { this.servlet_name = servlet_name; } public String getServlet_class() { return servlet_class; } public void setServlet_class(String servlet_class) { this.servlet_class = servlet_class; } }
import java.util.ArrayList; import java.util.List; /** * 一個servlet能夠對應多個action * @author SNOOPY * */
public class XmlMapping { private String servlet_name; private List<String> url_pattern; public XmlMapping() { url_pattern = new ArrayList<String>(); } public String getServlet_name() { return servlet_name; } public void setServlet_name(String servlet_name) { this.servlet_name = servlet_name; } public List<String> getUrl_pattern() { return url_pattern; } public void setUrl_pattern(List<String> url_pattern) { this.url_pattern = url_pattern; } }
而後再解析XML文件而且將解析好的值放入servlet上下文中。
import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; /** * 存儲一些咱們會用到的servlet上下文,而且提供獲取他們的方法 * @author SNOOPY * */
public class WebApp { private static ServletContext context; static { context = new ServletContext(); //建立存放servlet上下文的容器
Map<String, String> mapping = context.getMapping(); Map<String, String> servlet = context.getServlet(); //解析配置文件,將對應的字符串存入裏面
/*補充知識: * 解析配置文件的方法有不少,最基本的是SAX解析和DOM解析:SAX解析式基於事件流的解析,DOM解析是基於XML文檔樹結構的解析 * 另外還有DOM4J和JDOM均可以解析。 * DOM和SAX的區別: * DOM解析適合於對文件進行修改和隨機存取的操做,可是不適合於大型文件的操做; * SAX採用部分讀取的方式,因此能夠處理大型文件,並且只須要從文件中讀取特定內容,SAX解析能夠由用戶本身創建本身的對象模型。 * 因此DOM解析適合於修改,SAX解析適合於讀取大型文件,2者結合的話能夠用JDOM * * 本次示例爲了方便就選擇SAX解析,步驟一共分三步: * 一、得到解析工程類。 * 二、工程獲取解析器 * 三、加載文檔註冊處理器 */
//一、得到工廠類
SAXParserFactory factory = SAXParserFactory.newInstance(); try { //二、從解析工程獲取解析器
SAXParser parser = factory.newSAXParser(); //三、加載文檔並註冊處理器(handle)。注意:此處的文檔能夠用file的形式也能夠用流的形式,隨便,便於學習,下面提供兩種。 //String filePath = ""; //parser.parse(new File(filePath), handler);
XMLHandler handler = new XMLHandler(); InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/httpservlet/WEB-INF/web.xml"); parser.parse(is, handler); List<XmlServlet> serv = handler.getServlet(); for (XmlServlet xmlServlet : serv) { servlet.put(xmlServlet.getServlet_name(), xmlServlet.getServlet_class()); } List<XmlMapping> map = handler.getMapping(); for (XmlMapping maps : map) { List<String> actions = maps.getUrl_pattern(); for (String action : actions) { mapping.put(action, maps.getServlet_name()); } } } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static Servlet getServlet(String action){ if("".equals(action) || action == null){ return null; } //經過action找到servlet-name
String servlet_name = context.getMapping().get(action); //經過反射,找到相應的類,建立其對象並返回
String classPath = context.getServlet().get(servlet_name);//經過action獲得類路徑
Servlet servlet = null; if(classPath != null){ Class<?> clazz = null; try { clazz = Class.forName(classPath); servlet = (Servlet)clazz.newInstance();//要確保空構造存在
} catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return servlet; } }
這裏還須要一個XMLHandler處理器來處理xml文件
import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * 存儲對象 * @author SNOOPY * */
public class XMLHandler extends DefaultHandler { private List<XmlServlet> servlet ; private List<XmlMapping> mapping ; public List<XmlServlet> getServlet() { return servlet; } public void setServlet(List<XmlServlet> servlet) { this.servlet = servlet; } public List<XmlMapping> getMapping() { return mapping; } public void setMapping(List<XmlMapping> mapping) { this.mapping = mapping; } private XmlServlet serv ; private XmlMapping map ; private String beginTag; private boolean isMap; @Override public void startDocument() throws SAXException { servlet = new ArrayList<XmlServlet>(); mapping = new ArrayList<XmlMapping>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if(null!=qName){ beginTag = qName; if(qName.equals("servlet")){ serv = new XmlServlet(); isMap = false; }else if(qName.equals("servlet-mapping")){ map = new XmlMapping(); isMap = true; } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { if(beginTag != null){ String info = new String(ch, start, length); if(isMap){ if(beginTag.equals("servlet-name")){ map.setServlet_name(info.trim()); }else if(beginTag.equals("url-pattern")){ map.getUrl_pattern().add(info); } }else{ if(beginTag.equals("servlet-name")){ serv.setServlet_name(info); }else if(beginTag.equals("servlet-class")){ serv.setServlet_class(info); } } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if(null!=qName){ if(qName.equals("servlet")){ servlet.add(serv); }else if(qName.equals("servlet-mapping")){ mapping.add(map); } } beginTag = null; } @Override public void endDocument() throws SAXException { //文檔結束
} }
萬事具有隻欠東風!---->處理一下分發的類:
import java.io.IOException; import java.net.Socket; /** * 分發 * @author SNOOPY * */
public class Dispatch implements Runnable{ private Socket client; private Request request; private Response response; private int code = 200; public Dispatch(Socket client) { this.client = client; try { request = new Request(client.getInputStream()); response = new Response(client.getOutputStream()); } catch (IOException e) { //e.printStackTrace();
code = 500; return ; } } @Override public void run() { try{ String action = request.getAction(); Servlet servlet = WebApp.getServlet(action); if(servlet == null){ this.code = 404; response.pushToClient(code); return; } servlet.service(request, response); /*String method = request.getMethod(); if(method.equalsIgnoreCase("post")){ servlet.doPost(request, response); }else if(method.equalsIgnoreCase("get")){ servlet.doGet(request, response); }*/ } catch (Exception e) { e.printStackTrace(); code = 500; } response.pushToClient(code); try { client.close(); } catch (IOException e) { e.printStackTrace(); } } }
通過以上的分析以及代碼的書寫,一個簡易的web服務器就這樣誕生了!
各位看官能夠動手嘗試一下哦~~
固然代碼有不少的地方還很值得去優化和修正,但願有心的大神能指正錯誤,我會及時修正!!
注意:此文僅適用於剛入門的同窗,幫助理解服務器原理。
附:手寫服務器包結構(最終整理)