国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

一步一步實現Tomcat之二——實現一個簡單的Servlet容器

dayday_up / 743人閱讀

摘要:注本文使用規范是規范中的一個接口,我們可以自己實現這個接口在方法中實現自己的業務邏輯。我們只是實現一個簡單的容器示例,所以和其他方法留待以后實現。運行一下實現首先編寫一個自己的實現類。

前言

經過上一篇文章《一步一步實現Tomcat——實現一個簡單的Web服務器》,我們實現了一個簡單的Web服務器,可以響應瀏覽器請求顯示靜態Html頁面,本文更進一步,實現一個Servlet容器,我們不只能響應靜態頁面請求,還能響應Servlet請求,雖然現在我們只能在自己的Servlet中打印出“Hello World!”,但是我們離Tomcat服務器更近了一步。

基礎知識

相信大家應該對Java EE編程比較熟悉,故在此只簡單的描述一下基本概念。

1. Java Servlet

Java Servlet 是運行在 Web 服務器或應用服務器上的程序,也可以說是一組規范,只要按照規范實現自己的類,就可以在相應的Servlet服務器(Tomcat、Jetty等)中運行,響應瀏覽器請求,動態生成內容。

注:本文使用Servlet 2.3規范

2. javax.servlet.Servlet

是Servlet規范中的一個接口,我們可以自己實現這個接口在service方法中實現自己的業務邏輯。
service方法簽名如下:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
3. javax.servlet.ServletRequest

表示一次請求的接口,由服務器生成相應的實現類并傳送給上面的service(ServletRequest req, ServletResponse res)使用,用戶在實現自己的Servlet類是可以使用傳入的ServletRequest實現類中的各種方法,如請求地址,獲取請求參數,獲取cookie等。

4. javax.servlet.ServletResponse

表示一次相應的接口,由服務器生成相應的實現類并傳送給上面的service(ServletRequest req, ServletResponse res)使用,用戶在實現自己的Servlet類是可以使用傳入的ServletRequest實現類中的各種方法,如設置http相應頭部,向瀏覽器打印數據,跳轉頁面等。

用代碼說話

【圖一】

如圖所示,一個簡單的Servlet容器處理流程非常簡單,我們只需要在上篇文章中代碼基礎上稍加改動,就可以實現我們想要的功能。

接收http請求工作我們已經知道如何實現了,我們先從后兩項工作開始。

1. 實現ServletRequest和ServletResponse類

上篇文章我們也抽象了一個Request和Response類,但是這兩類并沒有繼承ServletRequestServletResponse接口,所以Servlet無法使用,所以我們需要分別繼承相應的接口。

1. 新Request類

原來Request中的方法都沒有變化,因為實現了ServletRequest接口,所以必須實現接口中定義的方法,但是現在我們還無需具體實現,大多都是返回null或留白。

/**
 * 表示請求值
 */
public class Request implements ServletRequest {

    private InputStream input;
    private String uri;

//    private String request;

    public Request(InputStream input) {
        this.input = input;
    }

    public void parse() {
        StringBuilder request = new StringBuilder(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);
        }
        catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }
        for (int j=0; j index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }

    public String getUri() {
        return uri;
    }

    @Override
    public Object getAttribute(String name) {
        return null;
    }

    @Override
    public Enumeration getAttributeNames() {
        return null;
    }

    @Override
    public String getCharacterEncoding() {
        return null;
    }
    //其他方法省略...
 
}
2. 新Response類

同新Request類一樣,新Response類也保留了原來的方法只是實現了ServletResponse接口,除了getWriter()方法因為稍后要用而實現了,其他ServletResponse接口方法均返回null或留白。

/**
 * 表示返回值
 */
public class Response implements ServletResponse {
    private static final int BUFFER_SIZE = 1024;
    private Request request;
    private OutputStream output;

