coder 爱翻译 How Tomcat Works 第二章 第一部分

第二章: A Simple Servlet Container

Servlet容器(container)可以处理简单的servlets和静态资源。你可以使用PrimitiveServlet来测试这个容器。
Listing 2.1: PrimitiveServlet.java 

import javax.servlet.*; 
import java.io.IOException; 
import java.io.PrintWriter; 
public class PrimitiveServlet implements Servlet { 
public void init(ServletConfig config) throws ServletException { 
System.out.println("init"); 
} 
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { 
System.out.println("from service"); 
PrintWriter out = response.getWriter(); 
out.println("Hello. Roses are red."); 
out.print("Violets are blue."); 
} 
public void destroy() { 
System.out.println("destroy"); 
} 
public String getServletInfo() { 
return null; 
} 
public ServletConfig getServletConfig() { 
return null;
 	} 
}  

为了知道application程序怎么工作的,你需要对javax.servlet.Servlet这个接口有更多的了解。这个接口将在后面讨论。之后,你将学习一个servlet容器为HTTP请求服务必须做些什么事。

The javax.servlet.Servlet Interface

Servlet编程:通过javax.servlet类和javax.servlet.http接口这两个包实现的。当然在这些类和接口中,javax.servlet.Servlet接口显得格外重要。所有的servlet必须(implements)实现这个接口或扩展(extends)这个类。

Servlet接口有5个方法,下面列出:

 public void init(ServletConfig config) throws ServletException
 public void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
 public void destroy()
 public ServletConfig getServletConfig()
 public java.lang.String getServletInfo()

Servlet的5个方法。Init、service和destroy方法是servlet的生命周期的方法。当servlet类实例化后,servlet容器就会调用init方法。servlet容器只准确地调用这个方法一次,表明这个servlet已经被放入服务中了。在servlet可以接收到任何之前,必须正确地完成init方法。一个servlet程序员可以重写(override)这个方法,写入只需要运行一次的初始化代码:载数据库驱动,初始化变量等等。在其他情况下,这个方法通常是空的方法。

servlet容器在当一个servlet有一个请求时就调用这个servlet的service方法。servlet容器会传递一个javax.servlet.ServletRequest对象和一个javax. servlet.ServletResponse对象。ServletRequest对象包含客户端的HTTP请求信息,而ServletResponse对象封装了servlet的响应。service方法在servlet的生命周期中会被调用很多次。

servlet容器从服务中移除一个servlet实例之前会调用destroy方法。当servlet容器关闭或者servlet容器需要释放掉一些内存时正常发生。这个方法只有在servlet的service方法里的所有的线程(exit)退出或者超时一段时间的情况下调用。当servlet容器调用了destroy方法后,它不会再调用该servlet的service方法。destroy方法给了servlet一个机会来清理所有的资源:内存、持有文件、线程等,以确保所有的持久化状态与servlet在内存的当前状态同步。

PrimitiveServlet是一个非常简单的servlet,它可以用来测试这章的servlet容器applications。PrimitiveServlet类实现了javax. servlet. Servlet并提供了Servlet的5种方法的实现。PrimitiveServlet是多么的简单。每次init,service或者destroy方法被调用,servlet把方法的名字打印到控制台上。此外,service方法获得了来自ServletResponse对象的java.io.PrintWtiter对象,这样就可以发送字符串到浏览器。

Application 1

现在让我们从servlet容器的角度来测试一下servlet程序。简而言之,一个功能齐全的servlet容器为每个对应的servlet的HTTP请求做了一下工作:

 当servlet是第一次调用的时候,加载servlet类和调用init方法(仅调用一次)。
 每次请求,构建一个javax. servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。
 调用servlet的service方法,传递ServletRequest和ServletReponse对象。
 当servlet类被关闭的时候,调用servlet的destroy方法和卸载servlet类。

