how tomcat works 讀書筆記(二)----------一個簡單的servlet容器

來源:互聯網
上載者:User

標籤:web伺服器   tomcat   socket   servlet   讀書筆記   

app1(建議讀者在看本章之前,先看how tomcat works 讀書筆記(一)----------一個簡單的web伺服器 http://blog.csdn.net/dlf123321/article/details/39378157)
回顧我們上一章,我們開發了一個最最簡單的web伺服器,它可以使使用者訪問伺服器內的靜態資源。當然這是遠遠不夠的,在這一節裡,我們就試著讓伺服器在能相應靜態資源的基礎上繼續支援servlet。
servlet介面
javax.servlet.Servlet介面定義了五個方法,如下:

我們所使用的servlet都是Servlet介面的間接實作類別。
每當我們執行個體化一個servlet的時候,servlet容器都會先唯一的調用一次init()方法進行初始化,然後再調用service()方法來處理使用者的需求,最後在銷毀servlet前容器會調用destroy()方法。
我們看看,這一節我們要用的測試servlet。
import javax.servlet.*;import com.jspsmart.upload.Request;import java.io.IOException;import java.io.PrintWriter;public class PrimitiveServlet implements Servlet {  public void init(ServletConfig config) throws ServletException {    System.out.println("init");  }  public void service(ServletRequest request, ServletResponse response)    throws ServletException, IOException {    System.out.println("from service");    PrintWriter out = response.getWriter();    out.println("Hello. Roses are red.");    out.print("Violets are blue."); //   out.flush();  }  public void destroy() {    System.out.println("destroy");  }  public String getServletInfo() {    return null;  }  public ServletConfig getServletConfig() {    return null;  }}
Servlet定義的5個方法,它都實現了(不過我們這一節要實現的servlet還沒有能力自動調用init和destroy方法)。


再看看我們的主方法,程式就從這裡開始(和上一節的HttpServer差別不是很大)
package ex02.pyrmont;import java.net.Socket;import java.net.ServerSocket;import java.net.InetAddress;import java.io.InputStream;import java.io.OutputStream;import java.io.IOException;public class HttpServer1 {  /** WEB_ROOT is the directory where our HTML and other files reside.   *  For this package, WEB_ROOT is the "webroot" directory under the working   *  directory.   *  The working directory is the location in the file system   *  from where the java command was invoked.   */  // shutdown command  private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";  // the shutdown command received  private boolean shutdown = false;  public static void main(String[] args) {    HttpServer1 server = new HttpServer1();    server.await();  }  public void await() {    ServerSocket serverSocket = null;    int port = 8080;    try {      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));    }    catch (IOException e) {      e.printStackTrace();      System.exit(1);    }    // Loop waiting for a request    while (!shutdown) {      Socket socket = null;      InputStream input = null;      OutputStream output = null;      try {        socket = serverSocket.accept();        input = socket.getInputStream();        output = socket.getOutputStream();        // create Request object and parse        Request request = new Request(input);        request.parse();        // create Response object        Response response = new Response(output);        response.setRequest(request);  //沒有再這裡直接處理靜態資源                               //而是判斷到底請求的事靜態資源還是servlet        // check if this is a request for a servlet or a static resource        // a request for a servlet begins with "/servlet/"        if (request.getUri().startsWith("/servlet/")) {          ServletProcessor1 processor = new ServletProcessor1();          processor.process(request, response);        }        else {          StaticResourceProcessor processor = new StaticResourceProcessor();          processor.process(request, response);        }        // Close the socket        socket.close();        //check if the previous URI is a shutdown command        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);      }      catch (Exception e) {        e.printStackTrace();        System.exit(1);      }    }  }}


大家可以很清楚地看到,這部分和上一節的不同主要就在response部分,更準確的說就是在這
  
if (request.getUri().startsWith("/servlet/")) {          ServletProcessor1 processor = new ServletProcessor1();          processor.process(request, response);        }        else {          StaticResourceProcessor processor = new StaticResourceProcessor();          processor.process(request, response);        }


我們看看,如果請求是以servlet開頭的該如何處理。
package ex02.pyrmont;import java.net.URL;import java.net.URLClassLoader;import java.net.URLStreamHandler;import java.io.File;import java.io.IOException;import javax.servlet.Servlet;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;public class ServletProcessor1 {  public void process(Request request, Response response) {    String uri = request.getUri();    String servletName = uri.substring(uri.lastIndexOf("/") + 1);        URLClassLoader loader = null;    try {      // create a URLClassLoader   //try部分的主要功能就是產生一個URLClassLoader      URL[] urls = new URL[1];      URLStreamHandler streamHandler = null;            File classPath = new File(Constants.WEB_ROOT);      //下面的URL建構函式為      //java.net.URL.URL(String protocol, String host, String file) throws MalformedURLException      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;       System.out.println(servletName+"*****"+Constants.WEB_ROOT+"   "+repository);      //下面的URL建構函式為      //java.net.URL.URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException      //至於 URLStreamHandler是什麼東西 暫時不必理會      urls[0] = new URL(null, repository, streamHandler);      loader = new URLClassLoader(urls);    }    catch (IOException e) {      System.out.println(e.toString() );    }    Class<?> myClass = null;    try {      myClass = loader.loadClass(servletName);   //載入請求的servlet                                                     }    catch (ClassNotFoundException e) {      System.out.println(e.toString());    }    Servlet servlet = null;    try {      servlet = (Servlet) myClass.newInstance();  //產生執行個體 並調用service方法      servlet.service((ServletRequest) request, (ServletResponse) response);    }    catch (Exception e) {      System.out.println(e.toString());    }    catch (Throwable e) {      System.out.println(e.toString());    }  }}


看看另一部分,如果請求的不是servlet(這一節就是靜態資源)該如何處理

package ex02.pyrmont;import java.io.IOException;public class StaticResourceProcessor {  public void process(Request request, Response response) {    try {      response.sendStaticResource();    }    catch (IOException e) {      e.printStackTrace();    }  }}


跟第一節沒有什麼區別。
我們的request類要實現ServletRequest介面
package ex02.pyrmont;import java.io.InputStream;import java.io.IOException;import java.io.BufferedReader;import java.io.UnsupportedEncodingException;import java.util.Enumeration;import java.util.Locale;import java.util.Map;import javax.servlet.AsyncContext;import javax.servlet.DispatcherType;import javax.servlet.RequestDispatcher;import javax.servlet.ServletContext;import javax.servlet.ServletInputStream;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;public class Request implements ServletRequest {  private InputStream input;  private String uri;  public Request(InputStream input) {    this.input = input;  }  public String getUri() {    return uri;  }  private String parseUri(String requestString) {    int index1, index2;    index1 = requestString.indexOf(' ');    if (index1 != -1) {      index2 = requestString.indexOf(' ', index1 + 1);      if (index2 > index1)        return requestString.substring(index1 + 1, index2);    }    return null;  }  public void parse() {    // Read a set of characters from the socket    StringBuffer request = new StringBuffer(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<i; j++) {      request.append((char) buffer[j]);    }    System.out.print(request.toString());    uri = parseUri(request.toString());  }  public void removeAttribute(String attribute) {  }  public void setAttribute(String key, Object value) {  }  public void setCharacterEncoding(String encoding)    throws UnsupportedEncodingException {  }//缺少的方法 用Eclipse自動產生即可 不再贅述}

同樣的response也要實現ServletResponse介面
package ex02.pyrmont;import java.io.OutputStream;import java.io.IOException;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.File;import java.io.PrintWriter;import java.util.Locale;import javax.servlet.ServletResponse;import javax.servlet.ServletOutputStream;import com.sun.org.apache.bcel.internal.generic.NEW;public class Response implements ServletResponse {  private static final int BUFFER_SIZE = 1024;  Request request;  OutputStream output;  PrintWriter writer;  public Response(OutputStream output) {    this.output = output;  }  public void setRequest(Request request) {    this.request = request;  }  /* This method is used to serve a static page */  public void sendStaticResource() throws IOException {    byte[] bytes = new byte[BUFFER_SIZE];    FileInputStream fis = null;    try {      /* request.getUri has been replaced by request.getRequestURI */    System.out.println(Constants.WEB_ROOT+" ***** "+ request.getUri());      File file = new File(Constants.WEB_ROOT, request.getUri());      fis = new FileInputStream(file);      /*         HTTP Response = Status-Line           *(( general-header | response-header | entity-header ) CRLF)           CRLF           [ message-body ]         Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF      */      int ch = fis.read(bytes, 0, BUFFER_SIZE);      System.out.println("response  **");      while (ch!=-1) {        output.write(bytes, 0, ch);        ch = fis.read(bytes, 0, BUFFER_SIZE);      }    }    catch (FileNotFoundException e) {      String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +        "Content-Type: text/html\r\n" +        "Content-Length: 23\r\n" +        "\r\n" +        "<h1>File Not Found</h1>";      output.write(errorMessage.getBytes());    }    finally {      if (fis!=null)        fis.close();    }  }  public PrintWriter getWriter() throws IOException {    // autoflush is true, println() will flush,    // but print() will not.    writer = new PrintWriter(output, true);    return writer;  }  //一樣 部分代碼 直接讓Eclipse 自動產生即可}


實驗一下
http://localhost:8080/servlet/PrimitiveServlet


控制台顯示

GET /servlet/PrimitiveServlet HTTP/1.1
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, 
Accept-Language: zh-cn
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1)
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive


PrimitiveServlet*****D:\Java Code\UpLoad\webroot   file:D:\Java Code\UpLoad\webroot\
from service



大家仔細看看
Violets are blue. 這句話沒有列印出來
大家看看Response類getWriter方法裡的PrintWriter一行
上面的英文,我就不解釋了
這個bug在以後的版本中會修改




app2上面的代碼其實還存在一個問題
在request部分中parseUri是private的,在這一節中這樣其實是可以的,但問題是這個方法有可能在外部類中使用,應該是public的。
如果改成public,問題又出現了
  public void service(ServletRequest request, ServletResponse response)    throws ServletException, IOException {    System.out.println("from service");    Request r=(Request) request;    System.out.println(r.parseUri("sdf"));    PrintWriter out = response.getWriter();    out.println("Hello. Roses are red.");    out.print("Violets are blue.");    out.flush();  }


我把servlet類中的service改成上面的樣子,大家看看,其實request的parseUri方法在servlet裡是不應該被使用的!
那怎麼辦?
第一種,將parseUri方法的public方法改成預設的存取修飾詞,包外的類就不能訪問了。
不過還有另一種方式
門面模式!!
在上面的例子中,我們寫的request實現了ServletRequest,我們可以再寫一個類RequestFacade類讓他也實現ServletRequest(response 類似)



package ex02.pyrmont;import java.io.IOException;import java.io.BufferedReader;import java.io.UnsupportedEncodingException;import java.util.Enumeration;import java.util.Locale;import java.util.Map;import javax.servlet.RequestDispatcher;import javax.servlet.ServletInputStream;import javax.servlet.ServletRequest;public class RequestFacade implements ServletRequest {  private ServletRequest request = null;  public RequestFacade(Request request) {    this.request = request;  }    public String getRemoteHost() {    return request.getRemoteHost();  }  //省略對ServletRequest介面的實現  //servletrequest介面的方法 在RequestFacade 內部實現時 都調用類的屬性request來實現  public void setCharacterEncoding(String encoding)    throws UnsupportedEncodingException {    request.setCharacterEncoding(encoding);  }}再看看  ServletProcessor 改成如下    Servlet servlet = null;    RequestFacade requestFacade = new RequestFacade(request);    ResponseFacade responseFacade = new ResponseFacade(response);    try {      servlet = (Servlet) myClass.newInstance();      servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);    }    catch (Exception e) {      System.out.println(e.toString());    }    catch (Throwable e) {      System.out.println(e.toString());    }httpServer 也改成如下 if (request.getUri().startsWith("/servlet/")) {          ServletProcessor2 processor = new ServletProcessor2();          processor.process(request, response);        }


app1給servlet裡面傳遞的參數實際上是Request類型的,有調用parseUri方法的危險。
app2給servlet裡面傳遞的參數實際上是RequestFacade,它根本就沒有parseUri方法,自然就安全了。



這篇文章中 對書中的源碼有刪減 大家可以下載完整版本的
深入剖析tomcat 源碼
http://down.51cto.com/data/486495








how tomcat works 讀書筆記(二)----------一個簡單的servlet容器

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.