<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 1.簡介 本文是[上一篇文章](http://www.hmoore.net/imnotdown1019/java_core_full/1011421)實踐篇,在上一篇文章中,我分析了選擇器 Selector 的原理。本篇文章,我們來說說 Selector 的應用,如標題所示,這里我基于 Java NIO 實現了一個簡單的 HTTP 服務器。在接下來的章節中,我會詳細講解 HTTP 服務器實現的過程。另外,本文所對應的代碼已經上傳到 GitHub 上了,需要的自取,倉庫地址為[toyhttpd](https://github.com/code4wt/toyhttpd)。好了,廢話不多說,進入正題吧。 ## [](http://www.tianxiaobo.com/2018/04/04/%E5%9F%BA%E4%BA%8E-Java-NIO-%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84-HTTP-%E6%9C%8D%E5%8A%A1%E5%99%A8/#2-實現)2\. 實現 本節所介紹的 HTTP 服務器是一個很簡單的實現,僅支持 HTTP 協議極少的特性。包括識別文件后綴,并返回相應的 Content-Type。支持200、400、403、404、500等錯誤碼等。由于支持的特性比較少,所以代碼邏輯也比較簡單,這里羅列一下: 1. 處理請求,解析請求頭 2. 響應請求,從請求頭中獲取資源路徑, 檢測請求的資源路徑是否合法 3. 根據文件后綴匹配 Content-Type 4. 讀取文件數據,并設置 Content-Length,如果文件不存在則返回404 5. 設置響應頭,并將響應頭和數據返回給瀏覽器。 接下來我們按照處理請求和響應請求兩步操作,來說說代碼實現。先來看看核心的代碼結構,如下: ``` /** * TinyHttpd * * @author code4wt * @date 2018-03-26 22:28:44 */ public class TinyHttpd { private static final int DEFAULT_PORT = 8080; private static final int DEFAULT_BUFFER_SIZE = 4096; private static final String INDEX_PAGE = "index.html"; private static final String STATIC_RESOURCE_DIR = "static"; private static final String META_RESOURCE_DIR_PREFIX = "/meta/"; private static final String KEY_VALUE_SEPARATOR = ":"; private static final String CRLF = "\r\n"; private int port; public TinyHttpd() { this(DEFAULT_PORT); } public TinyHttpd(int port) { this.port = port; } public void start() throws IOException { // 初始化 ServerSocketChannel ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress("localhost", port)); ssc.configureBlocking(false); // 創建 Selector Selector selector = Selector.open(); // 注冊事件 ssc.register(selector, SelectionKey.OP_ACCEPT); while(true) { int readyNum = selector.select(); if (readyNum == 0) { continue; } Set&lt;SelectionKey&gt; selectedKeys = selector.selectedKeys(); Iterator&lt;SelectionKey&gt; it = selectedKeys.iterator(); while (it.hasNext()) { SelectionKey selectionKey = it.next(); it.remove(); if (selectionKey.isAcceptable()) { SocketChannel socketChannel = ssc.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { // 處理請求 request(selectionKey); selectionKey.interestOps(SelectionKey.OP_WRITE); } else if (selectionKey.isWritable()) { // 響應請求 response(selectionKey); } } } } private void request(SelectionKey selectionKey) throws IOException {...} private Headers parseHeader(String headerStr) {...} private void response(SelectionKey selectionKey) throws IOException {...} private void handleOK(SocketChannel channel, String path) throws IOException {...} private void handleNotFound(SocketChannel channel) {...} private void handleBadRequest(SocketChannel channel) {...} private void handleForbidden(SocketChannel channel) {...} private void handleInternalServerError(SocketChannel channel) {...} private void handleError(SocketChannel channel, int statusCode) throws IOException {...} private ByteBuffer readFile(String path) throws IOException {...} private String getExtension(String path) {...} private void log(String ip, Headers headers, int code) {} } ``` 上面的代碼是 HTTP 服務器的核心類的代碼結構。其中 request 負責處理請求,response 負責響應請求。handleOK 方法用于響應正常的請求,handleNotFound 等方法用于響應出錯的請求。readFile 方法用于讀取資源文件,getExtension 則是獲取文件后綴。 ### [](http://www.tianxiaobo.com/2018/04/04/%E5%9F%BA%E4%BA%8E-Java-NIO-%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84-HTTP-%E6%9C%8D%E5%8A%A1%E5%99%A8/#21-處理請求)2.1 處理請求 處理請求的邏輯比較簡單,主要的工作是解析消息頭。相關代碼如下: ``` private void request(SelectionKey selectionKey) throws IOException { // 從通道中讀取請求頭數據 SocketChannel channel = (SocketChannel) selectionKey.channel(); ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); channel.read(buffer); buffer.flip(); byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); String headerStr = new String(bytes); try { // 解析請求頭 Headers headers = parseHeader(headerStr); // 將請求頭對象放入 selectionKey 中 selectionKey.attach(Optional.of(headers)); } catch (InvalidHeaderException e) { selectionKey.attach(Optional.empty()); } } private Headers parseHeader(String headerStr) { if (Objects.isNull(headerStr) || headerStr.isEmpty()) { throw new InvalidHeaderException(); } // 解析請求頭第一行 int index = headerStr.indexOf(CRLF); if (index == -1) { throw new InvalidHeaderException(); } Headers headers = new Headers(); String firstLine = headerStr.substring(0, index); String[] parts = firstLine.split(" "); /* * 請求頭的第一行必須由三部分構成,分別為 METHOD PATH VERSION * 比如: * GET /index.html HTTP/1.1 */ if (parts.length &lt; 3) { throw new InvalidHeaderException(); } headers.setMethod(parts[0]); headers.setPath(parts[1]); headers.setVersion(parts[2]); // 解析請求頭屬于部分 parts = headerStr.split(CRLF); for (String part : parts) { index = part.indexOf(KEY_VALUE_SEPARATOR); if (index == -1) { continue; } String key = part.substring(0, index); if (index == -1 || index + 1 &gt;= part.length()) { headers.set(key, ""); continue; } String value = part.substring(index + 1); headers.set(key, value); } return headers; } ``` 簡單總結一下上面的代碼邏輯,首先是從通道中讀取請求頭,然后解析讀取到的請求頭,最后將解析出的 Header 對象放入 selectionKey 中。處理請求的邏輯很簡單,不多說了。 ### [](http://www.tianxiaobo.com/2018/04/04/%E5%9F%BA%E4%BA%8E-Java-NIO-%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84-HTTP-%E6%9C%8D%E5%8A%A1%E5%99%A8/#22-響應請求)2.2 響應請求 看完處理請求的邏輯,接下來再來看看響應請求的邏輯。代碼如下: ``` private void response(SelectionKey selectionKey) throws IOException { SocketChannel channel = (SocketChannel) selectionKey.channel(); // 從 selectionKey 中取出請求頭對象 Optional&lt;Headers&gt; op = (Optional&lt;Headers&gt;) selectionKey.attachment(); // 處理無效請求,返回 400 錯誤 if (!op.isPresent()) { handleBadRequest(channel); channel.close(); return; } String ip = channel.getRemoteAddress().toString().replace("/", ""); Headers headers = op.get(); // 如果請求 /meta/ 路徑下的資源,則認為是非法請求,返回 403 錯誤 if (headers.getPath().startsWith(META_RESOURCE_DIR_PREFIX)) { handleForbidden(channel); channel.close(); log(ip, headers, FORBIDDEN.getCode()); return; } try { handleOK(channel, headers.getPath()); log(ip, headers, OK.getCode()); } catch (FileNotFoundException e) { // 文件未發現,返回 404 錯誤 handleNotFound(channel); log(ip, headers, NOT_FOUND.getCode()); } catch (Exception e) { // 其他異常,返回 500 錯誤 handleInternalServerError(channel); log(ip, headers, INTERNAL_SERVER_ERROR.getCode()); } finally { channel.close(); } } // 處理正常的請求 private void handleOK(SocketChannel channel, String path) throws IOException { ResponseHeaders headers = new ResponseHeaders(OK.getCode()); // 讀取文件 ByteBuffer bodyBuffer = readFile(path); // 設置響應頭 headers.setContentLength(bodyBuffer.capacity()); headers.setContentType(ContentTypeUtils.getContentType(getExtension(path))); ByteBuffer headerBuffer = ByteBuffer.wrap(headers.toString().getBytes()); // 將響應頭和資源數據一同返回 channel.write(new ByteBuffer[]{headerBuffer, bodyBuffer}); } // 處理請求資源未發現的錯誤 private void handleNotFound(SocketChannel channel) { try { handleError(channel, NOT_FOUND.getCode()); } catch (Exception e) { handleInternalServerError(channel); } } private void handleError(SocketChannel channel, int statusCode) throws IOException { ResponseHeaders headers = new ResponseHeaders(statusCode); // 讀取文件 ByteBuffer bodyBuffer = readFile(String.format("/%d.html", statusCode)); // 設置響應頭 headers.setContentLength(bodyBuffer.capacity()); headers.setContentType(ContentTypeUtils.getContentType("html")); ByteBuffer headerBuffer = ByteBuffer.wrap(headers.toString().getBytes()); // 將響應頭和資源數據一同返回 channel.write(new ByteBuffer[]{headerBuffer, bodyBuffer}); } ``` 上面的代碼略長,不過邏輯仍然比較簡單。首先,要判斷請求頭存在,以及資源路徑是否合法。如果都合法,再去讀取資源文件,如果文件不存在,則返回 404 錯誤碼。如果發生其他異常,則返回 500 錯誤。如果沒有錯誤發生,則正常返回響應頭和資源數據。這里只貼了核心代碼,其他代碼就不貼了,大家自己去看吧。 ### [](http://www.tianxiaobo.com/2018/04/04/%E5%9F%BA%E4%BA%8E-Java-NIO-%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84-HTTP-%E6%9C%8D%E5%8A%A1%E5%99%A8/#23-效果演示)2.3 效果演示 分析完代碼,接下來看點輕松的吧。下面貼一張代碼的運行效果圖,如下: ![tinyhttpd1_w](https://blog-pictures.oss-cn-shanghai.aliyuncs.com/tinyhttpd1_wm.gif) ## [](http://www.tianxiaobo.com/2018/04/04/%E5%9F%BA%E4%BA%8E-Java-NIO-%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84-HTTP-%E6%9C%8D%E5%8A%A1%E5%99%A8/#3總結)3.總結 本文所貼的代碼是我在學習 Selector 過程中寫的,核心代碼不到 300 行。通過動手寫代碼,也使得我加深了對 Selector 的了解。在學習 JDK 的過程中,強烈建議大家多動手寫代碼。通過寫代碼,并踩一些坑,才能更加熟練運用相關技術。這個是我寫 NIO 系列文章的一個感觸
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看