这章的第一个servlet容器并不具备很全的功能。所以它仅仅只能运行非常简单的servlet。其功能如下:

 等待HTTP请求。
 构建ServletRequest对象和ServletResponse对象。
 如果请求的是静态资源,调用StaticResourceProcessor实例的process方法,传递ServletRequest对象和ServletResponse对象。
 如果请求的是一个servlet,就加载这个servlet类,调用它的service方法,传递ServletRequest对象和ServletResponse对象。

在这个servlet容器中,每次servlet被请求时该servlet就会被加载。
这个应用中包括下面6个类:

 HttpServer1
 Request
 Response
 StaticResourceProcessor
 ServletProcessor1
 Constants

这个应用程序的入口(静态main方法)是在HttpServer1这个类中。主方法创建一个HttpServer1的实例和调用它的await方法。await方法是用来等待HTTP请求,为每一个请求创建Request对象和Response对象。await方法还可以根据请求的是一个静态资源或者是一个servlet来把请求转发给StaticResourceProcessor实例或者ServletProcessor实例。

Constants类中包括了被其他类引用的static final WEB_ROOT。WEB_ROOT表明了PrimitiveServlet和静态资源所处可以被这个容器所能访问的路径目录。HttpServer1实例在它接收到shutdown命令在前一直处于等待HTTP请求的状态。


The HttpServer1 Class

请求一个servlet,你可以使用下面的URL:

http://machineName:port/servlet/servletClass

这样你可以使用浏览器调用本地的PrimitiveServlet:

http://localhost:8080/servlet/PrimitiveServlet

servlet容器可以装载PrimitiveServlet。但是,如果你再调用其他的servlet,在个servlet容器就会抛出一个异常。就是说这个版本的容器只能处理一个servlet。
Listing 2.2: The HttpServer1 Class's await method 
package ex02.pyrmont; 
import java.net.Socket;
import java.net.ServerSocket; 
import java.net.InetAddress; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 
public class HttpServer1 { 
/** 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) { 
HttpServer1 server = new HttpServer1(); 
server.await(); 
} 
public void await() { 
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); 
} 
// 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/")) { 
ServletProcessor1 processor = new ServletProcessor1();
 processor.process(request, response); 
} else { 
StaticResoureProcessor processor = new StaticResourceProcessor(); 
processor.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); 
} 
} 
} 
}

The Request Class

Request类代表了一个请求对象,它被用来传给servlet的service方法。它必须诗选javax.servlet.ServletRequest接口。这个类必须为这个接口的所有方法提供实现。但是,一般情况下,我们只想做一个非常简单的实现。为了能够编译Request类,只需要为这些方法提供空的实现。下面可以看见所有的返回都为null。
Listing 2.3: The Request class 
package ex02.pyrmont; 
import java.io.InputStream; 
import java.io.IOException; 
import java.io.BufferedReader; 
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 input;
private String uri; 
public Request(InputStream input){ 
this.input = 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 = input.read(buffer);
} catch (IOException e) {
 e.printStackTrace(); i = -1; 
}
 		for (int j=0; j<i; j++) {
 			request.append((char) buffer(j)); 
} 
System.out.print(request.toString()); 
uri = parseUri(request.toString()); 
}
 /* implementation of ServletRequest */ 
public Object getAttribute(String attribute) { 
return null; 
} 
public Enumeration getAttributeNames() { 
return null;
}
public String getRealPath(String path) { 
return null;
}
public RequestDispatcher getRequestDispatcher(String path) {
 		return null; 
} 
public boolean isSecure() { 
return false; 
} 
public String getCharacterEncoding() { 
return null; 
}
public int getContentLength() { 
return 0; 
} 
public String getContentType() { 
return null; 
} 
public ServletInputStream getInputStream() throws IOException { 
return null; 
} 
public Locale getLocale() {
 		return null;
 	}
public Enumeration getLocales() { 
return null; 
} 
public String getParameter(String name) {
 		return null; 
} 
public Map getParameterMap() { 
return null; 
} 
public Enumeration getParameterNames() { 
return null; 
} 
public String[] getParameterValues(String parameter) { 
return null;
 	} 
public String getProtocol() {
return null; 
} 
public BufferedReader getReader() throws IOException { 
return null; 
} 
public String getRemoteAddr() { 
return null; 
}
public String getRemoteHost() {
 		return null;
 	}
 	public String getScheme() {
 		return null; 
}
public String getServerName() { 
return null;
} 
public int getServerPort() { 
return 0; 
} 
public void removeAttribute(String attribute) {

}
public void setAttribute(String key, Object value) {

}
public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException{
}      
}

