Java 網路程式設計 —— 建立非阻塞的 HTTP 伺服器

2023-05-28 18:01:15

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;
        }
    }
}