    public Response(OutputStream output) {
        this.output = output;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    public void sendStaticResource() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        //讀取訪問地址請求的文件
        File file = new File(Constants.WEB_ROOT, request.getUri());
        try (FileInputStream fis = new FileInputStream(file)){
            if (file.exists()) {
                //如果文件存在
                //添加相應頭。
                StringBuilder heads=new StringBuilder("HTTP/1.1 200 OK
");
                heads.append("Content-Type: text/html
");
                //頭部
                StringBuilder body=new StringBuilder();
                //讀取響應主體
                int len ;
                while ((len=fis.read(bytes, 0, BUFFER_SIZE)) != -1) {
                    body.append(new String(bytes,0,len));
                }
                //添加Content-Length
                heads.append(String.format("Content-Length: %d
",body.toString().getBytes().length));
                heads.append("
");
                output.write(heads.toString().getBytes());
                output.write(body.toString().getBytes());
            } else {
                response404(output);
            }
        }catch (FileNotFoundException e){
            response404(output);
        }
    }


    private void response404(OutputStream output) throws IOException {
        StringBuilder response=new StringBuilder();
        response.append("HTTP/1.1 404 File Not Found
");
        response.append("Content-Type: text/html
");
        response.append("Content-Length: 23
");
        response.append("
");
        response.append("

File Not Found

"); output.write(response.toString().getBytes()); } @Override public PrintWriter getWriter() throws IOException { return new PrintWriter(output,true); } @Override public String getCharacterEncoding() { return null; } //省略其他方法。 }

這里需要注意是new PrintWriter(output,true)方法,閱讀方法注釋,摘錄如下內容:

autoFlush – A boolean; if true, the println, printf, or format methods will flush the output buffer

也就是說調用print方法不會輸出到瀏覽器頁面。原書中說這是一個問題需要解決。

我又閱讀了Servlet API文檔getWriter()相關內容(傳送門),摘錄如下內容:

Returns a PrintWriter object that can send character text to the client. The PrintWriter uses the character encoding returned by getCharacterEncoding(). If the response"s character encoding has not been specified as described in getCharacterEncoding (i.e., the method just returns the default value ISO-8859-1), getWriter updates it to ISO-8859-1.

Calling flush() on the PrintWriter commits the response.

我理解此方法返回的PrintWriter是需要調用flush()才會刷新,所以我對所有的打印方法println();printf();print()等是否需要每次都自動刷新產生了疑惑,姑且先到這,看書中后面的處理能否能答疑解惑。

我們只是實現一個簡單的Servlet容器示例,所以ServletRequestServletResponse其他方法留待以后實現。

2. 運行用戶的Servlet

上篇文章我們直接讀取靜態Html文件,然后將內容直接返回給瀏覽器,其實處理Servlet也差不多,只不過我們面對的class文件,我們需要利用ClassLoader將類加載進虛擬機,然后利用反射原理生成Servlet類的對象,然后就可以調用相應service()方法,運行編寫Servlet類程序員的代碼了。

1. 處理Servlet的方法
/**
 * Servlet的處理類
 */
public class ServletProcessor {

    /**
     * Servlet處理方法。
     *
     * @param request
     * @param response
     */
    public void process(Request request, Response response) {
        //解析Servlet類名
        String uri = request.getUri();
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        URLClassLoader loader = null;

        try {
            // create a URLClassLoader
            URL[] urls = new URL[1];
            URLStreamHandler streamHandler = null;
            File classPath = new File(Constants.WEB_ROOT);
            //類加載器加載路徑
            String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
            urls[0] = new URL(null, repository, streamHandler);
            loader = new URLClassLoader(urls);
        }
        catch (IOException e) {
            throw new IllegalStateException(e);
        }
        Class clazz = null;
        try {
            //加載Servlet類
            clazz = loader.loadClass(servletName);
        }
        catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }
        try {
            //初始化Servlet類
            Servlet servlet = (Servlet) clazz.newInstance();
            //寫入響應頭部,否則瀏覽器無法解析。
            PrintWriter writer=response.getWriter();
            writer.print("HTTP/1.1 200 OK
");
            writer.print("Content-Type: text/html
");
            writer.print("
");
            //print方法不會自動刷新。
            writer.flush();
            //調用Servlet類中service方法。
            servlet.service(request,response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}    

注意這這三行代碼,書中原始代碼沒有相應邏輯。

 writer.print("HTTP/1.1 200 OK
");
 writer.print("Content-Type: text/html
");
 writer.print("
");

和上篇文章一樣,也需要加響應頭部,否則瀏覽器無法解析,不過這個添加頭部的方法十分不簡陋,以后我們會優雅的實現。

2. 有沒有發現“壞味道”

注意這行代碼:servlet.service(request,response);我們將Request類和Response類直接傳入了service方法,如果熟悉這個容器的程序員就可以在自己的Servlet使用這兩個內部類和他的方法。

public class HelloWorldServlet implements Servlet {
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        Request request=(Request)req;
        Response response=(Response)res;
        request.parse();
    }
}

parse()方法并不是ServletRequest接口方法,我們不想暴露給程序員,但也不能parse()改成private因為容器中其他類也需要使用。
Tomcat用了一個非常巧妙的外觀模式(Facade)解決了這個問題。

3. RequestResponse的外觀模式

既然是因為RequestResponse向上轉換類型后傳輸出現了問題,我們就從這兩個類入手改造,引入RequestFacadeResponseFacade兩個類,這兩個類和RequestResponse一樣需要實現ServletRequestServletResponse接口。

- RequestFacade類

【圖二】

public class RequestFacade implements ServletRequest {

  private ServletRequest request = null;

  public RequestFacade(Request request) {
    this.request = request;
  }

  //實現ServletRequest中方法
  public Object getAttribute(String attribute) {
    return request.getAttribute(attribute);
  }

  public Enumeration getAttributeNames() {
    return request.getAttributeNames();
  }

  public String getRealPath(String path) {
    return request.getRealPath(path);
  }
  //其他方法省略...

- ResponseFacade類

【圖三】

public class ResponseFacade implements ServletResponse {

  private ServletResponse response;
  public ResponseFacade(Response response) {
    this.response = response;
  }
  //實現ServletResponse 中方法
  public void flushBuffer() throws IOException {
    response.flushBuffer();
  }

  public int getBufferSize() {
    return response.getBufferSize();
  }

  public String getCharacterEncoding() {
    return response.getCharacterEncoding();
  }
  //其他方法省略...

}

通過觀察兩個外觀類,其實他們什么也沒有做,所有的接口實現方法都是調用內部的ServletRequestServletResponse的具體實現類來處理的。我們可以這樣改造我們上面ServletProcessor類中的代碼

RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
servlet.service( requestFacade, responseFacade);

傳入Servlet實現類中service方法的參數變成了RequestFacadeResponseFacade類型,程序員就不能再代碼中使用類型轉換轉換為RequestResponse類型,所以RequestFacadeResponseFacade避免了原來RequestResponse類不希望對外可見的方法的暴露。

注:
1.其實從RequestFacadeResponseFacade實現和類圖上更像是代理模式,但是此處使用場景確實起到了對外提供統一接口的作用,所以從功能上講,叫外觀模式也無可或非。
2.即使采用了外觀類,程序員依然可以在Servlet中使用反射獲取到外觀類中private屬性的內部類型,但是和強制轉型相同,程序員應該按照Servlet協議編寫程序,否則除非清楚自己目的,不然我想不到這樣做的意義。

3. 處理瀏覽器請求
public class HttpServer {
    private static final String SHUTDOWN_COMMAND = "shutdown";
    private boolean shutdown = false;

    public static void main(String[] args) {
        HttpServer httpServer=new HttpServer();
        httpServer.await();
    }

    public void await() {
        try (ServerSocket serverSocket = new ServerSocket(8080)) {
            serverProcess(serverSocket);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void serverProcess(ServerSocket serverSocket) {
        while (!shutdown) {
            try (Socket socket = serverSocket.accept()) {
//                System.out.println(socket.hashCode());
                InputStream input = socket.getInputStream();
                OutputStream output = socket.getOutputStream();
                //創建Request對象
                Request request = new Request(input);
                request.parse();
                //創建Response對象
                Response response = new Response(output);
                response.setRequest(request);
                if (request.getUri().startsWith("/servlet/")) {
                    //如果地址以/servlet開頭就作為Servlet處理
                    ServletProcessor processor = new ServletProcessor();
                    processor.process(request, response);
                }else {
                    //否則作為靜態資源使用
                    StaticResourceProcessor processor = new StaticResourceProcessor();
                    processor.process(request, response);
                }
                shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

和上篇文章中處理用戶請求類似,我們保留了處理處理靜態資源的能力(StaticResourceProcessor具體實現見源碼),又增加了處理Servlet的功能。

4. 運行一下 1. 實現HelloWorldServlet

首先編寫一個自己的Servlet實現類。

public class HelloWorldServlet implements Servlet {
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        res.getWriter().println("

Hello World!

"); } //其他方法省略 }

注意,這個HelloWorldServlet不在任何package下,因為加載的時候就是用請求地址攜帶的類名加載,如果添加了包名,反射的時候會加載失敗,以后我們會修復這個問題。

編譯這個類,將編譯好的class文件放入D:webRoot文件夾(代碼中定義的路徑)。

2. 用瀏覽器發送請求

在瀏覽器地址欄輸入http://localhost:8080/servlet/HelloWorldServlet,瀏覽器會打印出Hello World!。

后記

至此我們實現了一個簡單的Servlet容器,雖然我們的功能非常簡陋,但是通過兩篇文章的講解,大家應該能理解一個瀏覽器請求是如何經過服務器處理最終返回可以顯示頁面的大致流程。是不是很有成就感,簡單的幾行代碼就能演示我們日常使用的Tomcat服務器的基本功能。不過我們只看到了冰山一角,今后的文章會逐步一覽全貌。

源碼

文中源碼地址:https://github.com/TmTse/tiny...

參考

《深入剖析Tomcat》

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77016.html

相關文章

  • 步一實現Tomcat之一——實現一個簡單Web服務器

    摘要:原書中主要內容是一步一步實現一個類似于的容器。圖一協議處于協議棧的應用層,傳遞的內容是報文,報文就相當于語言中的短語和句子用來表明意圖。類表示一次客戶端請求解析請求待實現解析待實現類表示返回值發送靜態頁面的相應報文待實現。 前言 最近在讀《How Tomcat Works》,收獲頗豐,在編寫書中示例的過程中也踩了不少坑。不知你有沒有體會,編程就一門是不試不知道,一試嚇一跳的實踐藝術。所...

    yearsj 評論0 收藏0
  • Hello World -- Java Web版(Java Web 入門教程)

    摘要:在中運行,輸出如下圖,則說明安裝成功下載本文使用的是最新穩定版并解壓到任意目錄。設置環境變量為解壓后的目錄,該目錄中應包含以下文件。運行打開工具,依次運行兩個命令的目錄注意將替換成具體的路徑。 在閱讀本文之前,你一定知道如何用Java語言寫出Hello, World!了。那么,用Java語言如何寫出Web版的Hello, World!,使之顯示在瀏覽器中呢?本文將一步一步演示如何寫出J...

    james 評論0 收藏0
  • 使用 Docker 搭建簡易 Java Web 環境 (二)

    摘要:創建一個環境最近公司正在使用開發網站應用,所以有必要了解下如何使用創建對應的環境。還好,提供了文檔的形式來組合多個容器來搭建開發環境。下一步我們將使用來構建更加復雜的開發環境。 showImg(https://segmentfault.com/img/remote/1460000011106825); 從《從最簡單的入手學習 Docker (一)》一文中,可以簡單的了解 Docker ...

    Tamic 評論0 收藏0

發表評論

0條評論

dayday_up

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<