你可以試著使用 AsyncContext 來改寫一下〈簡介 ServletContext〉裏的電子書下載範例:
package cc.openhome;
import java.io.*;
import java.util.concurrent.CompletableFuture;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet(
    urlPatterns = { "/ebook" }, 
    initParams = {
    @WebInitParam(name = "PDF_FILE", value = "/WEB-INF/jdbc.pdf") }, 
    asyncSupported = true
)
public class Ebook extends HttpServlet {
    private String PDF_FILE;
    @Override
    public void init() throws ServletException {
        super.init();
        PDF_FILE = getInitParameter("PDF_FILE");
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String coupon = request.getParameter("coupon");
        if ("123456".equals(coupon)) {
            AsyncContext ctx = request.startAsync();
            CompletableFuture.runAsync(() -> {
                response.setContentType("application/pdf");
                try (InputStream in = getServletContext().getResourceAsStream(PDF_FILE)) {
                    OutputStream out = response.getOutputStream();
                    byte[] buffer = new byte[1024];
                    int length = -1;
                    while ((length = in.read(buffer)) != -1) {
                        out.write(buffer, 0, length);
                    }
                } catch (IOException ex) {
                    throw new UncheckedIOException(ex);
                } finally {
                    ctx.complete();
                }
            });
        }
    }
}
這會使得容器分配的執行緒可以儘快地服務其他請求,然而,回應時的 ServletOutputStream 是阻斷式,而檔案讀取也是,這表示 CompletableFuture 處理時的執行緒,遇到這些阻斷式 I/O 時,然而必須等待,無法儘早回到執行緒池中。
在檔案讀取的部份,你可以試著 NIO2 的非阻斷 API,那麼請求的讀取呢?在 Servlet 3.1 中,ServletOutputStream 可以實現非阻斷輸出,這可以透過對 ServletOutputStream 註冊一個 WriteListener 實例來達到:
package javax.servlet;
import java.io.IOException;
public interface WriteListener extends java.util.EventListener{
    public void onWritePossible() throws IOException;
    public void onError(java.lang.Throwable throwable);
}
在 ServletOutputStream 可以寫出的時候,會呼叫 onWritePossible 方法,若發生例外的話,會呼叫 onError(),要註冊 WriteListener 實例,必須在非同步 Servlet 中進行,例如,可以將〈簡介 ServletContext〉裏的電子書下載範例改寫,使用 ServletOutputStream 的非阻斷功能:
package cc.openhome;
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet(
    urlPatterns = { "/ebook" }, 
    initParams = {
    @WebInitParam(name = "PDF_FILE", value = "/WEB-INF/jdbc.pdf") }, 
    asyncSupported = true
)
public class Ebook extends HttpServlet {
    private String PDF_FILE;
    @Override
    public void init() throws ServletException {
        super.init();
        PDF_FILE = getInitParameter("PDF_FILE");
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String coupon = request.getParameter("coupon");
        if ("123456".equals(coupon)) {
            AsyncContext ctx = request.startAsync();
            ServletOutputStream out = response.getOutputStream();
            out.setWriteListener(new WriteListener() {
                InputStream in = getServletContext().getResourceAsStream(PDF_FILE);
                @Override
                public void onError(Throwable t) {
                    try {
                        in.close();
                    }
                    catch(IOException ex) {
                        throw new UncheckedIOException(ex);
                    }
                    throw new RuntimeException(t);
                }
                @Override
                public void onWritePossible() throws IOException {
                    byte[] buffer = new byte[1024];
                    int length = 0;
                    while (out.isReady() && (length = in.read(buffer)) != -1) {
                        out.write(buffer, 0, length);
                    }
                    if(length == -1) {
                        in.close();
                        ctx.complete();
                    }
                }
            });
        }
    }
}
在這個例子當中,每次 ServletOutputStream 可以寫出資料時,會呼叫 onWritePossible(),在檔案讀不到資料時,length 會是 -1,這時完成非同步請求,基於簡化範例,檔案讀取的動作就還是先用阻斷式的 API。

