Tomcat中的连接器是一个独立的模块,可以被插入到servlet容器中,而且还有很多连接器可以使用。例如Coyote,mod_jk,mod_jk2,mod_webapp等。Tomcat中使用的连接器必须满足以下条件:
1.实现org.apache.catalina.Connector接口
2.负责创建实现了org.apache.catalina.Request接口的request对象
3.负责创建实现了org.apache.catalina.Response接口的response对象
Tomcat的连接器等待引入的HTTP请求,创建request对象和response对象,然后调用org.apache.catalina.Container接口的invoke()方法,将request对象和response对象传给servlet容器。
package com.whatsmars.tomcat.connector; /** * Created by shenhongxi on 16/4/11. */ public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); Container container = new SimpleContainer(); connector.setContainer(container); connector.setBufferSize(2048); connector.start(); } }
package com.whatsmars.tomcat.connector; import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /** * Created by shenhongxi on 16/4/11. */ public class HttpConnector implements Runnable { private Container container; boolean stopped; private String scheme = "http"; private int bufferSize; 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() { new Thread(this).start(); } public String getScheme() { return scheme; } public int getBufferSize() { return bufferSize; } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } public Container getContainer() { return container; } public void setContainer(Container container) { this.container = container; } }
package com.whatsmars.tomcat.connector; import org.apache.catalina.util.Enumerator; import org.apache.catalina.util.ParameterMap; import org.apache.catalina.util.RequestUtil; import javax.servlet.*; import javax.servlet.http.*; import java.io.BufferedReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.Principal; import java.util.*; /** * Created by shenhongxi on 16/4/11. */ public class HttpRequest implements HttpServletRequest { private String requestURI; private int contentLength; private String contentType; private String queryString; private String method; private String protocol; protected Map headers = new HashMap(); protected SocketInputStream input; /** * The parsed parameters for this request. This is populated only if * parameter information is requested via one of the * <code>getParameter()</code> family of method calls. The key is the * parameter name, while the value is a String array of values for this * parameter. * <p> * <strong>IMPLEMENTATION NOTE</strong> - Once the parameters for a * particular request are parsed and stored here, they are not modified. * Therefore, application level access to the parameters need not be * synchronized. */ protected ParameterMap parameters = null; // extends LinkedHashMap has a boolean var 'locked' /** * Have the parameters for this request been parsed yet? */ protected boolean parsed = false; public HttpRequest(SocketInputStream input) { this.input = input; } public void addHeader(String name, String value) { name = name.toLowerCase(); synchronized (headers) { ArrayList values = (ArrayList) headers.get(name); if (values == null) { values = new ArrayList(); headers.put(name, values); } values.add(value); } } public String getParameter(String name) { parseParameters(); String values[] = (String[]) parameters.get(name); if (values != null) return (values[0]); else return (null); } public Map getParameterMap() { parseParameters(); return (this.parameters); } public Enumeration getParameterNames() { parseParameters(); return (new Enumerator(parameters.keySet())); } public String[] getParameterValues(String name) { parseParameters(); String values[] = (String[]) parameters.get(name); if (values != null) return (values); else return null; } /** * 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 (Exception 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; } public void setRequestURI(String requestURI) { this.requestURI = requestURI; } public void setContentLength(int contentLength) { this.contentLength = contentLength; } public void setContentType(String contentType) { this.contentType = contentType; } public void setQueryString(String queryString) { this.queryString = queryString; } public void setMethod(String method) { this.method = method; } public void setProtocol(String protocol) { this.protocol = protocol; } // ... other methods }
package com.whatsmars.tomcat.connector; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.Collection; import java.util.Locale; /** * Created by shenhongxi on 16/4/11. */ public class HttpResponse implements HttpServletResponse { OutputStream output; HttpRequest request; PrintWriter writer; public HttpResponse(OutputStream output) { this.output = output; } public void setRequest(HttpRequest request) { this.request = request; } /** * call this method to send headers and response to the output */ public void finishResponse() { // sendHeaders(); // Flush and close the appropriate output mechanism if (writer != null) { writer.flush(); writer.close(); } } public void addCookie(Cookie cookie) { } public boolean containsHeader(String name) { return false; } public String encodeURL(String url) { return null; } public String encodeRedirectURL(String url) { return null; } public String encodeUrl(String url) { return null; } public String encodeRedirectUrl(String url) { return null; } public void sendError(int sc, String msg) throws IOException { } public void sendError(int sc) throws IOException { } public void sendRedirect(String location) throws IOException { } // ... other methods }
package com.whatsmars.tomcat.connector; import com.whatsmars.tomcat.servlet.StaticResourceProcessor; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; /** * Created by shenhongxi on 16/4/11. */ public class HttpProcessor { private HttpConnector connector; private HttpRequest request; private HttpResponse response; private HttpRequestLine requestLine = new HttpRequestLine(); public HttpProcessor(HttpConnector connector) { this.connector = connector; } public void process(Socket socket) { SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize()); // 1.读取套接字的输入流 output = socket.getOutputStream(); // create HttpRequest object and parse request = new HttpRequest(input); response = new HttpResponse(output); response.setRequest(request); response.setHeader("Server", "Mars Servlet Container"); parseRequest(input, output); // 解析请求行,即HTTP请求的第一行内容 parseHeaders(input); // 解析请求头 if (request.getRequestURI().startsWith("/servlet/")) { connector.getContainer().invoke((HttpServletRequest) request, (HttpServletResponse) response); } else { StaticResourceProcessor processor = new StaticResourceProcessor(); //processor.process(request, response); } socket.close(); } catch (Exception e) { e.printStackTrace(); } } private void parseHeaders(SocketInputStream input) throws IOException, ServletException{ while (true) { // 一行一行解析完header HttpHeader header = new HttpHeader(); // Read the next header input.readHeader(header); if (header.nameEnd == 0) { if (header.valueEnd == 0) { return; } else { throw new ServletException("httpProcessor parseHeaders colon"); } } String name = new String(header.name, 0, header.nameEnd); String value = new String(header.value, 0, header.valueEnd); request.addHeader(name, value); // do something for some headers, ignore others. if (name.equals("cookie")) { // ... // request.addCookie(cookies[i]); } else if (name.equals("content-length")) { int n = -1; try { n = Integer.parseInt(value); } catch (Exception e) { throw new ServletException("httpProcessor.parseHeaders.contentLength"); } request.setContentLength(n); } else if (name.equals("content-type")) { request.setContentType(value); } } } private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException { input.readRequestLine(requestLine); String method = new String(requestLine.method, 0, requestLine.methodEnd); String uri = null; String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd); // Validate the incoming request line if (method.length() < 1) { throw new ServletException("Missing HTTP request method"); } else if (requestLine.uriEnd < 1) { throw new ServletException("Missing HTTP request URI"); } // Parse any query parameters out of the request URI int question = requestLine.indexOf("?"); if (question >= 0) { request.setQueryString(new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1)); uri = new String(requestLine.uri, 0, question); } else { request.setQueryString(null); uri = new String(requestLine.uri, 0, requestLine.uriEnd); } String normalizedUri = normalize(uri); ((HttpRequest) request).setMethod(method); request.setProtocol(protocol); if (normalizedUri != null) { ((HttpRequest) request).setRequestURI(normalizedUri); } else { ((HttpRequest) request).setRequestURI(uri); } if (normalizedUri == null) { throw new ServletException("Invalid URI: " + uri + "'"); } } // Return a context-relative path, beginning with a "/" protected String normalize(String path) { if (path == null) return null; String normalized = path; // ... return path; } }
package com.whatsmars.tomcat.connector; /** * Created by shenhongxi on 16/4/13. */ public final class HttpHeader { public static final int INITIAL_NAME_SIZE = 32; public static final int INITIAL_VALUE_SIZE = 64; public static final int MAX_NAME_SIZE = 128; public static final int MAX_VALUE_SIZE = 4096; public char[] name; public int nameEnd; public char[] value; public int valueEnd; protected int hashCode = 0; public HttpHeader() { this(new char[INITIAL_NAME_SIZE], 0, new char[INITIAL_VALUE_SIZE], 0); } public HttpHeader(char[] name, int nameEnd, char[] value, int valueEnd) { this.name = name; this.nameEnd = nameEnd; this.value = value; this.valueEnd = valueEnd; } public HttpHeader(String name, String value) { this.name = name.toLowerCase().toCharArray(); this.nameEnd = name.length(); this.value = value.toCharArray(); this.valueEnd = value.length(); } /** * Release all object references, and initialize instance variables, in * preparation for reuse of this object. */ public void recycle() { nameEnd = 0; valueEnd = 0; hashCode = 0; } }
package com.whatsmars.tomcat.connector; /** * Created by shenhongxi on 16/4/13. */ public final class HttpRequestLine { public static final int INITIAL_METHOD_SIZE = 8; public static final int INITIAL_URI_SIZE = 64; public static final int INITIAL_PROTOCOL_SIZE = 8; public static final int MAX_METHOD_SIZE = 1024; public static final int MAX_URI_SIZE = 32768; public static final int MAX_PROTOCOL_SIZE = 1024; public char[] method; public int methodEnd; public char[] uri; public int uriEnd; public char[] protocol; public int protocolEnd; public HttpRequestLine() { this(new char[INITIAL_METHOD_SIZE], 0, new char[INITIAL_URI_SIZE], 0, new char[INITIAL_PROTOCOL_SIZE], 0); } public HttpRequestLine(char[] method, int methodEnd, char[] uri, int uriEnd, char[] protocol, int protocolEnd) { this.method = method; this.methodEnd = methodEnd; this.uri = uri; this.uriEnd = uriEnd; this.protocol = protocol; this.protocolEnd = protocolEnd; } public int indexOf(String str) { // ... return -1; } /** * Release all object references, and initialize instance variables, in * preparation for reuse of this object. */ public void recycle() { methodEnd = 0; uriEnd = 0; protocolEnd = 0; } }
package com.whatsmars.tomcat.connector; import java.io.IOException; import java.io.InputStream; /** * Created by shenhongxi on 16/4/11. * Extends InputStream to be more efficient reading lines during HTTP header processing. */ public class SocketInputStream extends InputStream { /** * Underlying input stream. */ private InputStream input; /** * Internal buffer. */ protected byte[] buf; /** * Last valid byte. */ protected int count; /** * Position in the buffer. */ protected int pos; public SocketInputStream(InputStream input, int bufferSize) { this.input = input; this.buf = new byte[bufferSize]; } // input => buf => HttpRequestLine public void readRequestLine(HttpRequestLine requestLine) throws IOException { // Recycling check if (requestLine.methodEnd != 0) requestLine.recycle(); // Checking for a blank line // Reading the method name // Reading URI // Reading protocol } // input => buf => HttpHeader public void readHeader(HttpHeader header) throws IOException { // Recycling check if (header.nameEnd != 0) header.recycle(); // Checking for a blank line // Reading the header name // Reading the header value (which can be spanned over multiple lines) } @Override public int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return buf[pos++] & 0xff; } /** * Fill the internal buffer using data from the undelying input stream. */ protected void fill() throws IOException { pos = 0; count = 0; int nRead = input.read(buf, 0, buf.length); if (nRead > 0) { count = nRead; } } }
package com.whatsmars.tomcat.connector; import com.whatsmars.tomcat.servlet.Constants; import javax.servlet.Servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; /** * Created by shenhongxi on 16/4/14. */ public class SimpleContainer implements Container { public void invoke(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String servletName = ( (HttpServletRequest) request).getRequestURI(); servletName = servletName.substring(servletName.lastIndexOf("/") + 1); URLClassLoader loader = null; try { 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) { System.out.println(e.toString() ); } Class myClass = null; try { myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { System.out.println(e.toString()); } Servlet servlet = null; try { servlet = (Servlet) myClass.newInstance(); servlet.service((HttpServletRequest) request, (HttpServletResponse) response); } catch (Exception e) { System.out.println(e.toString()); } catch (Throwable e) { System.out.println(e.toString()); } } }
package com.whatsmars.tomcat.connector; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * Created by shenhongxi on 16/4/14. */ public interface Container { public void invoke(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; }
最后,关于HttpProcessor池,连接器线程和处理器线程的等待与唤醒,请参考多线程知识自行脑补。
Request和Response的外观类(包装类)参见http://wely.iteye.com/blog/2290575