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

TOMCAT内核之旅--一个简单的Servlet容器--学习心得(二)

2017-10-26 10:06 621 查看

前言

上一节我们实现了一个最最最简单的WEB服务器,他的功能只能处理静态资源,而学过javaWEB的同学都知道,我在使用TOMCAT服务器,最基础的功能是能够请求静态页面,但是,我们最主要的是请求servlet,完成后台工作;


一、Servlet容器是如何工作的

servlet容器是一个复杂的系统。不过,一个servlet容器要为一个servlet的请求提供服务,基本上有三件事要做:


创建一个request对象并填充那些有可能被所引用的servlet使用的信息,如参数、头部、cookies、查询字符串、URI等等。一个request对象是javax.servlet.ServletRequest或javax.servlet.http.ServletRequest接口的一个实例。

创建一个response对象,所引用的servlet使用它来给客户端发送响应。response对象是javax.servlet.ServletResponse或javax.servlet.http.ServletResponse接口的一个实例。

调用servlet的service方法,并传入request和response对象。在这里servlet会从request对象取值,给response写值。

本章将通过两个程序来说明你如何开发自己的servlet容器。第一个程序被设计得足够简单使得你能理解一个servlet容器是如何工作的。然后我们再对第一个进行优化。


二、javax.servlet.Servlet接口

为了理解应用程序是如何工作的,你需要熟悉javax.servlet.Servlet接口。在这之后,你将会学习一个servlet容器做了什么工作来为一个servlet提供HTTP请求。
Servlet编程是通过javax.servlet和javax.servlet.http这两个包的类和接口来实现的。其中一个至关重要的就是javax.servlet.Servlet接口了。所有的servlet必须实现实现或者继承实现该接口的类。
Servlet接口有五个方法,其用法如下:


public void init(ServletConfig config) throws ServletException

public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException

public void destroy()

public ServletConfig getServletConfig()

public java.lang.String getServletInfo()

在Servlet的五个方法中,init,service和destroy是servlet的生命周期方法。在servlet类已经初始化之后,init方法将会被servlet容器所调用。servlet容器只调用一次,以此表
4000
明servlet已经被加载进服务中。init方法必须在servlet可以接受任何请求之前成功运行完毕。一个servlet程序员可以通过覆盖这个方法来写那些仅仅只要运行一次的初始化代码,例如加载数据库驱动,值初始化等等。在其他情况下,这个方法通常是留空的。
servlet容器为servlet请求调用它service方法。servlet容器传递一个javax.servlet.ServletRequest和javax.servlet.ServletResponse对象。ServletRequest对象包括客户端的HTTP请求信息,而ServletResponse对象封装servlet的响应。在servlet的生命周期中,service方法将会给调用多次。
这里你可以自己在Tomcat服务器的基础上,自行实现一个servlet,证实我们所讲的东西,这里就证实了!


三、一个简单的Servlet容器

现在,让我们从一个servlet容器的角度来研究一下servlet编程。总的来说,一个全功能的servlet容器会为servlet的每个HTTP请求做下面一些工作:


当第一次调用servlet的时候,加载该servlet类并调用servlet的init方法(仅仅一次)。

对每次请求,构造一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。

调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。

当servlet类被关闭的时候,调用servlet的destroy方法并卸载servlet类。

在这里,我们必须强调一下,这个最最最简单的servlet容器不是全功能的。因此,她不能运行什么除了非常简单的servlet,而且也不调用servlet的init方法和destroy方法。相反它做了下面的事情:

等待HTTP请求。

构造一个ServletRequest对象和一个ServletResponse对象。

假如该请求需要一个静态资源的话,调用StaticResourceProcessor实例的process方法,同时传递ServletRequest和ServletResponse对象。

假如该请求需要一个servlet的话,加载servlet类并调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。

注意:在这个servlet容器中,每一次servlet被请求的时候,servlet类都会被加载。

我们对这个容器的功能进行了分析,接下来就是编程了。这里附上源代码。

1.HttpServer类,等待浏览器的请求,产生对应的响应;