The Response Class

Response类实现javax.servlet.ServletReponse接口。跟Request类似,除了getWriter方法外,其他的实现方法都为空。
Listing 2.4: The Response class 
package ex02.pyrmont; 
import java.io.OutputStream; 
import java.io.IOException; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.File; import java.io.PrintWriter; 
import java.util.Locale; 
import javax.servlet.ServletResponse; 
import javax.servlet.ServletOutputStream; 
public class Response implements ServletResponse { 
private static final int BUFFER_SIZE = 1024; 
Request request; 
OutputStream output; 
PrintWriter writer; 
public Response(OutputStream output) { 
this.output = output; 
} 
public void setRequest(Request request) {
this.request = request; 
} 
/* This method is used to serve static pages */ 
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 */ 
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(); 
} 
} 
/**
 implementation of ServletResponse 
*/ 
public void flushBuffer() throws IOException (){} 
public int getBufferSize() { 
return 0; 
} 
public String getCharacterEncoding() { 
return null; 
}
public Locale getLocale() { 
return null; 
} 
public ServletOutputStream getOutputStream() throws IOException {
return null; 
} 
public PrintWriter getWriter() throws IOException { 
// autoflush is true, println() will flush,
// but print() will not. 
writer = new PrintWriter(output, true); 
return writer; 
} 
public boolean isCommitted() { 
return false; 
} 
public void reset() { }
public void resetBuffer() { } 
public void setBufferSize(int size) { } 
public void setContentLength(int length) { }
public void setContentType(String type) { } 
public void setLocale(Locale locale) { } 
}

在getWriter方法中PrintWriter类的构造函数的第二个参数是一个boolean类型,它表明是否自动刷新。如果是true,会自动刷新output。

此外,如果调用print方法恰巧发生在servlet的service方法的最后一行,output不会被发送到浏览器上。这个问题会在后面的应用被修正。


The StaticResourceProcessor Class

StaticResourceProcessor类是用来为请求静态资源提供服务的。它只有一个process方法。
Listing 2.5: The StaticResourceProcessor class 

package ex02.pyrmont; 
import java.io.IOException; 
public class StaticResourceProcessor { 
public void process(Request request, Response response) { 
try { 
response.sendStaticResource(); 
} catch (IOException e) {
e.printStackTrace(); 
}
}
}

process方法接收2个参数:Request实例和Response实例。这个方法简单调用Response对象的sendStaticResource方法。


The ServletProcessor1 Class

ServlerProcessor类是为servlet处理HTTP请求。
Listing 2.6: The ServletProcessor1 class 

package ex02.pyrmont; 
import java.net.URL; 
import java.net.URLClassLoader; 
import java.net.URLStreamHandler; 
import java.io.File; 
import java.io.IOException; 
import javax.servlet.Servlet; 
import javax.servlet.ServletRequest; 
import javax.servlet.ServletResponse; 
public class ServletProcessor1 { 
public void process(Request request, Response response) {
String uri = request.getUri(); 
String servletName = uri.substring(uri.lastIndexOf("/") + 1); 
URLClassLoader loader = null; 
try { 
// create a URLClassLoader 
URL[] urls = new URL[1]; 
URLStreamHandler streamHandler = null; 
File classPath = new File(Constants.WEB_ROOT); 
// 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() ; 
// the code for forming the URL is taken from 
// the addRepository method in 
// org.apache.catalina.loader.StandardClassLoader. 
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((ServletRequest) request, (ServletResponse) response);
 		} catch (Exception e) { 
System.out.println(e.toString());
} catch (Throwable e) { 
System.out.println(e.toString()); 
}
} 	
}

