標籤: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容器