package com.liu.tomcat.simpleServlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpServer {
/** WEB_ROOT is the directory where our HTML and other files reside.
*  For this package, WEB_ROOT is the "webroot" directory under the working
*  directory.
*  The working directory is the location in the file system
*  from where the java command was invoked.
*/
// shutdown command
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

// the shutdown command received
private boolean shutdown = false;

public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}

private void await() {
ServerSocket serverSocket = null;
int port = 8088;
try {
serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}

// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();

// create Request object and parse
Request request = new Request(input);
request.parse();

// create Response object
Response response = new Response(output);
response.setRequest(request);

// check if this is a request for a servlet or a static resource
// a request for a servlet begins with "/servlet/"
if(request.getUri().startsWith("/servlet/")) {
ServletProcessor servletProcessor = new ServletProcessor();
servletProcessor.process(request, response);
} else {
StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor();
staticResourceProcessor.process(request, response);
}

// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}


与第一节不同的是,我们对请求进行了判断,其中的代码为:

if(request.getUri().startsWith("/servlet/")) {
ServletProcessor servletProcessor = new ServletProcessor();
servletProcessor.process(request, response);
} else {
StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor();
staticResourceProcessor.process(request, response);
}


2.Request类

Request类代表一个request对象并被传递给servlet的service方法。就本身而言,它必须实现javax.servlet.ServletRequest接口。这个类必须提供这个接口所有方法的实现。不过,我们想要让它非常简单并且仅仅提供实现其中一些方法,我们在以下各章中再实现全部的方法。要编译Request类,你需要把这些方法的实现留空。


package com.liu.tomcat.simpleServlet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;

public class Request implements ServletRequest{
private InputStream inputStream;
private String uri;

public Request(InputStream input) {
this.inputStream = input;
}

public String getUri() {
return uri;
}

private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}

public void parse() {
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = inputStream.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
System.out.print("requese:\n" + request.toString());
uri = parseUri(request.toString());
System.out.println("uri:" + uri);
}

@Override
public Object getAttribute(String arg0) {
return null;
}

@Override
public Enumeration<?> getAttributeNames() {
return null;
}

@Override
public String getCharacterEncoding() {
return null;
}

@Override
public int getContentLength() {
return 0;
}

@Override
public String getContentType() {
return null;
}

@Override
public ServletInputStream getInputStream() throws IOException {
return null;
}

@Override
public Locale getLocale() {
return null;
}

@Override
public Enumeration<?> getLocales() {
return null;
}

@Override
public String getParameter(String arg0) {
return null;
}

@Override
public Map<?, ?> getParameterMap() {
return null;
}

@Override
public Enumeration<?> getParameterNames() {
return null;
}

@Override
public String[] getParameterValues(String arg0) {
return null;
}

@Override
public String getProtocol() {
return null;
}

@Override
public BufferedReader getReader() throws IOException {
return null;
}

@Override
public String getRealPath(String arg0) {
return null;
}

@Override
public String getRemoteAddr() {
return null;
}

@Override
public String getRemoteHost() {
return null;
}

@Override
public RequestDispatcher getRequestDispatcher(String arg0) {
return null;
}

@Override
public String getScheme() {
return null;
}

@Override
public String getServerName() {
return null;
}

@Override
public int getServerPort() {
return 0;
}

@Override
public boolean isSecure() {
return false;
}

@Override
public void removeAttribute(String arg0) {
}

@Override
public void setAttribute(String arg0, Object arg1) {
}

@Override
public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException {
}

}


3.esponse类

Response类,实现了javax.servlet.ServletResponse。就本身而言,这个类必须提供接口里边的所有方法的实现。类似于Request类,我们把除了getWriter之外的所有方法的实现留空。
然而在这个类中,只处理和我们第一节一样的功能,仅仅处理静态资源。


package com.liu.tomcat.simpleServlet;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Locale;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;

public class Response implements ServletResponse {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream outputStream;
PrintWriter writer;

public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}

public void setRequest(Request 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.getUri());
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
*/
String successMessage = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"\r\n";
outputStream.write(successMessage.getBytes());
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
outputStream.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>";
outputStream.write(errorMessage.getBytes());
}
finally {
if (fis!=null)
fis.close();
}
}

@Override
public void flushBuffer() throws IOException {
// TODO Auto-generated method stub

}

@Override
public int getBufferSize() {
// TODO Auto-generated method stub
return 0;
}

