即便再简陋的服务器也是服务器,今天就来循着书本的第二章来看看如何实现一个servlet容器。

背景知识

  既然说到servlet容器这个名词,我们首先要了解它到底是什么。

servlet

  相比你或多或少有所了解。servlet是用java编写的服务器端程序,主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

容器

  容器的概念很大,在这里可以理解为能够管理对象(servlet)的生命周期,对象与对象之间的依赖关系。

  基于对以上两个概念的解释,那么对于serelvet容器的概念也就不再那么陌生了。

servlet容器

  就是创建、管理servlet规范中相关对象、生命周期的应用程序。

Servlet接口

  servlet是一种编程规范,要实现servlet编程需要用到javax.servlet和javax.servlet.http。所有的servlet程序都需要实现或继承自实现了javax.servlet.servlet接口。

Servlet接口的方法

  • init():servlet容器的初始化方法,该方法只会被调用一次;
  • service():不同于init只会触发一次,service在客户端请求后就会被调用。同时需要传入参数servletRequest和servletResponse。从字面意思就能知道,servletRequest携带了客户端发送的HTTP请求的信息,而servletResponse则用于封装servlet的响应信息。
  • destroy():当servlet实例调用完毕要被移除时,destroy方法将被调用。
  • getServletConfig():该方法用于取得<servlet> <init-param>配置的参数
  • getServletInfo():该方法提供有关servlet的信息,如作者、版本、版权。

servlet容器的职责

  • 第一次调用servlet时,需要载入serlvet类并调用init方法;
  • 针对客户端的request请求,创建一个servletRequest对象和一个servletResponse对象;
  • 传参servletRequest和servletResponse,调用service方法;
  • 当关闭servlet类时,调用destroy方法。

简陋的servlet容器

  之所以说是简陋的servlet容器,因为这里并没有实现servlet所有的方法,该容器只能支持很简单的servlet,也没有init方法和destroy方法。主要实现功能如下:

  • 等待HTTP请求;
  • 创建serlvetRequest和servletResponse对象;
  • 能够分别处理静态资源和servlet,当客户端请求静态资源时,则调用StaticResourceProcessor对象的process方法;当请求为serlvet则载入请求的servlet类并调用service方法。

主要包括6个类

  • HttpServer1:程序的入口,负责创建Request和Response对象,并根据HTTP请求类型将其转给相应的处理器处理;
  • Request:用于封装客户端HTTP请求信息;
  • Response:用于封装服务器响应信息;
  • StaticResourceProcessor:静态资源处理器;
  • ServletProcessor1:servlet处理器;
  • Constants:用于定义一些常量,如WEB_ROOT

HttpServer1

package day0522;

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 {
StaticResourceProcessor 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);
}
}
}
}

  

从代码可以看出,该类主要内容与上篇的HttpServer类似,不同点有:

  • await会一直等待HTTP请求,如果等到请求,该方法会根据请求类型分发给对应的处理器来处理;
  • 支持静态资源的请求,可以通过类似http://localhost:8080/index.html这样的请求来访问
  • index.html页面;
  • 支持servlet的请求和解析,可以通过类似http://localhost:8080/PrimitiveServlet来访问PrimitiveServlet

Request与上篇介绍的Request无异,不再介绍,但是需要说明一点,这里的Request实现了ServletRequest接口。

package day0522;

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 the 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 {
} @Override
public int getRemotePort() {
// TODO Auto-generated method stub
return 0;
} @Override
public String getLocalName() {
// TODO Auto-generated method stub
return null;
} @Override
public String getLocalAddr() {
// TODO Auto-generated method stub
return null;
} @Override
public int getLocalPort() {
// TODO Auto-generated method stub
return 0;
} }

  

Response

  同理,这里的Response也不在赘述。

package day0522;

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 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
*/
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) {
} @Override
public String getContentType() {
// TODO Auto-generated method stub
return null;
} @Override
public void setCharacterEncoding(String charset) {
// TODO Auto-generated method stub }
}

  

  这里的getWriter方法中新建了PrintWriter,其中第二个参数是一个boolean类型,表示是否启动autoFlush。

StaticResourceProcessor

package day0522;

import java.io.IOException;

public class StaticResourceProcessor {

  public void process(Request request, Response response) {
try {
response.sendStaticResource();
}
catch (IOException e) {
e.printStackTrace();
}
}
}

  

看代码可以看出:

  该类相较上篇是新建的类,主要实现的方法有sendStaticResource,实际上这个方法在上篇中也有,只是直接放在Response中出现,并在HttpServer中声明调用,而这里是将两种请求类型分别封装成类。

ServletProcessor

package day0522;

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 class.
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());
} }
}

  

从代码看出:

  • 该类只有一个方法process,接收Request和Response两个参数;
  • 通过uri.substring来获取请求的servlet名;
  • 通过新建一个类加载器来装载请求的servlet类,用的类加载器为java.net.URLClassLoader;
  • 有了类加载器后,通过loadClass方法载入serlvet类;
  • 创建一个载入类的实例,并调用其service方法。

