Servlet容器有兩個主要的模塊,即連接器(connector)與容器(container),本文接下來創建一個連接器來增強前面文章中的應用程序的功能,以一種更優雅的方式來創建request對象和response對象;為了兼容Servlet 2.3和2.4,連接器這里創建的是javax.servlet.http.HttpServletRequest對象和javax.servlet.http.HttpServletResponse對象(servlet對象類型可以是實現javax.servlet.Servlet接口或繼承自javax.servlet.GenericServlet類或javax.servlet.http.HttpServlet類)。
在本文的應用程序中,連接器會解析http請求頭、cookies和請求參數等;同時修改了Response類的getWriter()方法,使其工作得更好。
本文首先要介紹一下在servlet容器中是怎么實現錯誤消息國際化的,這里主要是StringManager類實現的功能
Tomcat是將錯誤消息存儲在properties文件中,便于讀取和編輯;可是由于 tomcat的類特別多,將所有類使用的錯誤消息都存儲在同一個properties文件中將會造成維護的困難;所以tomcat的處理方式是將properties劃分在不同的包中,每個properties文件都是用StringManager類的一個實例來處理,這樣在tomcat運行時,會產生StringManager類的多個實例;同一個包里面的類共享一個StringManager類的實例(這里采用單例模式);這些不同包用到的的StringManager類的實例存儲在一個hashtable容器中,以包名作為key存儲StringManager類的實例
public class StringManager { /** * The ResourceBundle for this StringManager. */ private ResourceBundle bundle; /** * Creates a new StringManager for a given package. This is a * private method and all access to it is arbitrated by the * static getManager method call so that only one StringManager * per package will be created. * * @param packageName Name of package to create StringManager for. */ private StringManager(String packageName) { String bundleName = packageName + ".LocalStrings" ; bundle = ResourceBundle.getBundle(bundleName); } /** * Get a string from the underlying resource bundle. * * @param key */ public String getString(String key) { if (key == null ) { String msg = "key is null" ; throw new NullPointerException(msg); } String str = null ; try { str = bundle.getString(key); } catch (MissingResourceException mre) { str = "Cannot find message associated with key '" + key + "'" ; } return str; } // -------------------------------------------------------------- // STATIC SUPPORT METHODS // -------------------------------------------------------------- private static Hashtable managers = new Hashtable(); /** * Get the StringManager for a particular package. If a manager for * a package already exists, it will be reused, else a new * StringManager will be created and returned. * * @param packageName */ public synchronized static StringManager getManager(String packageName) { StringManager mgr = (StringManager)managers.get(packageName); if (mgr == null ) { mgr = new StringManager(packageName); managers.put(packageName, mgr); } return mgr; } }
下面我們來分析連接器是怎樣實現的:
public class HttpConnector implements Runnable { boolean stopped; private String scheme = "http" ; public String getScheme() { return scheme; } public void run() { 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 ); } while (! stopped) { // Accept the next incoming connection from the server socket Socket socket = null ; try { socket = serverSocket.accept(); } catch (Exception e) { continue ; } // Hand this socket off to an HttpProcessor HttpProcessor processor = new HttpProcessor( this ); processor.process(socket); } } public void start() { Thread thread = new Thread( this ); thread.start(); } }
我們從HttpConnector連接器的實現可以看到,該連接器負責監聽客戶端請求,當監聽到客戶端請求時,將獲取的socket對象交給HttpProcessor對象的process()方法處理
我們接下來分析HttpProcessor類的實現:
/* this class used to be called HttpServer */ public class HttpProcessor { public HttpProcessor(HttpConnector connector) { this .connector = connector; } /** * The HttpConnector with which this processor is associated. */ private HttpConnector connector = null ; private HttpRequest request;
private HttpResponse response; public void process(Socket socket) { SocketInputStream input = null ; OutputStream output = null ; try {
// 包裝為SocketInputStream對象 input = new SocketInputStream(socket.getInputStream(), 2048 ); output = socket.getOutputStream(); // create HttpRequest object and parse request = new HttpRequest(input); // create HttpResponse object response = new HttpResponse(output); response.setRequest(request); response.setHeader( "Server", "Pyrmont Servlet Container" ); // 解析請求行 parseRequest(input, output);
// 解析請求頭 parseHeaders(input); // check if this is a request for a servlet or a static resource // a request for a servlet begins with "/servlet/" if (request.getRequestURI().startsWith("/servlet/" )) { ServletProcessor processor = new ServletProcessor(); processor.process(request, response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(request, response); } // Close the socket socket.close(); // no shutdown for this application } catch (Exception e) { e.printStackTrace(); } }
上面的方法中獲取socket對象的輸入流與輸出流分別創建Request對象與Response對象,然后解析http請求行與請求頭(并填充到Request對象的相應屬性),最后分發給處理器處理
接下來的Request對象實現了javax.servlet.http.HttpServletRequest接口,主要是提供一些設置相關請求參數的方法和獲取相關請求參數的方法
http請求頭、cookies和請求參數等信息分別存儲在如下成員變量中
protected ArrayList cookies = new ArrayList();
protected HashMap headers = new HashMap();
protected ParameterMap parameters = null;
需要注意的是protected void parseParameters()方法只需執行一次,該方法是用來初始化ParameterMap parameters成員變量,方法如下
/** * Parse the parameters of this request, if it has not already occurred. * If parameters are present in both the query string and the request * content, they are merged. */ protected void parseParameters() { if (parsed) return ; ParameterMap results = parameters; if (results == null ) results = new ParameterMap(); results.setLocked( false ); String encoding = getCharacterEncoding(); if (encoding == null ) encoding = "ISO-8859-1" ; // Parse any parameters specified in the query string String queryString = getQueryString(); try { RequestUtil.parseParameters(results, queryString, encoding); } catch (UnsupportedEncodingException e) { ; } // Parse any parameters specified in the input stream String contentType = getContentType(); if (contentType == null ) contentType = "" ; int semicolon = contentType.indexOf(';' ); if (semicolon >= 0 ) { contentType = contentType.substring(0 , semicolon).trim(); } else { contentType = contentType.trim(); } if ("POST".equals(getMethod()) && (getContentLength() > 0 ) && "application/x-www-form-urlencoded" .equals(contentType)) { try { int max = getContentLength(); int len = 0 ; byte buf[] = new byte [getContentLength()]; ServletInputStream is = getInputStream(); while (len < max) { int next = is.read(buf, len, max - len); if (next < 0 ) { break ; } len += next; } is.close(); if (len < max) { throw new RuntimeException("Content length mismatch" ); } RequestUtil.parseParameters(results, buf, encoding); } catch (UnsupportedEncodingException ue) { ; } catch (IOException e) { throw new RuntimeException("Content read fail" ); } } // Store the final results results.setLocked( true ); parsed = true ; parameters = results; }
我就不明白,該方法為什么不采取同步鎖關鍵字?難道是對初始化ParameterMap parameters成員變量次數沒有硬性要求?
上面方法中的ParameterMap對象,繼承自HashMap,采用boolean locked = false成員變量設置寫入的鎖(這玩意也有問題)
public final class ParameterMap extends HashMap { // ----------------------------------------------------------- Constructors /** * Construct a new, empty map with the default initial capacity and * load factor. */ public ParameterMap() { super (); } /** * Construct a new, empty map with the specified initial capacity and * default load factor. * * @param initialCapacity The initial capacity of this map */ public ParameterMap( int initialCapacity) { super (initialCapacity); } /** * Construct a new, empty map with the specified initial capacity and * load factor. * * @param initialCapacity The initial capacity of this map * @param loadFactor The load factor of this map */ public ParameterMap( int initialCapacity, float loadFactor) { super (initialCapacity, loadFactor); } /** * Construct a new map with the same mappings as the given map. * * @param map Map whose contents are dupliated in the new map */ public ParameterMap(Map map) { super (map); } // ------------------------------------------------------------- Properties /** * The current lock state of this parameter map. */ private boolean locked = false ; /** * Return the locked state of this parameter map. */ public boolean isLocked() { return ( this .locked); } /** * Set the locked state of this parameter map. * * @param locked The new locked state */ public void setLocked( boolean locked) { this .locked = locked; } /** * The string manager for this package. */ private static final StringManager sm = StringManager.getManager( "org.apache.catalina.util" ); // --------------------------------------------------------- Public Methods /** * Remove all mappings from this map. * * @exception IllegalStateException if this map is currently locked */ public void clear() { if (locked) throw new IllegalStateException (sm.getString( "parameterMap.locked" )); super .clear(); } /** * Associate the specified value with the specified key in this map. If * the map previously contained a mapping for this key, the old value is * replaced. * * @param key Key with which the specified value is to be associated * @param value Value to be associated with the specified key * * @return The previous value associated with the specified key, or * <code>null</code> if there was no mapping for key * * @exception IllegalStateException if this map is currently locked */ public Object put(Object key, Object value) { if (locked) throw new IllegalStateException (sm.getString( "parameterMap.locked" )); return ( super .put(key, value)); } /** * Copy all of the mappings from the specified map to this one. These * mappings replace any mappings that this map had for any of the keys * currently in the specified Map. * * @param map Mappings to be stored into this map * * @exception IllegalStateException if this map is currently locked */ public void putAll(Map map) { if (locked) throw new IllegalStateException (sm.getString( "parameterMap.locked" )); super .putAll(map); } /** * Remove the mapping for this key from the map if present. * * @param key Key whose mapping is to be removed from the map * * @return The previous value associated with the specified key, or * <code>null</code> if there was no mapping for that key * * @exception IllegalStateException if this map is currently locked */ public Object remove(Object key) { if (locked) throw new IllegalStateException (sm.getString( "parameterMap.locked" )); return ( super .remove(key)); } }
同樣Response對象實現了javax.servlet.http.HttpServletResponse接口
這里改寫了前面文章中的getWriter()方法輸出字符流到客戶端
public PrintWriter getWriter() throws IOException { ResponseStream newStream = new ResponseStream( this ); newStream.setCommit( false ); OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding()); writer = new ResponseWriter(osr); return writer; }
---------------------------------------------------------------------------?
本系列How Tomcat Works系本人原創?
轉載請注明出處 博客園 刺猬的溫馴?
本人郵箱: chenying998179 # 163.com ( #改為@ )
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