@Override
public String getCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}

@Override
public Locale getLocale() {
// TODO Auto-generated method stub
return null;
}

@Override
public ServletOutputStream getOutputStream() throws IOException {
// TODO Auto-generated method stub
return null;
}

@Override
public PrintWriter getWriter() throws IOException {
// autoflush is true, println() will flush,
// but print() will not.
writer = new PrintWriter(outputStream, true);
return writer;
}

@Override
public boolean isCommitted() {
// TODO Auto-generated method stub
return false;
}

@Override
public void reset() {
// TODO Auto-generated method stub

}

@Override
public void resetBuffer() {
// TODO Auto-generated method stub

}

@Override
public void setBufferSize(int arg0) {
// TODO Auto-generated method stub

}

@Override
public void setContentLength(int arg0) {
// TODO Auto-generated method stub

}

@Override
public void setContentType(String arg0) {
// TODO Auto-generated method stub

}

@Override
public void setLocale(Locale arg0) {
// TODO Auto-generated method stub

}

}


ServletProcess类用于处理Servlet请求

package com.liu.tomcat.simpleServlet;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class ServletProcessor {
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
System.out.println(servletName);
URLClassLoader loader = null;

try {
// create a URLClassLoader
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
System.out.println("ServletProcessorx类下的classpath:" + classPath);
// the forming of repository is taken from the createClassLoader method in
// org.apache.catalina.startup.ClassLoaderFactory
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
System.out.println("repository:" + repository);

// the code for forming the URL is taken from the addRepository method in
// org.apache.catalina.loader.StandardClassLoader class.
urls[0] = new URL(null, repository, streamHandler);
//该类加载器用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。
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());
}

try {
Servlet servlet = (Servlet) myClass.newInstance();
try {
servlet.service((ServletRequest)request, (ServletResponse)response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}


这个类的功能就是根据uri,加载请求的servlet类,生成servelt的是实例,调用其service()方法。

其中有大量的类,我们并不熟悉,这里我们仅知道他们是用来加载类的,以后的小结中,我们会详细学习类加载器。

四、测试

1、测试请求静态资源



2、测试servlet

但是我在测试servlet时,出现了异常,没有找到对应路径下的类,但是我已经写好了,这个原因我目前还没有搞明白,还请大家集体献计;



五、程序改进

上述程序有一个严重的问题。在ServletProcessor类的process方法,你向上转换Request实例为javax.servlet.ServletRequest,并作为第一个参数传递给servlet的service方法。你也向下转换Response实例为javax.servlet.ServletResponse,并作为第二个参数传递给servlet的service方法。


try {
servlet.service((ServletRequest)request, (ServletResponse)response);
} catch (ServletException e) {
e.printStackTrace();
}


这会危害安全性。知道这个servlet容器的内部运作的Servlet程序员可以分别把ServletRequest和ServletResponse实例向下转换为ex02.pyrmont.Request和ex02.pyrmont.Response,并调用他们的公共方法。拥有一个Request实例,它们就可以调用parse方法。拥有一个Response实例,就可以调用sendStaticResource方法。
有同学就说了,我们可以将Request和Response类中的方法设为private,虽然说是起到了保护的作用,但是在其他类中我们该需要调用这两个类的方法。其中有一个解决方法就是让Request和Response类拥有默认访问修饰,他们在自己的包外不可以被使用。
但是这有一个更好的解决方案:通过“第三者”,使其拥有私有的Request(Response类)实例,这个“第三者”实现了ServletRequest接口(ServletResponse接口)并通过在构造方法中传递一个引用了ServletRequest(ServletResponse)对象的Request(Response)实例作为参数来实例化。ServletRequest(ServletResponse)接口中每个方法的实现都调用了Request(Response)对象的相应方法。然而ServletRequest(ServletResponse)对象本身是私有的,并不能在类的外部访问。我们只需把“第三者”的实例化对象传给service方法即可!


至此,又是本节的小结了:我们讨论了两个简单的可以用来提供静态资源和处理像***Servlet这么简单的servlet的servlet容器。同样也提供了关于javax.servlet.Servlet接口和相关类型的背景信息。

同样,有问题留言哈哈哈。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: