在寫這篇博客之前我查了很久發現全網都沒有一篇寫httpserver源碼解析的
所以今天就由我來為大家解析一下httpserver的源碼。(這里我會去掉其中的https部分的源碼,只講http部分,對httpserver中https的實現感興趣的讀者可以嘗試自己去閱讀,這部分並不復雜)
第一次在沒有參考資料的情況下寫這么長一篇源碼解析,可能會有很多錯誤和講不清楚的地方,希望大家盡量指出來。
本文鏈接 https://www.cnblogs.com/fatmanhappycode/p/12614428.html
httpserver的簡單使用例子
大家最好先跟着我構建這樣一個小demo,跑起來之后再一步一步去看源碼
/** * @author 肥宅快樂碼 */ public class HttpServerSample { private static void serverStart() throws IOException { HttpServerProvider provider = HttpServerProvider.provider(); // 監聽端口8080,連接排隊隊列,如果隊列中的連接超過這個數的話就會拒絕連接 HttpServer httpserver =provider.createHttpServer(new InetSocketAddress(8080), 100); // 監聽路徑為RestSample,請求處理后回調RestGetHandler里的handle方法 httpserver.createContext("/RestSample", new RestGetHandler()); // 管理工作線程池 ExecutorService executor = new ThreadPoolExecutor(10,200,60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy()); httpserver.setExecutor(executor); httpserver.start(); System.out.println("server started"); } public static void main(String[] args) throws IOException { serverStart(); } } /** * 回調類,里面的handle方法主要完成將包裝好的請求頭返回給客戶端的功能 */ class RestGetHandler implements HttpHandler { @Override public void handle(HttpExchange he) throws IOException { String requestMethod = he.getRequestMethod(); // 如果是get方法 if ("GET".equalsIgnoreCase(requestMethod)) { // 獲取響應頭,接下來我們來設置響應頭信息 Headers responseHeaders = he.getResponseHeaders(); // 以json形式返回,其他還有text/html等等 responseHeaders.set("Content-Type", "application/json"); // 設置響應碼200和響應body長度,這里我們設0,沒有響應體 he.sendResponseHeaders(200, 0); // 獲取響應體 OutputStream responseBody = he.getResponseBody(); // 獲取請求頭並打印 Headers requestHeaders = he.getRequestHeaders(); Set<String> keySet = requestHeaders.keySet(); Iterator<String> iter = keySet.iterator(); while (iter.hasNext()) { String key = iter.next(); List values = requestHeaders.get(key); String s = key + " = " + values.toString() + "\r\n"; responseBody.write(s.getBytes()); } // 關閉輸出流 responseBody.close(); } } }
httpserver初始化及啟動源碼
初始化
① 最開始我們通過 HttpServerProvider provider = HttpServerProvider.provider(); 創建了一個HttpServerProvider,也就是這里的DefaultHttpServerProvider
// HttpServerProvider.java public static HttpServerProvider provider () { // 這里我們刪掉了其他部分,只留下172、173兩行 // 這里創建了一個DefaultHttpServerProvider provider = new sun.net.httpserver.DefaultHttpServerProvider(); return provider; }
② 之后我們調用 HttpServer httpserver =provider.createHttpServer(new InetSocketAddress(8080), 100); ,
也就是調用了DefaultHttpServerProvider的createHttpServer創建一個HttpServerImpl,當然這里也可以用createHttpsServer創建一個HttpsServerImpl,但是前面說了我們這篇不分析https,所以這里忽略了createHttpsServer方法
還有這里創建ServerImpl的構造方法我們暫時不講,留到后面再講
// DefaultHttpServerProvider.java public HttpServer createHttpServer (InetSocketAddress addr, int backlog) throws IOException { return new HttpServerImpl (addr, backlog); } // HttpServerImpl.java HttpServerImpl ( InetSocketAddress addr, int backlog ) throws IOException { server = new ServerImpl (this, "http", addr, backlog); }
③ 接下來我們創建了一個監聽路徑 httpserver.createContext("/RestSample", new RestGetHandler());
// HttpServer.java public abstract HttpContext createContext (String path, HttpHandler handler) ; // HttpContextImpl.java public HttpContextImpl createContext (String path, HttpHandler handler) { // 這里調用的server是ServerImpl類的對象 return server.createContext (path, handler); }
這里成功返回了一個HttpContextImpl對象,這個我們后面會說,這里我們要知道的是,HttpServerImpl調用的是ServerImpl的實現
到這里我們差不多可以聊一下httpserver的主要結構了:
主要結構
HttpServer是這里的祖先類,它是一個抽象類,抽象了一個HttpServer應該有的方法
而HttpsServer和我們想象的不一樣,它和HttpServer不是平行關系,而是HttpServer的子類,它在HttpServer的基礎上加了setHttpsConfigurator和getHttpsConfigurator這兩個方法而已
HttpServerImpl和HttpsServerImpl雖然都是實現類,但是它們的方法都是調用ServerImpl的方法,都是圍繞ServerImpl的
所以我們也可以把ServerImpl看做這個項目的核心類
④ 之后設置一下工作線程池,初始化任務就完成了
ExecutorService executor = new ThreadPoolExecutor(10,200,60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadPoolExecutor.AbortPolicy()); httpserver.setExecutor(executor);
啟動
httpserver.start();
啟動自然和我們剛剛聊的結構一樣都是從HttpServer開始一層調一層調用到ServerImpl的方法的:
// HttpServer.java public abstract void start () ; // HttpServerImpl.java public void start () { server.start(); } // ServerImpl.java public void start () { // server未綁定端口或處於已啟動或已關閉狀態 // 順便先提一下,這里就可以留意到,ServerImpl作為一個核心類,管理了各種各樣的狀態(state)等 if (!bound || started || finished) { throw new IllegalStateException ("server in wrong state"); } // 如果沒有設置線程池,那就用默認的,默認的話等於沒有用線程池,是直接execute的,所以盡可能直接創建線程池 if (executor == null) { executor = new DefaultExecutor(); } // 創建了一個Dispatcher線程,用來分發任務,如Accept或者Readable Thread t = new Thread (dispatcher); // 設置一下狀態 started = true; // 運行線程 t.start(); }
ServerImpl結構圖
前面我們說過,ServerImpl是這整個項目的核心部分,它管理了httpserver的狀態,提供了各種接口以及通用的方法,它也負責了幾個內部類線程的啟動
所以,接下來我們會分為ServerImpl、Dispatcher、Exchange、ServerTimerTask與ServerTimerTask1四個部分來講解
ServerImpl
主要屬性
(https相關的我去掉了)
比較長,大家稍微過一眼有個印象,之后遇到的時候再回來看就行
// http或https private String protocol; private Executor executor; // 負責接收連接用的類(這個本來在209行附近,我把它提上來了) private Dispatcher dispatcher; // ContextList這個類只是封裝了一個List<HttpContextImpl>及一些方法,如限制監聽的context(路徑)的數目和查找context的方法 private ContextList contexts; private InetSocketAddress address; // nio相關的那些類 private ServerSocketChannel schan; private Selector selector; private SelectionKey listenerKey; // 負責管理之前提到的idle連接,也就是長連接的set // 長連接時,連接如果沒有任務,就加進去. 如果超過一定時間沒有任務,則主動斷開長連接 private Set<HttpConnection> idleConnections; // 管理所有的連接,方便在stop等情況下直接斷開所有連接 private Set<HttpConnection> allConnections; // 管理req連接和rsp連接,防止請求或響應超時,超時時由定時線程斷開連接 private Set<HttpConnection> reqConnections; private Set<HttpConnection> rspConnections; // 這兩個之后6.4的Exchange的addEvent方法部分我們再說 private List<Event> events; private final Object loLock = new Object(); // 各種狀態,相信大家看得懂是什么意思 private volatile boolean finished = false; private volatile boolean terminating = false; private boolean bound = false; private boolean started = false; // 系統時間,會由ServerTimerTask進行更新 private volatile long time; // 這個似乎並沒有任何用 private volatile long subticks = 0; // 這個是用來記錄一共更新了多少次time的,相當於時間戳一樣的東西 private volatile long ticks; // 把HttpServer包裝進來,方便調用 private HttpServer wrapper; // 這個的意思是ServerTimerTask每隔多長時間定期run一下,因為ServerTimerTask是一個定時任務線程 // 默認是10000ms也就是10秒一次 private final static int CLOCK_TICK = ServerConfig.getClockTick(); // 這個是允許長連接駐留的時間,默認是30秒 private final static long IDLE_INTERVAL = ServerConfig.getIdleInterval(); // 允許最大長連接數,默認200 private final static int MAX_IDLE_CONNECTIONS = ServerConfig.getMaxIdleConnections(); // ServerTimerTask1的定期時間,默認是1秒 private final static long TIMER_MILLIS = ServerConfig.getTimerMillis (); // 最后這兩個默認為-1,至於為什么是-1后面ServerTimerTask部分我們會說 private final static long MAX_REQ_TIME = getTimeMillis(ServerConfig.getMaxReqTime()); private final static long MAX_RSP_TIME=getTimeMillis(ServerConfig.getMaxRspTime()); private final static boolean REQ_RSP_CLEAN_ENABLED = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1; // ServerTimerTask和ServerTimerTask1的對象,跑起來就是ServerTimerTask和ServerTimerTask1線程了 private Timer timer, timer1; private Logger logger;
構造方法
這就是剛剛2.1小節中提到的ServerImpl的構造方法,沒什么要講的,無非就是初始化了變量並啟動了ServerTimerTask和ServerTimerTask1線程
ServerImpl ( HttpServer wrapper, String protocol, InetSocketAddress addr, int backlog ) throws IOException { this.protocol = protocol; this.wrapper = wrapper; this.logger = Logger.getLogger ("com.sun.net.httpserver"); ServerConfig.checkLegacyProperties (logger); this.address = addr; contexts = new ContextList(); schan = ServerSocketChannel.open(); if (addr != null) { ServerSocket socket = schan.socket(); socket.bind (addr, backlog); bound = true; } selector = Selector.open (); schan.configureBlocking (false); listenerKey = schan.register (selector, SelectionKey.OP_ACCEPT); dispatcher = new Dispatcher(); idleConnections = Collections.synchronizedSet (new HashSet<HttpConnection>()); allConnections = Collections.synchronizedSet (new HashSet<HttpConnection>()); reqConnections = Collections.synchronizedSet (new HashSet<HttpConnection>()); rspConnections = Collections.synchronizedSet (new HashSet<HttpConnection>()); time = System.currentTimeMillis(); timer = new Timer ("server-timer", true); // 可以看到,在初始化階段兩個定時任務就已經啟動了 timer.schedule (new ServerTimerTask(), CLOCK_TICK, CLOCK_TICK); if (timer1Enabled) { timer1 = new Timer ("server-timer1", true); timer1.schedule (new ServerTimerTask1(),TIMER_MILLIS,TIMER_MILLIS); logger.config ("HttpServer timer1 enabled period in ms: "+TIMER_MILLIS); logger.config ("MAX_REQ_TIME: "+MAX_REQ_TIME); logger.config ("MAX_RSP_TIME: "+MAX_RSP_TIME); } events = new LinkedList<Event>(); logger.config ("HttpServer created "+protocol+" "+ addr); }
當然ServerImpl有很多通用的方法,但是這里我們不講,等到用到它們的時候我們再講,這樣比較方便了解這些通用方法的具體用途
Dispatcher
先來看它的run方法
run()
public void run() { // 如果已經完全關閉服務器,那就不用任何處理了 while (!finished) { try { // ================這段大概就是把處理完成返回結果完畢的連接注冊進idle長連接里面,后面流程經過再細講===================================== List<Event> list = null; synchronized (lolock) { if (events.size() > 0) { list = events; events = new LinkedList<Event>(); } } if (list != null) { for (Event r: list) { handleEvent (r); } } for (HttpConnection c : connsToRegister) { reRegister(c); } connsToRegister.clear(); // ======================================================================================================================== // 阻塞,超過1000ms就繼續運行 selector.select(1000); /* process the selected list now */ Set<SelectionKey> selected = selector.selectedKeys(); Iterator<SelectionKey> iter = selected.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); iter.remove (); // 這里listenrKey是accept事件,相當於key.isAcceptable() if (key.equals (listenerKey)) { // 如果正在關閉服務器,那就不用處理了,直接把新的連接continue然后remove掉就可以了 if (terminating) { continue; } SocketChannel chan = schan.accept(); // 根據需要開啟TCPNoDelay,也就是關閉Nagle算法,減小緩存帶來的延遲 if (ServerConfig.noDelay()) { chan.socket().setTcpNoDelay(true); } if (chan == null) { continue; /* cancel something ? */ } chan.configureBlocking (false); SelectionKey newkey = chan.register (selector, SelectionKey.OP_READ); // 創建connection並把channel放進去 HttpConnection c = new HttpConnection (); c.selectionKey = newkey; c.setChannel (chan); // 把connection緩存到Key中 newkey.attach (c); // 請求開始,注冊到reqConnections中 requestStarted (c); allConnections.add (c); } else { try { if (key.isReadable()) { boolean closed; SocketChannel chan = (SocketChannel)key.channel(); // 這里把剛剛attach緩存的connection取出來了 HttpConnection conn = (HttpConnection)key.attachment(); // 這里的這種先取消注冊並設置為阻塞的讀取方式與多次讀取有關 // 因為后面是先讀頭部,之后再讀取body等其他部分的 key.cancel(); chan.configureBlocking (true); // 如果這個connection是之前保存着的空閑長連接,那么直接移出idleConnections中 // 並加入reqConnections開始請求(因為io流都初始化好了,可以直接用) if (idleConnections.remove(conn)) { // 加入reqConnections開始請求 requestStarted (conn); } // 調用handle進行后續處理 handle (chan, conn); } else { assert false; } } catch (CancelledKeyException e) { handleException(key, null); } catch (IOException e) { handleException(key, e); } } } // 調用select去掉cancel了的key selector.selectNow(); } catch (IOException e) { logger.log (Level.FINER, "Dispatcher (4)", e); } catch (Exception e) { logger.log (Level.FINER, "Dispatcher (7)", e); } } try {selector.close(); } catch (Exception e) {} }
這里稍微總結一下,Dispatcher的run主要就是完成socket連接的Accept和Readable事件的分發功能,其中accept分發給它自己,它自己創建channel並注冊,自己創建連接並緩存。而Readable事件則在經過簡單處理后交給handle去調用Exchange線程繼續進行后續任務
handle(SocketChannel, HttpConnection)
public void handle (SocketChannel chan, HttpConnection conn) throws IOException { try { // 構造一個Exchange后讓executor線程池去執行,這里相當於一個異步任務 · // 在將任務交給executor后,dispatcher就可以返回了 Exchange t = new Exchange (chan, protocol, conn); executor.execute (t); } catch (HttpError e1) { logger.log (Level.FINER, "Dispatcher (4)", e1); closeConnection(conn); } catch (IOException e) { logger.log (Level.FINER, "Dispatcher (5)", e); closeConnection(conn); } }
Exchange
既然前面把任務丟給了Exchange,那么接下來我們就來看Exchange的run方法在做什么
run()
public void run () { // context對應着這個http請求訪問的路徑和處理器, // 而一個未解析http請求自然context為null,也就是不知道這個請求是想請求哪個路徑的 context = connection.getHttpContext(); boolean newconnection; try { // 這里是已經解析過的http請求才會進去,因為它們有context // 為什么會有解析過的http請求呢?想想長連接,前面Dispatcher的75、76行我們提到過 // 長連接也就是idleConnection會緩存那些io流在connection里面,當然也包括context //(但是只是存在,並不代表context不需要重新解析,畢竟再次請求時請求的資源鏈接不一定相同) if (context != null ) { this.rawin = connection.getInputStream(); this.rawout = connection.getRawOutputStream(); newconnection = false; } else { newconnection = true; if (https) { // . . . . . . } else { // 這里Request的兩種stream都封裝了一些讀寫方法,比較繁瑣所以不分析了 rawin = new BufferedInputStream( new Request.ReadStream ( ServerImpl.this, chan )); rawout = new Request.WriteStream ( ServerImpl.this, chan ); } connection.raw = rawin; connection.rawout = rawout; } Request req = new Request (rawin, rawout); requestLine = req.requestLine(); // 讀取請求的一行后,如果請求為空就關閉connection // 那么什么情況為空呢?大家都知道,http請求大體分三部分, // 1.三次握手連接,被封裝成socket的accept // 2.開始發送內容,被封裝成socket的readable事件 // 那么四次揮手呢?其實也是readable,但是其內容為空 // 所以這里其實是揮手關閉連接的意思 if (requestLine == null) { closeConnection(connection); return; } // 獲取請求類型(GET/POST...) int space = requestLine.indexOf (' '); if (space == -1) { reject (Code.HTTP_BAD_REQUEST, requestLine, "Bad request line"); return; } String method = requestLine.substring (0, space); // 獲取請求的url int start = space+1; space = requestLine.indexOf(' ', start); if (space == -1) { reject (Code.HTTP_BAD_REQUEST, requestLine, "Bad request line"); return; } String uriStr = requestLine.substring (start, space); URI uri = new URI (uriStr); // http請求版本(1.0/1.1...) start = space+1; String version = requestLine.substring (start); Headers headers = req.headers(); // 如果是采用Transfer-encoding,那么解析body的方式不同, // 而且Context-Length將被忽略,所以標記為長度clen = -1 // 具體可以去了解一下Transfer-encoding String s = headers.getFirst ("Transfer-encoding"); long clen = 0L; if (s !=null && s.equalsIgnoreCase ("chunked")) { clen = -1L; } else { // 沒用Transfer-encoding而用了Content-Length s = headers.getFirst ("Content-Length"); if (s != null) { clen = Long.parseLong(s); } if (clen == 0) { // 如果主體長度為0,那么請求已經結束,這里將connection從 // reqConnections中移出,並添加當前時間,加入rspConnections requestCompleted (connection); } } // 這里就是最開始ServerImpl屬性(可以回去看)里ContextList里封裝的方法 // 用來查詢是否有匹配的context路徑 ctx = contexts.findContext (protocol, uri.getPath()); if (ctx == null) { reject (Code.HTTP_NOT_FOUND, requestLine, "No context found for request"); return; } connection.setContext (ctx); // 如果沒有回調方法,也就是最開始demo里自定義的RestGetHandler類 if (ctx.getHandler() == null) { reject (Code.HTTP_INTERNAL_ERROR, requestLine, "No handler for context"); return; } // 相當於http請求的完整封裝,后面再包上一層HttpExchangeImpl就是 // RestGetHandler類里的回調方法handle的參數了 tx = new ExchangeImpl ( method, uri, req, clen, connection ); // 看看有沒有connection:close參數,1.0默認close,需要手動開啟keep-alive String chdr = headers.getFirst("Connection"); Headers rheaders = tx.getResponseHeaders(); if (chdr != null && chdr.equalsIgnoreCase ("close")) { tx.close = true; } if (version.equalsIgnoreCase ("http/1.0")) { tx.http10 = true; if (chdr == null) { tx.close = true; rheaders.set ("Connection", "close"); } else if (chdr.equalsIgnoreCase ("keep-alive")) { rheaders.set ("Connection", "keep-alive"); int idle=(int)(ServerConfig.getIdleInterval()/1000); int max=ServerConfig.getMaxIdleConnections(); String val = "timeout="+idle+", max="+max; rheaders.set ("Keep-Alive", val); } } // 是新連接而不是長連接的話,給connection賦值一下 if (newconnection) { connection.setParameters ( rawin, rawout, chan, engine, sslStreams, sslContext, protocol, ctx, rawin ); } // 如果客戶端發出expect:100-continue,意思就是客戶端想要post東西(一般是比較大的),詢問是否同意 // 返回響應碼100后客戶端才會繼續post數據 String exp = headers.getFirst("Expect"); if (exp != null && exp.equalsIgnoreCase ("100-continue")) { logReply (100, requestLine, null); sendReply ( Code.HTTP_CONTINUE, false, null ); } // 獲取一下系統自帶的過濾器sf或者用戶自定義的過濾器uf,這里都默認為無 List<Filter> sf = ctx.getSystemFilters(); List<Filter> uf = ctx.getFilters(); // 構造成一個鏈表,以鏈表的形式一層一層調用過濾器 Filter.Chain sc = new Filter.Chain(sf, ctx.getHandler()); Filter.Chain uc = new Filter.Chain(uf, new LinkHandler (sc)); // 初始化一下包裝的io流,這里我把getRequestBody拿過來,兩個大同小異 /** *public InputStream getRequestBody () { * if (uis != null) { * return uis; * } * if (reqContentLen == -1L) { * uis_orig = new ChunkedInputStream (this, ris); * uis = uis_orig; * } else { * uis_orig = new FixedLengthInputStream (this, ris, reqContentLen); * uis = uis_orig; * } * return uis; *} */ tx.getRequestBody(); tx.getResponseBody(); if (https) { uc.doFilter (new HttpsExchangeImpl (tx)); } else { // 開始執行過濾方法,參數和我剛剛提到的一樣,就是包成HttpExchangeImpl的ExchangeImpl // 接下來我們就往這里看 uc.doFilter (new HttpExchangeImpl (tx)); } } catch (IOException e1) { logger.log (Level.FINER, "ServerImpl.Exchange (1)", e1); closeConnection(connection); } catch (NumberFormatException e3) { reject (Code.HTTP_BAD_REQUEST, requestLine, "NumberFormatException thrown"); } catch (URISyntaxException e) { reject (Code.HTTP_BAD_REQUEST, requestLine, "URISyntaxException thrown"); } catch (Exception e4) { logger.log (Level.FINER, "ServerImpl.Exchange (2)", e4); closeConnection(connection); } }
doFilter()
// Filter.java的Chain內部類 public void doFilter (HttpExchange exchange) throws IOException { // 遞歸調用直到沒有filter時,調用自定義的回調方法,也就是RestGetHandler的handle方法 if (!iter.hasNext()) { handler.handle (exchange); } else { Filter f = iter.next(); f.doFilter (exchange, this); } }
我重新貼一遍demo里的RestGetHandler給大家看(17和32行的注釋有改動,注意看):
/** * 回調類,里面的handle方法主要完成將包裝好的請求頭返回給客戶端的功能 */ class RestGetHandler implements HttpHandler { @Override public void handle(HttpExchange he) throws IOException { String requestMethod = he.getRequestMethod(); // 如果是get方法 if ("GET".equalsIgnoreCase(requestMethod)) { // 獲取響應頭,接下來我們來設置響應頭信息 Headers responseHeaders = he.getResponseHeaders(); // 以json形式返回,其他還有text/html等等 responseHeaders.set("Content-Type", "application/json"); // 設置響應碼200和響應body長度,這里我們設0,沒有響應體,這里也初始化了io流 // 這里如果為0,則初始化ChunkedOutputStream或UndefLengthOutputStream // 如果不為0,則初始化FixedLengthOutputStream he.sendResponseHeaders(200, 0); // 獲取響應體 OutputStream responseBody = he.getResponseBody(); // 獲取請求頭並打印 Headers requestHeaders = he.getRequestHeaders(); Set<String> keySet = requestHeaders.keySet(); Iterator<String> iter = keySet.iterator(); while (iter.hasNext()) { String key = iter.next(); List values = requestHeaders.get(key); String s = key + " = " + values.toString() + "\r\n"; responseBody.write(s.getBytes()); } // 關閉輸出流,也就是關閉ChunkedOutputStream // 接下來看這里 responseBody.close(); } } }
在回調方法完成返回數據給客戶端的任務后,調用了close方法
close()
這里我們重點關注最后一行代碼
public void close () throws IOException { if (closed) { return; } flush(); try { writeChunk(); out.flush(); LeftOverInputStream is = t.getOriginalInputStream(); if (!is.isClosed()) { is.close(); } } catch (IOException e) { } finally { closed = true; } WriteFinishedEvent e = new WriteFinishedEvent (t); // 這里我們只關注最后一行,其他的不關注 // 這行調用了addEvent方法 t.getHttpContext().getServerImpl().addEvent (e); }
addEvent()
// 這里就調用了4.1中ServerImpl的屬性的第28、29、30行的內容 void addEvent (Event r) { // 而這里的鎖,就是防止Dispatcher的run方法最前面那里 // 防止它取出events時與這里的add產生沖突 synchronized (lolock) { events.add (r); // 這里的wakeup就是往管道里輸入一個字節喚醒Dispatcher里 // 的selector.select(1000),讓它不再阻塞,去取出events selector.wakeup(); } }
到這里Exchange的工作就完成了,接下來我來稍微總結一下:
- 首先Exchange對http請求進行解析和封裝,匹配相應的context的handle,初始化一下io流
- 然后Exchange調用相應的回調handle方法進行處理
- handle方法一般都是我們自己寫的響應方法,我這里自定義的RestGetHandler的handle方法負責把請求頭作為內容響應回去,也就是下圖這種效果
- 然后handle方法調用io流的close關閉io流,表示響應結束
- 並調用addEvent方法把ExchangeImpl封裝成event放進List里面,至於為什么要這么做我們接下來繼續分析
既然有地方加入List,那自然有地方取出List,回憶一下,我們剛剛見到List<Event>的主要有兩個地方
一個是ServerImpl屬性里的28~30行,也就是說它是ServerImpl的屬性
還有一個地方則是Dispatcher類的run方法里,我說了后面再細講,大家可以回去瞄一眼在什么位置
接下來我們就來講這個部分:
public void run() { // 如果已經完全關閉服務器,那就不用任何處理了 while (!finished) { try { // 這里就把events取出來放到list里面了,並把events重新賦值空對象 List<Event> list = null; // 還記得我們剛剛說過,lolock鎖是防止addEvent操作和取操作沖突的 synchronized (lolock) { if (events.size() > 0) { list = events; events = new LinkedList<Event>(); } } // 之后遍歷取出每個event,並調用handleEvent方法 if (list != null) { for (Event r: list) { // 接下來看這里 handleEvent (r); } } for (HttpConnection c : connsToRegister) { reRegister(c); } connsToRegister.clear();
handleEvent(Event)
/** * 處理event,將長連接加入等待重新注冊的connectionsToRegister列表中 */ private void handleEvent (Event event) { ExchangeImpl t = event.exchange; HttpConnection c = t.getConnection(); try { if (event instanceof WriteFinishedEvent) { if (terminating) { finished = true; } // 完成響應,處理一些狀態,可以自己去看,沒幾行 responseCompleted (c); LeftOverInputStream is = t.getOriginalInputStream(); if (!is.isEOF()) { t.close = true; } // 如果空閑的連接超過MAX_IDLE_CONNECTIONS(默認200,可以看之前ServerImpl的屬性), // 則不能再添加了,並且關閉連接 if (t.close || idleConnections.size() >= MAX_IDLE_CONNECTIONS) { c.close(); allConnections.remove (c); } else { if (is.isDataBuffered()) { requestStarted (c); handle (c.getChannel(), c); } else { // 將連接加入connectionsToRegister列表中等待重新注冊進 connectionsToRegister.add (c); } } } } catch (IOException e) { logger.log ( Level.FINER, "Dispatcher (1)", e ); c.close(); } }
之后就是遍歷connectionsToRegister列表並將連接注冊進idleConnections長連接set中
for (HttpConnection c : connsToRegister) { // 接下來看這里 reRegister(c); } connsToRegister.clear();
reRegister()
/** * 把之前cancel的key重新用非阻塞的方式監聽起來 * 並且把連接加入idleConnections空閑連接中 */ void reRegister (HttpConnection connection) { try { SocketChannel chan = connection.getChannel(); chan.configureBlocking (false); SelectionKey key = chan.register (selector, SelectionKey.OP_READ); key.attach (connection); connection.time = getTime() + IDLE_INTERVAL; idleConnections.add (connection); } catch (IOException e) { logger.log(Level.FINER, "Dispatcher(8)", e); connection.close(); } }
就這樣,完成響應的請求就在idleConnection中緩存起來
整體流程圖
從一個HTTP請求講起
上面是我抓的包,可以看到一個http請求一共三部分組成,第一部分是tcp三次握手連接服務端,第二部分是傳輸信息主體,第三部分就是tcp四次揮手斷開連接
而這三部分的tcp操作都對應抽象成了socket的操作,所謂socket,其實就是對tcp和udp的一個上層抽象,方便程序員調用的
其中最明顯的,就是accept對應三次握手操作了
所以接下來,我們的流程圖就會從一次http請求開始,展示這三個部分分別對應項目的哪些部分,讓讀者有一個更清晰的理解
如果還是不理解的話,建議對着圖重新看一遍這篇文章
最后,在這個過程中,有調用到ServerImpl的requestStarted()方法,以及我沒有標出來的requestCompleted和close時調用的responseCompleted(這兩個這篇文章里沒有,可以自己追蹤去看一下在哪里調用了),這些方法都是對ServerImpl的屬性:
private Set<HttpConnection> idleConnections; // 管理所有的連接,方便在stop等情況下直接斷開所有連接 private Set<HttpConnection> allConnections; // 管理req連接和rsp連接,防止請求或響應超時,超時時由定時線程斷開連接 private Set<HttpConnection> reqConnections; private Set<HttpConnection> rspConnections;
做了一系列添加刪除操作代表請求開始,請求結束,響應開始,響應結束和代表長連接被緩存起來等,那么這些到底有什么用呢?緩存connection嗎?並不是。connection是緩存在key里面的,通過attachment獲得。其實他們的真實作用是方便在超時的時候由定時任務去清理它們。
定時任務ServerTimerTask和ServerTimerTask1
// 前面我們在ServerImpl的構造方法說過,這兩個定時任務都已經運行了 // 這個負責清理長連接的是10秒(ServerImpl里的CLOCK_TICK)運行一次 class ServerTimerTask extends TimerTask { public void run () { LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>(); time = System.currentTimeMillis(); ticks ++; synchronized (idleConnections) { for (HttpConnection c : idleConnections) { if (c.time <= time) { toClose.add (c); } } for (HttpConnection c : toClose) { idleConnections.remove (c); allConnections.remove (c); // 這里調用HTTPConnection的close方法,方法里清理輸入輸出流和關閉channel等 c.close(); } } } } // 這個是每秒(TIMER_MILLIS)執行一次 class ServerTimerTask1 extends TimerTask { // runs every TIMER_MILLIS public void run () { LinkedList<HttpConnection> toClose = new LinkedList<HttpConnection>(); time = System.currentTimeMillis(); synchronized (reqConnections) { if (MAX_REQ_TIME != -1) { for (HttpConnection c : reqConnections) { if (c.creationTime + TIMER_MILLIS + MAX_REQ_TIME <= time) { toClose.add (c); } } for (HttpConnection c : toClose) { logger.log (Level.FINE, "closing: no request: " + c); reqConnections.remove (c); allConnections.remove (c); c.close(); } } } toClose = new LinkedList<HttpConnection>(); synchronized (rspConnections) { if (MAX_RSP_TIME != -1) { for (HttpConnection c : rspConnections) { if (c.rspStartedTime + TIMER_MILLIS +MAX_RSP_TIME <= time) { toClose.add (c); } } for (HttpConnection c : toClose) { logger.log (Level.FINE, "closing: no response: " + c); rspConnections.remove (c); allConnections.remove (c); c.close(); } } } } }
本來只是想簡單寫一個httpserver玩玩的,但是查了網上很多資料,發現代碼質量有些參差不齊,所以就干脆直接參考了jdk里的httpserver的源碼了,總體感覺很簡潔。當然如果沒有特殊需要的話,還是讀集合類juc之類的源碼比較有價值一些。
最后老習慣再附一圖:
熬夜變垃圾!(;´Д`)