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 (#改为@)