Java 網路編程 —— 創建非阻塞的 HTTP 伺服器

来源:https://www.cnblogs.com/Yee-Q/archive/2023/05/28/17438284.html
-Advertisement-
Play Games

## HTTP 概述 HTTP 客戶程式必須先發出一個 HTTP 請求,然後才能接收到來自 HTTP 服器的響應,瀏覽器就是最常見的 HTTP 客戶程式。HTTP 客戶程式和 HTTP 伺服器分別由不同的軟體開發商提供,它們都可以用任意的編程語言編寫。HTTP 嚴格規定了 HTTP 請求和 HTTP ...


HTTP 概述

HTTP 客戶程式必須先發出一個 HTTP 請求,然後才能接收到來自 HTTP 服器的響應,瀏覽器就是最常見的 HTTP 客戶程式。HTTP 客戶程式和 HTTP 伺服器分別由不同的軟體開發商提供,它們都可以用任意的編程語言編寫。HTTP 嚴格規定了 HTTP 請求和 HTTP 響應的數據格式,只要 HTTP 伺服器與客戶程式都遵守 HTTP,就能彼此看得懂對方發送的消息

1. HTTP 請求格式

下麵是一個 HTTP 請求的例子

POST /hello.jsp HTTP/1.1
Accept:image/gif, image/jpeg, */*
Referer: http://localhost/login.htm
Accept-Language: en,zh-cn;q=0.5
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 10.0)
Host: localhost
Content-Length:43
Connection: Keep-Alive
Cache-Control: no-cache

username=root&password=12346&submit=submit

HTTP 規定,HTTP 請求由三部分構成,分別是:

  • 請求方法、URI、HTTP 的版本

    • HTTP 請求的第一行包括請求方式、URI 和協議版本這三項內容,以空格分開:POST /hello.jsp HTTP/1.1
  • 請求頭(Request Header)

    • 請求頭包含許多有關客戶端環境和請求正文的有用信息。例如,請求頭可以聲明瀏覽器的類型、所用的語言、請求正文的類型,以及請求正文的長度等

      Accept:image/gif, image/jpeg, */*
      Referer: http://localhost/login.htm
      Accept-Language: en,zh-cn;q=0.5		//瀏覽器所用的語言
      Content-Type: application/x-www-form-urlencoded		//正文類型
      Accept-Encoding: gzip, deflate
      User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 10.0)	//瀏覽器類型
      Host: localhost	 //遠程主機
      Content-Length:43	//正文長度
      Connection: Keep-Alive
      Cache-Control: no-cache
      
  • 請求正文(Request Content)

    • HTTP 規定,請求頭和請求正文之間必須以空行分割(即只有 CRLF 符號的行),這個空行非常重要,它表示請求頭已經結束,接下來是請求正文,請求正文中可以包含客戶以 POST 方式提交的表單數據

      username=root&password=12346&submit=submit
      

2. HTTP 響應格式

下麵是一個 HTTP 響應的例子

HTTP/1.1 200 0K
Server: nio/1.1
Content-type: text/html; charset=GBK
Content-length:97
    
<html>
<head>
	<title>helloapp</title>
</head>
<body >
	<h1>hello</h1>
</body>
</htm1>

HTTP 響應也由三部分構成,分別是:

  • HTTP 的版本、狀態代碼、描述

    • HTTP 響應的第一行包括伺服器使用的 HTTP 的版本、狀態代碼,以及對狀態代碼的描述,這三項內容之間以空格分割
  • 響應頭 (Response Header)

    • 響應頭也和請求頭一樣包含許多有用的信息,例如伺服器類型、正文類型和正文長度等

      Server: nio/1.1		//伺服器類型
      Content-type: text/html; charset=GBK	//正文類型
      Content-length:97	//正文長度
      
  • 響應正文(Response Content)

    • 響應正文就是伺服器返回的具體的文檔,最常見的是 HTML 網頁。HTTP 響應頭與響應正文之間也必須用空行分隔

      <html>
      <head>
      	<title>helloapp</title>
      </head>
      <body >
      	<h1>hello</h1>
      </body>
      </htm1>
      

創建阻塞的 HTTP 伺服器