ServletProcessor1类出奇的简单,之包含了一个方法:process。这个方法接收2个参数:一个javax.servlet.ServletRequest的实例和javax.servlet.SetvletResponse的实例。ServletRequest方法中通过getRequestUri方法获取URI。

String uri = request.getUri();

记住URI是下面的格式:

/servlet/servletName

servletName是这个servlet类的名字。

为了加载这个servlet类,我们需要从URI中知道servlet的名字。我们可以使用下面的方法:

String servletName = uri.subString(uri.lastIndexOf("/")+1);
之后,process方法会加载这个servlet。为了达到加载servlet的目的,你需要创建一个类加载器(class loader),告诉这个类加载器这个想要被加载的类的路径。对于servlet容器。类加载器是直接搜索Constants.WEB_ROOT所指向的目录。这个目录是指向工作目录下的webroot目录。

为了加载servlet,你使用java.net.URLClassLoader类,它是java.lang.ClassLoader的间接子类。一旦你创建好了URLClassLoader的实例,你就可以用它的loadClass方法加载这个servlet类。初始化URLClassLoader是很明确的。这个类有3个构造函数,最简单的一个:

public URLClassLoader(URL[] urls);

urls是一个java.net.URL对象数组。每一个URL都是以一个/结束。确保指向一个目录。此外,这URL如果需要,可以引用一个JAR文件,这个JAR 文件会被下载下来并打开。

注意:在一个servlet容器中,一个类加载器可以找到servlet类的地方被称为仓库(repository)。

在我们的应用中,这里只有一个location是类加载器必须查看的。工作目录下的webroot目录。此外,我们通过创建一个单独URL的数组。这个URL类提供了构造函数的数量,这样就可以有很多种方式创建URL对象。在这个应用中,我们使用相同的构造函数:

public URL(URL context, java.lang.String spec, URLStreamHandler hander) throws MalformedURLException

你可以使用这个构造函数:通过传一个字符串说明给第二个参数,而第一个和第三个参数都为null。这里还有另外一个接收3个参数的构造函数:

public URL(java.lang.String protocol, java.lang.String host, java.lang.String file) throws MalformedURLException

如果你只是简单是使用下面的代码,编译器不知道你调用的是哪个构造函数:

new URL(null, aString, null);

你可以像下面使用:

URLStreamHandler streamHandler = null;
new URL(null, aString, streamHandler);

对第二个参数,你可以传一个包含仓库(repository)的字符串(一个servlet类可以被找到的目录)。向下面使用;

String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;

把所有的都结合起来,这里process方法创建URLClassLoader实例:
// create a URLClassLoader
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); 

有个类加载器,你可以使用loadClass方法加载一个servlet:

Class myClass = null;
try {
myClass = loader.loadClass(servletName);
} catch (ClassNotFoundException e) {
System.out.println(e.toString());
}

接下来,process方法创建一个被加载的servlet实例,向下转型为javax.servlet.Servlet,然后调用servlet的service方法:

Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
} catch (Exception e) {
System.out.println(e.toString());
} catch (Throwable e) {
System.out.println(e.toString());
}


编译后运行这个应用:
http://localhost:8080/index.html或http://localhost:8080/servlet/PrimitiveServlet

当调用PrimitiveServlet,你可以看到浏览器上面的内容:
Hello.Roses are red.

你不能看到第二个字符串:Violets are blue,因为只有第一个字符串被刷新到浏览器。在第三章,我们将修正这个问题。


你可能感兴趣的:(java,tomcat,应用服务器,socket,servlet)