Java 網絡編程 —— 創建非阻塞的 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;
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章