下例(SimpleHttpServer)創建了一個非常簡單的 HTTP 伺服器,它接收客戶程式的 HTTP 請求,把它列印到控制台。然後對 HTTP 請求做簡單的解析,如果客戶程式請求訪問 login.htm,就返回該網頁,否則一律返回 hello.htm 網頁。login.htm 和 hello.htm 文件位於 root 目錄下

SimpleHttpServer 監聽 80 埠,按照阻塞模式工作,採用線程池來處理每個客戶請求

public class SimpleHttpServer {
    
    private int port = 80;
    private ServerSocketChannel serverSocketChannel = null;
    private ExecutorService executorService;
    private static final int POOL MULTIPLE = 4;
    private Charset charset = Charset.forName("GBK");
    
    public SimpleHttpServer() throws IOException {
        executorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * POOL MULTIPLE);
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().setReuseAddress(true);
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        System.out.println("伺服器啟動");
    }
    
    public void service() {
        while (true) {
            SocketChannel socketChannel = null;
            try {
                socketChannel = serverSocketChannel.accept();
                executorService.execute(new Handler(socketChannel));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String args[])throws IOException {
        new SimpleHttpServer().service();
    }
    
    public String decode(ByteBuffer buffer) {......}	//解碼
    
    public ByteBuffer encode(String str) {......}	//編碼
    
    //Handler是內部類,負責處理HTTP請求
    class Handler implements Runnable {
        
        private SocketChannel socketChannel;
        
        public Handler(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }
        
        public void run() {
            handle(socketChannel);
        }
        
        public void handle(SocketChannel socketChannel) {
            try {
                Socket socket = socketChannel.socket();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                
                //接收HTTP請求,假定其長度不超過1024位元組
                socketChannel.read(buffer);
                buffer.flip();
                String request = decode(buffer);
                //列印HTTP請求
                System.out.print(request);
                
                //生成HTTP響應結果
                StringBuffer sb = new StringBuffer("HTTP/1.1 200 0K\r\n");
                sb.append("Content-Type:text/html\r\n\r\n");
                //發送HTTP響應的第1行和響應頭
                socketChannel.write(encode(sb.toString()));
                
                FileInputStream in;
                //獲得HTTP請求的第1行
                String firstLineOfRequest = request.substring(0, request.indexOf("\r\n"));
                if(firstLineOfRequest.indexOf("login.htm") != -1) {
                    in = new FileInputStream("login.htm");
                } else {
                    in = new FileInputStream("hello.htm");
                }
                    
                FileChannel fileChannel = in.getChannel();
                //發送響應正文
                fileChannel.transferTo(0, fileChannel.size(), socketChannel);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if(socketChannel != null) {
                        //關閉連接
                        socketChannel.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

創建非阻塞的 HTTP 伺服器

下麵是本節所介紹的非阻塞的 HTTP 伺服器範例的模型

  • HttpServer:伺服器主程式,由它啟動伺服器
  • AcceptHandler:負責接收客戶連接
  • RequestHandler:負責接收客戶的 HTTP 請求,對其解析,然後生成相應的 HTTP 響應,再把它發送給客戶
  • Request:表示 HTTP 請求
  • Response:表示 HTTP 響應
  • Content:表示 HTTP 響應的正文

1. 伺服器主程式 HttpServer

HttpServer 僅啟用了單個主線程,採用非阻塞模式來接收客戶連接,以及收發數據

public class HttpServer {
    
    private Selector selector = null;
    private ServerSocketChannel serverSocketChannel = null;
    private int port = 80;
    private Charset charset = Charset.forName("GBK");
    
    public HttpServer() throws IOException {
        //創建Selector和ServerSocketChannel
        //把ServerSocketchannel設置為非阻塞模式,綁定到80埠
        ......
    }
    
    public void service() throws IOException {
        //註冊接收連接就緒事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, new AcceptHandler());
        while(true) {
            int n = selector.select();
            if(n==0) continue;
            Set readyKeys = selector.selectedKeys();
            Iterator it = readyKeys.iterator();
			while(it.hasNext()) {
                SelectionKey key = null;
                try {
                    key = (SelectionKey) it.next();
                    it.remove();
                    final Handler handler = (Handler) key.attachment();
                    handler.handle(key); //由 Handler 處理相關事件
                } catch(IOException e) {
                    e.printStackTrace();
                    try {
                        if(key != null) {
                            key.cancel();
                            key.channel().close();
                        }
                    } catch(Exception ex) {
                        e.printStackTrace();
                    }
                }
            }            
        }
    }
    
    public static void main(String args[])throws Exception {
        final HttpServer server = new HttpServer();
        server.service();
    }
}

2. 具有自動增長的緩衝區的 ChannelIO 類

自定義的 ChannelIO 類對 SocketChannel 進行了包裝,增加了自動增長緩衝區容量的功能。當調用 socketChannel.read(ByteBuffer bufer) 方法時,如果 buffer 已滿,即使通道中還有未接收的數據,read 方法也不會讀取任何數據,而是直接返回 0,表示讀到了零位元組

為了能讀取通道中的所有數據,必須保證緩衝區的容量足夠大。在 ChannelIO 類中有一個 requestBuffer 變數,它用來存放客戶的 HTTP 請求數據,當 requestBuffer 剩餘容量已經不足 5%,並且還有 HTTP 請求數據未接收時,ChannellO 會自動擴充 requestBuffer 的容量,該功能由 resizeRequestBuffer() 方法完成

public class ChannelIO {
    
    protected SocketChannel socketChannel;
    protected ByteBuffer requestBuffer; //存放請求數據
    private static int requestBufferSize = 4096;
    
    public ChannelIO(SocketChannel socketChannel, boolean blocking) throws IOException {
        this.socketChannel = socketChannel;
        socketChannel.configureBlocking(blocking); //設置模式
        requestBuffer = ByteBuffer.allocate(requestBufferSize);
    }
    
    public SocketChannel 
        () {
        return socketChannel;
    }
    
    /**
     * 如果原緩衝區的剩餘容量不夠,就創建一個新的緩衝區,容量為原來的兩倍
     * 並把原來緩衝區的數據拷貝到新緩衝區
     */
    protected void resizeRequestBuffer(int remaining) {
        if (requestBuffer.remaining() < remaining) {
            ByteBuffer bb = ByteBuffer.allocate(requestBuffer.capacity() * 2);
            requestBuffer.flip();
            bb.put(requestBuffer); //把原來緩衝區中的數據拷貝到新的緩衝區
            requestBuffer = bb;
        }
    }
    
    /**
     * 接收數據,把它們存放到requestBuffer
     * 如果requestBuffer的剩餘容量不足5%
     * 就通過resizeRequestBuffer()方法擴充容量
     */
    public int read() throws IOException {
        resizeRequestBuffer(requestBufferSize/20);
        return socketChannel.read(requestBuffer);
    }
    
    /** 返回requestBuffer,它存放了請求數據 */
    public ByteBuffer getReadBuf() {
        return requestBuffer;
    }
    
    /** 發送參數指定的 ByteBuffer 的數據 */
    public int write(ByteBuffer src) throws IOException {
        return socketChannel.write(src);
    }
    
    /** 把FileChannel的數據寫到SocketChannel */
    public long transferTo(FileChannel fc, long pos, long len) throws IOException {
        return fc.transferTo(pos, len, socketChannel);
    }
    
    /** 關閉SocketChannel */
    public void close() throws IOException {
        socketChannel.close();
    }
}

3. 負責處理各種事件的 Handler 介面

Handler 介面負責處理各種事件,它的定義如下:

public interface Handler {
    public void handle(SelectionKey key) throws IOException;
}

Handler 介面有 AcceptHandler 和 RequestHandler 兩個實現類。AcceptHandler 負責處理接收連接就緒事件,RequestHandler 負責處理讀就緒和寫就緒事件。更確切地說,RequestHandler 負責接收客戶的 HTTP 請求,以及發送 HTTP 響應

4. 負責處理接收連接就緒事件的 AcceptHandler類

AcceptHandler 負責處理接收連接就緒事件,獲得與客戶連接的 SocketChannel,然後向 Selector 註冊讀就緒事件,並且創建了一個 RequestHandler,把它作為 SelectionKey 的附件。當讀就緒事件發生時,將由這個 RequestHandler 來處理該事件

public class AcceptHandler implements Handler {
    
    public void handle(SelectionKey key) throws IOException {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        //在非阻塞模式下,serverSocketChannel.accept()有可能返回null
        SocketChannel socketChannel = serverSocketChannel.accept();
        if (socketChannel == null) return;
        //ChannelIO設置為採用非阻塞模式
        ChannelIO cio = new ChannelIO(socketChannel, false);
        RequestHandler rh = new RequestHandler(cio);
        //註冊讀就緒事件,把RequestHandler作為附件
        socketChannel.register(key.selector(), SelectionKey.OP_READ, rh);
    }
}

5. 負責接收 HTTP 請求和發送 HTTP 響應的 RequestHandler 類

RequestHandler 先通過 ChannelIO 來接收 HTTP 請求,當接收到 HTTP 請求的所有數據後,就對 HTTP 請求數據進行解析,創建相應的 Request 對象,然後依據客戶的請求內容,創建相應的 Response 對象,最後發送 Response 對象中包含的 HTTP 響應數據。為了簡化程式,RequestHandler 僅僅支持 GET 和 HEAD 兩種請求方式

public class RequestHandler implements Handler {
    
    private ChannelIO channelIO;
    //存放HTTP請求的緩衝區
    private ByteBuffer requestByteBuffer = null;
    //表示是否已經接收到HTTP請求的所有數據
    private boolean requestReceived = false;
    //表示HTTP請求
    private Request request = null;
    //表示HTTP響應
    private Response response = null;
    
    RequestHandler(ChannelIO channelIO) {
        this.channelIO = channelIO;
    }
    
    /** 接收HTTP請求,發送HTTP響應 */
    public void handle(SelectionKey sk) throws IOException {
        try {
            //如果還沒有接收HTTP請求的所有數據,就接收HTTP請求
            if (request == null) {
                if (!receive(sk)) return;
                requestByteBuffer.flip();
                //如果成功解析了HTTP請求,就創建一個Response對象
                if (parse()) build();
                try {
                    //準備HTTP響應的內容
                    response.prepare(); 
                } catch (IOException x) {
                    response.release();
                    response = new Response(Response.Code.NOT_FOUND, new StringContent(x.getMessage()));
                    response.prepare();
                }
                
                if (send()) {
                    //如果HTTP響應沒有發送完畢,則需要註冊寫就緒事件,以便在寫就緒事件發生時繼續發送數據
                    sk.interestOps(SelectionKey.OP_WRITE);
                } else {
                    //如HTTP響應發送完畢,就斷開底層連接,並且釋放Response占用資源
                    channelIO.close();
                    response.release();
                }
            } else {
                //如果已經接收到HTTP請求的所有數據
                //如果HTTP響應發送完畢
                if (!send()) {
                    channelIO.close();
                    response.release();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            channelIO.close();
            if (response != null) {
                response.release();
            }
        }
    }
    
    /**
     * 接收HTTP請求,如果已經接收到了HTTP請求的所有數據,就返回true,否則返回false
     */
    private boolean receive(SelectionKey sk) throws IOException {
        ByteBuffer tmp = null;
        //如果已經接收到HTTP請求的所有數據,就返回true
        if (requestReceived) return true;
        //如果已經讀到通道的末尾,或者已經讀到HTTP請求數據的末尾標誌,就返回true
        if ((channelIO.read() < 0) || Request.isComplete(channelIO.getReadBuf())) {
            requestByteBuffer = channelIO.getReadBuf();
            return (requestReceived = true);
        }
        return false;
    }
    
    /**
     * 通過Request類的parse()方法,解析requestByteBuffer的HTTP請求數據
     * 構造相應的Request對象
     */
    private boolean parse() throws IOException {
        try {
            request = Request.parse(requestByteBuffer);
            return true;
        } catch (MalformedRequestException x) {
            //如果HTTP請求的格式不正確,就發送錯誤信息
            response = new Response(Response.Code.BAD_REQUEST, new StringContent(x))
        }
        return false;
    }
    
    /** 創建HTTP響應 */
    private void build() throws IOException {
        Request.Action action = request.action();
        //僅僅支持GET和HEAD請求方式
        if ((action != Request.Action.GET) && (action != Request.Action.HEAD)) {
            response = new Response(Response.Code.METHOD_NOT_ALLOWED, new StringContent("Method Not Allowed"));
        } else {
            response = new Response(Response.Code.OK, new FileContent(request.uri()), action);
        }
    }
    
    /** 發送HTTP響應,如果全部發送完畢,就返回false,否則返回true */
    private boolean send() throws IOException {
        return response.send(channelIO);
    }
}

6. 代表 HTTP 請求的 Request 類

RequestHandler 通過 ChannelIO 讀取 HTTP 請求數據時,這些數據被放在 requestByteBuffer 中。當 HTTP 請求的所有數據接收完畢,就要對 requestByteBufer 的數據進行解析,然後創建相應的 Request 對象。Request 對象就表示特定的 HTTP 請求

public class Request {
    
    //枚舉類,表示HTTP請求方式
    static enum Action {
        GET,PUT,POST,HEAD;
    }
    
    public static Action parse(String s) {
        if (s.equals("GET"))
            return GET;
        if (s.equals("PUT"))
            return PUT;
        if (s.equals("POST"))
            return POST;
        if (s,equals("HEAD"))
            return HEAD;
        throw new IllegalArgumentException(s);
    }
    
    private Action action;	//請求方式
    private String version;	//HTTP版本
    private URI uri;		//URI
    
    public Action action() { return action; }
    public String version() { return version; }
    public URI uri() { return uri; }
    
    private Request(Action a, String V, URI u) {
        action = a;
        version = v;
        uri =u;
    }
    
    public String toString() {
        return (action + " " + version + " " + uri);
    }
    
    private static Charset requestCharset = Charset.forName("GBK");
    
    /**
     * 判斷ByteBuffer是否包含HTTP請求的所有數據
     * HTTP請求以”r\n\r\n”結尾
     */
    public static boolean isComplete(ByteBuffer bb) {
        ByteBuffer temp = bb.asReadOnlyBuffer();
        temp.flip();
        String data = requestCharset.decode(temp).toString();
        if(data.indexOf("r\n\r\n") != -1) {
            return true;
        }
        return false;
    }
    
    /**
     * 刪除請求正文
     */
    private static ByteBuffer deleteContent (ByteBuffer bb) {
        ByteBuffer temp = bb.asReadOnlyBuffer();
        String data = requestCharset.decode(temp).toString();
        if(data.indexOf("\r\n\r\n") != -1) {
            data = data.substrinq(0, data.indexOf("\r\n\r\n") + 4);
            return requestCharset.encode(data);
        }
        return bb;
    }
    
    /**
     * 設定用於解析HTTP請求的字元串匹配模式,對於以下形式的HTTP請求
     * GET /dir/file HTTP/1.1
     * Host: hostname
     * 將被解析成:
     * group[l] = "GET”
     * group[2]="/dir/file"
     * group[3]="1.1"
     * group[4]="hostname"
     */
    private static Pattern requestPattern =
        Pattern.compile("\\A([A-Z]+) +([^]+) +HTTP/([0-9\\.]+)$"
                        + ",*^Host:([]+)$.*\r\n\r\n\\z",
                        Pattern.MULTILINE | Pattern.DOTALL);
    
    /** 解析HTTP請求,創建相應的Request對象 */
    public static Request parse(ByteBuffer bb) throws MalformedRequestException {
        bb = deleteContent(bb); //刪除請求正文
        CharBuffer cb = requestCharset.decode(bb); //解碼
        Matcher m = requestPattern.matcher(cb); //進行字元串匹配
        //如果HTTP請求與指定的字元串式不匹配,說明請求數據不正確
        if (!m.matches())
            throw new MalformedRequestException();
        Action a;
        //獲得請求方式
        try {
            a = Action.parse(m.group(1));
        } catch (IllegalArgumentException x) {
            throw new MalformedRequestException();
        }
        //獲得URI
        URI u;
        try {
            u=new URI("http://" + m.group(4) + m.group(2));
        } catch (URISyntaxException x) {
            throw new MalformedRequestException();
        }
        //創建一個Request對象,並將其返回
        return new Request(a, m.group(3), u);
    }
}

7. 代表 HTTP 響應的 Response 類

Response 類表示 HTTP 響應,它有三個成員變數:code、headerBufer 和 content,它們分別表示 HTTP 響應中的狀態代碼、響應頭和正文

public class Response implements Sendable {
    
    //枚舉類,表示狀態代碼
    static enum Code {
        
        OK(200, "OK"),
        BAD_REQUEST(400, "Bad Request"),
        NOT_FOUND(404, "Not Found"),
        METHOD_NOT_ALLOWED(405, "Method Not Allowed");
        
        private int number;
        private String reason;
        
        private Code(int i, String r) {
            number = i;
            reason =r;
        }
        
        public String toString() {
            return number + " "  + reason;
        }
    }
    
    private Code code; //狀態代碼
    private Content content; //響應正文
    private boolean headersOnly; //表示HTTP響應中是否僅包含響應頭
    private ByteBuffer headerBuffer = null; //響應頭
    
    public Response(Code rc, Content c) {
        this(rc, c, null);
    }
    
    public Response(Code rc, Content c, Request.Action head) {
        code = rc;
        content = c;
        headersOnly = (head == Request.Action.HEAD);
    }
    
    /** 創建響應頭的內容,把它存放到ByteBuffer */
    private ByteBuffer headers() {
        CharBuffer cb = CharBuffer.allocate(1024);
        while(true) {
            try {
                cb.put("HTTP/1.1").put(code.toString()).put(CRLF);
                cb.put("Server: nio/1.1").put(CRLF);
                cb.put("Content-type: ") .put(content.type()).put(CRIE);
                cb.put("Content-length: ").put(Long.toString(content.length())).put(CRLF);
                cb.put(CRLF);
                break;
            } catch (BufferOverflowException x) {
                assert(cb.capacity() < (1 << 16));
                cb = CharBuffer.allocate(cb.capacity() * 2);
                continue;
            }
        }
        cb.flip();
        return responseCharset.encode(cb); //編碼
    }
    
    /** 準備 HTTP 響應中的正文以及響應頭的內容 */
    public void prepare() throws IOException {
        content.prepare();
        headerBuffer= headers();
    }
    
    /** 發送HTTP響應,如果全部發送完畢,就返回false,否則返回true */
    public boolean send(ChannelIO cio) throws IOException {
        if (headerBuffer == null) {
            throw new IllegalStateException();
        }
        //發送響應頭
        if (headerBuffer.hasRemaining()) {
            if (cio.write(headerBuffer) <= 0)
                return true;
        }
        //發送響應正文
        if (!headersOnly) {
            if (content.send(cio))
                return true;
        }
        return false;
    }
    
    /** 釋放響應正文占用的資源 */
    public void release() throws IOException {
        content.release();
    }
}

8. 代表響應正文的 Content 介面及其實現類

Response 類有一個成員變數 content,表示響應正文,它被定義為 Content 類型

public interface Content extends Sendable {
    
    //正文的類型
    String type();
    
    //返回正文的長度
    //在正文準備之前,即調用prepare()方法之前,length()方法返回“-1”
    long length();
}

Content 介面繼承了 Sendable 介面,Sendable 介面表示伺服器端可發送給客戶的內容

public interface Sendable {
    
    // 準備發送的內容
    public void prepare() throws IOException;
    
    // 利用通道發送部分內容,如果所有內容發送完畢,就返回false
	//如果還有內容未發送,就返回true
	//如果內容還沒有準備好,就拋出 IlleqalstateException
	public boolean send(ChannelIO cio) throws IOException;
    
    //當伺服器發送內容完畢,就調用此方法,釋放內容占用的資源
    public void release() throws IOException;
}

Content 介面有 StringContent 和 FileContent 兩個實現類,StringContent 表示字元串形式的正文,FileContent 表示文件形式的正文

FileContent 類有一個成員變數 fleChannel,它表示讀文件的通道。FileContent 類的 send() 方法把 fileChannel 中的數據發送到 ChannelIO 的 SocketChannel 中,如果文件中的所有數據發送完畢,send() 方法就返回 false

public class FileContent implements Content {
    
    //假定文件的根目錄為"root",該目錄應該位於classpath下
    private static File ROOT = new File("root");
    private File file;
    
    public FileContent(URI uri) {
        file = new File(ROOT, uri.getPath().replace('/', File,separatorChar));
    }
    
    private String type = null;
    
    /** 確定文件類型 */
    public String type() {
        if (type != null) return type;
        String nm = file.getName();
        if (nm.endsWith(".html") || nm.endsWith(".htm"))
            type = "text/html; charset=iso-8859-1"; //HTML網頁
        else if ((nm.indexOf('.') < 0) || nm.endsWith(".txt"))
            type = "text/plain; charset=iso-8859-1"; //文本文件
        else
            type = "application/octet-stream"; //應用程式
        return type;
    }
    
    private FileChannel fileChannel = null;
    private long length = -1; //文件長度
    private long position = -1;//文件的當前位置
    
    public long length() {
        return length;
    }
    
    /** 創建 FileChannel 對象 */
    public void prepare() throws IOException {
        if (fileChannel == null)
            fileChannel = new RandomAccessFile(file, "r").getChannel();
        length = fileChannel.size();
        position =0;
    }
    
    /** 發送正文,如果發送完畢,就返回 false,否則返回true */
    public boolean send(ChannelIO channelIO) throws IOException {
        if (fileChannel == null)
            throw new IllegalStateException();
        if (position < 0)
            throw new IllegalStateException();
        if (position >= length)
            return false; //如果發送完畢,就返回false
        position += channelIO,transferTo(fileChannel, position, length - position);
        return (position < length);
    }
    
    public void release() throws IOException {
        if (fileChannel != null) {
            fileChannel.close(); //關閉fileChannel
            fileChannel = null;
        }
    }
}


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 網路分層結構 電腦網路體系大致分為三種,OSI七層模型、TCP/IP四層模型和五層模型。一般面試的時候考察比較多的是五層模型。最全面的Java面試網站:[最全面的Java面試網站](https://topjavaer.cn) ![](http://img.topjavaer.cn/img/t ...
  • Java的Object類是所有類的根類,它提供了一些通用的方法。下麵是一些常用的Object類方法: 1. equals(Object obj):判斷當前對象是否與給定對象相等。預設情況下,equals方法比較的是對象的引用,但可以通過在具體類中重寫equals方法來改變其比較行為。 2. hash ...
  • ini 配置文件格式如下 要求:ini 文件必須是GBK編碼,如果是UTF-8編碼,python讀取配置文件會報錯。 # 這裡是註釋內容 # [FY12361] #婦幼保健介面服務埠 serverIP=192.168.1.11 serverPort=8400 [SM] #國產SM加密服務埠 se ...
  • # Rust Web 全棧開發之 Actix 嘗鮮並構建REST API ## 一、Actix 嘗鮮 ### 需要使用的crate - actix-web v4.3.1 - actix-rt v2.8.0 ```bash ~ via 🅒 base ➜ cd rust ~/rust via 🅒 b ...
  • ## 前言 TCP源碼篇,當前只分析TCP層的源碼實現,按功能分塊分析,介面為RAW介面。 NETCONN介面和SOCKET介面會獨立一篇文章進行分析。 本文基於讀者已學習了TCP協議原理篇的基礎上進行源碼分析,不再在此篇文章中過多解析TCP相關概念。 ‍ 建議讀者對著LWIP庫源碼進行閱讀。對於初 ...
  • 最近在學中頻信號處理的一些東西,順便用 QT 寫了一個小工具,可以顯示信號的時域波形圖、幅度譜、功率譜、二次方譜、四次方譜、八次方譜、瞬時包絡、瞬時頻率、瞬時相位、非線性瞬時相位、瞬時幅度直方圖、瞬時頻率直方圖、瞬時相位直方圖、眼圖、星座圖、語譜圖、瀑布圖。 ...
  • 由於老周的示例代碼都是用 VS Code + CMake + Qt 寫的,為了不誤導人,在標題中還是加上“VS Code”好一些。 上次咱們研究了剪貼板的基本用法,也瞭解了叫 QMimeData 的重要類。為啥要強調這個類?因為接下來扯到的拖放操作也是和它有關係。哦,對了,咱們先避開一下主題,關於剪 ...
  • ## 實踐環境 Python 3.6.2 ## 什麼是協程 **協程**(Coroutine)一種電腦程式組件,該程式組件通過允許暫停和恢復任務,為非搶占式多任務生成子程式。**協程**也可以簡單理解為協作的程式,通過協同多任務處理實現併發的函數的變種(一種可以支持中斷的函數)。 下麵,我們通過日常 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...