jdk下httpserver源碼解析

在寫這篇部落格之前我查了很久發現全網都沒有一篇寫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() + "rn";                  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這兩個方法而已

HttpServerImplHttpsServerImpl雖然都是實現類,但是它們的方法都是調用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() + "rn";                  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的工作就完成了,接下來我來稍微總結一下:

  1. 首先Exchange對http請求進行解析和封裝,匹配相應的context的handle,初始化一下io流
  2. 然後Exchange調用相應的回調handle方法進行處理
  3. handle方法一般都是我們自己寫的響應方法,我這裡自定義的RestGetHandler的handle方法負責把請求頭作為內容響應回去,也就是下圖這種效果
  4.  然後handle方法調用io流的close關閉io流,表示響應結束
  5. 並調用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之類的源碼比較有價值一些。

 

最後老習慣再附一圖:

熬夜變垃圾!(;´Д`)