模拟tomcat连接器

阅读更多

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
     * getParameter() family of method calls.  The key is the
     * parameter name, while the value is a String array of values for this
     * parameter.
     * 

* IMPLEMENTATION NOTE - 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

 

 

 

你可能感兴趣的:(tomcat连接器)