catalina简介
Catalina是一个成熟的软件(也就是servlet),设计和开发的十分优雅,功能结构也是模块化的。是tomcat的核心组件。
Catalina可以划分为两个模块: 连接器(connector)和容器(container)结构如下:
这个连接器来增强之前的示例一个简单servlet容器
源码下载地址
请看:链接: https://pan.baidu.com/s/1trwFTSxiyrvPyzW6N8ugFw 提取码: jhi8
运行需要之前的jar包
由于现在大部分程序都更新了tomcat8及tomcat8+,所以按照示例中的代码编写会有很多错误。
pom文件中需要引入老版本的catalina包,才会不显示红叉
<!-- https://mvnrepository.com/artifact/tomcat/catalina -->
<dependency>
<groupId>tomcat</groupId>
<artifactId>catalina</artifactId>
<version>4.1.36</version>
</dependency>
这个catalina连接器比之前servlet容器示例,主要有哪些变化呢?
- 启动方式,由命令行改为启动类(建立新线程执行)
package com.chl.webserver.catalina.connector.http;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 负责创建一个服务器套接字,该套接字会等待传入的HTTP请求。每启动会创建一个HttpConnector实例,该实例是独立线程。
* @author chenhailong
*
*/
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();
}
}
- 在获取socket请求流信息时,对请求行、请求头做了较好的解析(做网关项目会有类似需求)
package com.chl.webserver.catalina.connector.http;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.StringManager;
import com.chl.webserver.catalina.ServletProcessor;
import com.chl.webserver.catalina.StaticResourceProcessor;
/* 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 HttpRequestLine requestLine = new HttpRequestLine();
private HttpResponse response;
protected String method = null;
protected String queryString = null;
/**
* The string manager for this package.
*/
protected StringManager sm =
StringManager.getManager("ex03.pyrmont.connector.http");
public void process(Socket socket) {
SocketInputStream input = null;
OutputStream output = null;
try {
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();
}
}
/**
* This method is the simplified version of the similar method in
* org.apache.catalina.connector.http.HttpProcessor.
* However, this method only parses some "easy" headers, such as
* "cookie", "content-length", and "content-type", and ignore other headers.
* @param input The input stream connected to our socket
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a parsing error occurs
*/
private void parseHeaders(SocketInputStream input)
throws IOException, ServletException {
while (true) {
HttpHeader header = new HttpHeader();;
// Read the next header
input.readHeader(header);
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException
(sm.getString("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")) {
Cookie cookies[] = RequestUtil.parseCookieHeader(value);
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("jsessionid")) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
request.setRequestedSessionId(cookies[i].getValue());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
}
}
request.addCookie(cookies[i]);
}
}
else if (name.equals("content-length")) {
int n = -1;
try {
n = Integer.parseInt(value);
}
catch (Exception e) {
throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
}
request.setContentLength(n);
}
else if (name.equals("content-type")) {
request.setContentType(value);
}
} //end while
}
private void parseRequest(SocketInputStream input, OutputStream output)
throws IOException, ServletException {
// Parse the incoming request line
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);
}
// Checking for an absolute URI (with the HTTP protocol)
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
// Parsing out protocol and host name
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
}
else {
uri = uri.substring(pos);
}
}
}
// Parse any requested session ID out of the request URI
String match = ";jsessionid=";
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match.length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
}
else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL(true);
uri = uri.substring(0, semicolon) + rest;
}
else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
// Normalize URI (using String operations at the moment)
String normalizedUri = normalize(uri);
// Set the corresponding request properties
((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 "/", that represents
* the canonical version of the specified path after ".." and "." elements
* are resolved out. If the specified path attempts to go outside the
* boundaries of the current context (i.e. too many ".." path elements
* are present), return <code>null</code> instead.
*
* @param path Path to be normalized
*/
protected String normalize(String path) {
if (path == null)
return null;
// Create a place for the normalized path
String normalized = path;
// Normalize "/%7E" and "/%7e" at the beginning to "/~"
if (normalized.startsWith("/%7E") || normalized.startsWith("/%7e"))
normalized = "/~" + normalized.substring(4);
// Prevent encoding '%', '/', '.' and '\', which are special reserved
// characters
if ((normalized.indexOf("%25") >= 0)
|| (normalized.indexOf("%2F") >= 0)
|| (normalized.indexOf("%2E") >= 0)
|| (normalized.indexOf("%5C") >= 0)
|| (normalized.indexOf("%2f") >= 0)
|| (normalized.indexOf("%2e") >= 0)
|| (normalized.indexOf("%5c") >= 0)) {
return null;
}
if (normalized.equals("/."))
return "/";
// Normalize the slashes and add leading slash if necessary
if (normalized.indexOf('\\') >= 0)
normalized = normalized.replace('\\', '/');
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return (null); // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) +
normalized.substring(index + 3);
}
// Declare occurrences of "/..." (three or more dots) to be invalid
// (on some Windows platforms this walks the directory tree!!!)
if (normalized.indexOf("/...") >= 0)
return (null);
// Return the normalized path that we have completed
return (normalized);
}
}
- 对response响应做了增强,如对接三方回调,有时需要设置响应头等信息
(贴部分代码,完成代码请自行下载)
/**
* Returns a default status message for the specified HTTP status code.
*
* @param status The status code for which a message is desired
*/
protected String getStatusMessage(int status) {
switch (status) {
case SC_OK:
return ("OK");
case SC_ACCEPTED:
return ("Accepted");
case SC_BAD_GATEWAY:
return ("Bad Gateway");
case SC_BAD_REQUEST:
return ("Bad Request");
case SC_CONFLICT:
return ("Conflict");
case SC_CONTINUE:
return ("Continue");
case SC_CREATED:
return ("Created");
case SC_EXPECTATION_FAILED:
return ("Expectation Failed");
case SC_FORBIDDEN:
return ("Forbidden");
...
...
...
case SC_USE_PROXY:
return ("Use Proxy");
case 207: // WebDAV
return ("Multi-Status");
case 422: // WebDAV
return ("Unprocessable Entity");
case 423: // WebDAV
return ("Locked");
case 507: // WebDAV
return ("Insufficient Storage");
default:
return ("HTTP Response Status " + status);
}
}
public OutputStream getStream() {
return this.output;
}
/**
* Send the HTTP response headers, if this has not already occurred.
*/
protected void sendHeaders() throws IOException {
if (isCommitted())
return;
// Prepare a suitable output writer
OutputStreamWriter osr = null;
try {
osr = new OutputStreamWriter(getStream(), getCharacterEncoding());
}
catch (UnsupportedEncodingException e) {
osr = new OutputStreamWriter(getStream());
}
final PrintWriter outputWriter = new PrintWriter(osr);
// Send the "Status:" header
outputWriter.print(this.getProtocol());
outputWriter.print(" ");
outputWriter.print(status);
if (message != null) {
outputWriter.print(" ");
outputWriter.print(message);
}
outputWriter.print("\r\n");
// Send the content-length and content-type headers (if any)
if (getContentType() != null) {
outputWriter.print("Content-Type: " + getContentType() + "\r\n");
}
if (getContentLength() >= 0) {
outputWriter.print("Content-Length: " + getContentLength() + "\r\n");
}
// Send all specified headers (if any)
synchronized (headers) {
Iterator names = headers.keySet().iterator();
while (names.hasNext()) {
String name = (String) names.next();
ArrayList values = (ArrayList) headers.get(name);
Iterator items = values.iterator();
while (items.hasNext()) {
String value = (String) items.next();
outputWriter.print(name);
outputWriter.print(": ");
outputWriter.print(value);
outputWriter.print("\r\n");
}
}
}
// Add the session ID cookie if necessary
/* HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
HttpSession session = hreq.getSession(false);
if ((session != null) && session.isNew() && (getContext() != null)
&& getContext().getCookies()) {
Cookie cookie = new Cookie("JSESSIONID", session.getId());
cookie.setMaxAge(-1);
String contextPath = null;
if (context != null)
contextPath = context.getPath();
if ((contextPath != null) && (contextPath.length() > 0))
cookie.setPath(contextPath);
else
cookie.setPath("/");
if (hreq.isSecure())
cookie.setSecure(true);
addCookie(cookie);
}
*/
// Send all specified cookies (if any)
synchronized (cookies) {
}
// Send a terminating blank line to mark the end of the headers
outputWriter.print("\r\n");
outputWriter.flush();
committed = true;
}
public void setRequest(HttpRequest request) {
this.request = request;
}
/* This method is used to serve a static page */
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
/* request.getUri has been replaced by request.getRequestURI */
File file = new File(Constants.WEB_ROOT, request.getRequestURI());
fis = new FileInputStream(file);
/*
HTTP Response = Status-Line
*(( general-header | response-header | entity-header ) CRLF)
CRLF
[ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
*/
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
catch (FileNotFoundException e) {
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
finally {
if (fis!=null)
fis.close();
}
}
public void write(int b) throws IOException {
if (bufferCount >= buffer.length)
flushBuffer();
buffer[bufferCount++] = (byte) b;
contentCount++;
}
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
// If the whole thing fits in the buffer, just put it there
if (len == 0)
return;
if (len <= (buffer.length - bufferCount)) {
System.arraycopy(b, off, buffer, bufferCount, len);
bufferCount += len;
contentCount += len;
return;
}
// Flush the buffer and start writing full-buffer-size chunks
flushBuffer();
int iterations = len / buffer.length;
int leftoverStart = iterations * buffer.length;
int leftoverLen = len - leftoverStart;
for (int i = 0; i < iterations; i++)
write(b, off + (i * buffer.length), buffer.length);
// Write the remainder (guaranteed to fit in the buffer)
if (leftoverLen > 0)
write(b, off + leftoverStart, leftoverLen);
}
/** implementation of HttpServletResponse */
public void addCookie(Cookie cookie) {
if (isCommitted())
return;
// if (included)
// return; // Ignore any call from an included servlet
synchronized (cookies) {
cookies.add(cookie);
}
}
public void addDateHeader(String name, long value) {
if (isCommitted())
return;
// if (included)
// return; // Ignore any call from an included servlet
addHeader(name, format.format(new Date(value)));
}
public void addHeader(String name, String value) {
if (isCommitted())
return;
// if (included)
// return; // Ignore any call from an included servlet
synchronized (headers) {
ArrayList values = (ArrayList) headers.get(name);
if (values == null) {
values = new ArrayList();
headers.put(name, values);
}
values.add(value);
}
}
public void addIntHeader(String name, int value) {
if (isCommitted())
return;
// if (included)
// return; // Ignore any call from an included servlet
addHeader(name, "" + value);
}
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;
}
public void setHeader(String name, String value) {
if (isCommitted())
return;
// if (included)
// return; // Ignore any call from an included servlet
ArrayList values = new ArrayList();
values.add(value);
synchronized (headers) {
headers.put(name, values);
}
String match = name.toLowerCase();
if (match.equals("content-length")) {
int contentLength = -1;
try {
contentLength = Integer.parseInt(value);
}
catch (NumberFormatException e) {
;
}
if (contentLength >= 0)
setContentLength(contentLength);
}
else if (match.equals("content-type")) {
setContentType(value);
}
}
public void setIntHeader(String name, int value) {
if (isCommitted())
return;
//if (included)
//return; // Ignore any call from an included servlet
setHeader(name, "" + value);
}
public void setLocale(Locale locale) {
if (isCommitted())
return;
//if (included)
//return; // Ignore any call from an included servlet
// super.setLocale(locale);
String language = locale.getLanguage();
if ((language != null) && (language.length() > 0)) {
String country = locale.getCountry();
StringBuffer value = new StringBuffer(language);
if ((country != null) && (country.length() > 0)) {
value.append('-');
value.append(country);
}
setHeader("Content-Language", value.toString());
}
}
- 增加了设计模式的使用(外观模式),隐藏了复杂的实现,方便调用
public class HttpRequestFacade implements HttpServletRequest {
private HttpServletRequest request;
public HttpRequestFacade(HttpRequest request) {
this.request = request;
}
/* implementation of the HttpServletRequest*/
public Object getAttribute(String name) {
return request.getAttribute(name);
}
public Enumeration getAttributeNames() {
return request.getAttributeNames();
}
这一章节的内容,结构的设计比上一节多了一些。也是很受益的,设计项目代码时可以参考。
注:catalina是tomcat作者喜欢的一个小岛的名字。