至此,我们明白了:

  • servlet容器会等待http请求;
  • request负责封装http请求信息;
  • response负责封装相应信息;
  • staticResourceProcessor负责静态资源请求处理;
  • servletProcessor负责servlet的请求处理;
  • 一个简易的servlet容器的运作原理。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。



友情赞助

如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。

    1. 支付宝                          2. 微信

                      

探秘Tomcat——一个简易的Servlet容器的更多相关文章

  1. 一个简单的servlet容器

    [0]README 0.1)本文部分文字转自 “深入剖析Tomcat”,旨在学习  一个简单的servlet容器  的基础知识: 0.2)for complete source code, pleas ...

  2. how tomcat works 读书笔记(二)----------一个简单的servlet容器

    app1 (建议读者在看本章之前,先看how tomcat works 读书笔记(一)----------一个简单的web服务器 http://blog.csdn.net/dlf123321/arti ...

  3. Tomcat学习笔记(二)—— 一个简单的Servlet容器

    1.简介:Servlet编程是通过javax.Servlet和javax.servlet.http这两个包的类和接口实现的,其中javax.servlet.Servlet接口至关重要,所有的Servl ...

  4. 一个简单的Servlet容器实现

    上篇写了一个简单的Java web服务器实现,只能处理一些静态资源的请求,本篇文章实现的Servlet容器基于前面的服务器做了个小改造,增加了Servlet请求的处理. 程序执行步骤 创建一个Serv ...

  5. DI 原理解析 并实现一个简易版 DI 容器

    本文基于自身理解进行输出,目的在于交流学习,如有不对,还望各位看官指出. DI DI-Dependency Injection,即"依赖注入":对象之间依赖关系由容器在运行期决定, ...

  6. 攻城狮在路上(肆)How tomcat works(二) 一个简单的servlet容器

    该节在上一节的基础上增加了所谓对静态资源和动态资源访问的不同控制流程.示例里面采用的是对路径“/servlet/”进行了特殊处理. 一. 主要还是从HttpServer1中的main方法开始,先解析出 ...

  7. 2.自己搭建的一个简易的ioc容器

    1.persondao类namespace MyselfIoC{    public class PersonDao    {        public override string ToStri ...

  8. Tomcat剖析(二):一个简单的Servlet服务器

    Tomcat剖析(二):一个简单的Servlet服务器 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三) ...

  9. SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)

    Spring Boot默认使用Tomcat作为嵌入式的Servlet容器,只要引入了spring-boot-start-web依赖,则默认是用Tomcat作为Servlet容器: <depend ...

随机推荐

  1. Metaio在Unity3D中报错 Start: failed to load tracking configuration: TrackingConfigGenerated.xml 解决方法

    报错:Start: failed to load tracking configuration: TrackingConfigGenerated.xml Start: failed to load t ...

  2. php面试 1013总结

    面试题1:http://www.docin.com/p-288430879.html 数据库优化: session和cookies区别 缓存系统有哪些 myisam和InDB读写区别 varchar和 ...

  3. android——fargment基础

    1.Fragment的产生与介绍 Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视.针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应 ...

  4. Expert 诊断优化系列------------------内存不够用么?

    现在很多用户被数据库的慢的问题所困扰,又苦于花钱请一个专业的DBA成本太高.软件维护人员对数据库的了解又不是那么深入,所以导致问题迟迟不能解决,或只能暂时解决不能得到根治.开发人员解决数据问题基本又是 ...

  5. 排列组合算法的javascript实现

    命题:从成员数为N的集合S中,选出M个元素,分别求其排列与组合结果集,即 A(N, M)与C(N, M) js解法: function queue(arr, size){ if(size > a ...

  6. JavaScript的前世今生

    和CSS一样,JavaScript在各浏览器下并非完全一致,它所带来的兼容性问题时常困扰着我们,以至于现在“能否处理流行浏览器的兼容性问题”成为了检验一个程序员是否合格的标准之一.了解JavaScri ...

  7. 深圳本土web前端经验交流

    群号:125776555  深圳本土web前端技术交流群 baidu tencent前端拒绝垃圾广告.吹水,欢迎讨论技术.跳槽经验期待您的加入 

  8. C语言 · 回文数 · 基础练习

    问题描述 1221是一个非常特殊的数,它从左边读和从右边读是一样的,编程求所有这样的四位十进制数. 输出格式 按从小到大的顺序输出满足条件的四位十进制数.   代码如下: 注意:这里要提醒一下读者:蓝 ...

  9. TDR测试原理

    什么是TDR? TDR是英文Time Domain Reflectometry 的缩写,中文名叫时域反射计,是测量传输线特性阻抗的主要工具.TDR主要由三部分构成:快沿信号发生器,采样示波器和探头系统 ...

  10. php单条件查询,关键字查询

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...