您的位置:首页 > 运维架构 > Tomcat

模拟tomcat连接器

2017-08-25 00:00 204 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tomcat连接器