how tomcat works[2]:一个简单的servlet容器

一个简单的servlet容器

1.Servlet接口。
public void destroy();
public ServletConfig getServletConfig();
public String getServletInfo();
public void init(ServletConfig arg0) throws ServletException;
public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException;

​ 该接口中共有五个方法。与servlet的生命周期有关的有三个:init() , service(), destroy();

​ 在初始化的时候servlet容器会调用init()方法。

​ 当servlet的一个客户端请求到达后,servlet容器就调用相应servlet的service()方法。service()方法可以被调用多次。service()方法有两个参数。第一个参数ServletRequest:包含客户端的HTTP请求和信息。第二个参数ServletResponse:封装servlet的响应。

​ 再将servlet实例从服务器中移除前,会调用destroy()方法。只有当service()方法中的所有线程都退出或执行超时后,才会调用该方法。该方法调用后,将无法再调用service()方法。调用该方法的目的是让servlet实例有机会去释放自身所占的资源。

2.简单的servlet容器

2.1一个功能齐全的servlet容器要做以下几件事:

​ 1.当第一次调用某个servlet时,要载入该servlet类,并调用其init()方法(仅此一次);

​ 2.针对每次request请求,创建一个ServletRequest对象和一个ServletResponse对象。

​ 3.调用servlet的service()方法时,将ServletRequest对象和ServletResponse对象最为参数传入;

​ 4.当要关闭该servlet时,调用其destory()方法,并卸载该servlet类。

2.2本次要编写的servlet容器结构如下图所示:
在这里插入图片描述

​ 该程序的入口在类HttpServer。创建HttpServer实例后,调用其await()方法,监听客户端的HTTP请求。为接收到的每个请求创建一个Request和Response对象,然后根据该HTTP请求的是静态资源还是servlet,将该HTTP请求分发给StaticResourceProcessor实例或者ServletProcessor实例。

2.3 HttpServer类:

​ HttpServer类是程序的入口。该类定义了两个常量。第一个是静态资源在本地存放的路径;第二个是当程序监听到SHOTDOWN命令时,退出监听。

​ main()方法中,创建该类的实例去调用该类的await()方法,await()方法建立侦听,然后进入死循环监听客户端的连接,一直到接收到SHOTDOWN命令后退出循环。

​ 在循环中,监听到有客户端连接时,建立通信信道,调用Request类中的parse()方法解析客户端发来的HTTP请求.得到请求的资源定位符URI。实例化Request和Response对象。判断该请求是静态资源请求还是servlet请求。如果是静态资源请求,将分发给StaticResourceProcessor类处理,如果是servlet请求,发给ServletProcessor处理

package com.yc.test;

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

/**
 * HttpServer类是程序的入口。
 * main()方法中,创建该类的实例去调用该类的await()方法,
 * await()方法建立侦听,然后进入死循环监听客户端的连接,
 * 一直到接收到SHOTDOWN命令后退出循环。
 * 在循环中,监听到有客户端连接时,建立通信信道,
 * 调用Request类中的parse()方法解析客户端发来的HTTP请求.得到请求的资源定位符URI。
 * 实例化Request和Response对象。判断该请求是静态资源请求还是servlet请求。
 * 如果是静态资源请求,将分发给StaticResourceProcessor类处理,
 * 如果是servlet请求,发给ServletProcessor处理
 * 
 * @author yc
 *
 */
public class HttpServer {
    
    private boolean shutdown = false;
    
    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        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);
        }
        
        /** 循环等待请求的到来 */
        while (!shutdown) {
            Socket socket = null;
            InputStream input = null;
            OutputStream output = null;
            
            try {
                socket = serverSocket.accept();
                input = socket.getInputStream();
                output = socket.getOutputStream();
                
                /** 创建请求对象然后解析 */
                Request request = new Request(input);
                request.parse();
                
                /** 创建响应对象 */
                Response response = new Response(output);
                response.setRequest(request);
                
                /** 判断请求并分发 */
                if (request.getUri().startsWith("/servlet/")) {
					ServletProcessor processor = new ServletProcessor();
					processor.process(request, response);
				} else {
					StaticResourceProcessor processor = new StaticResourceProcessor();
					processor.process(request, response);
				}
                
                /** 关闭socket */
                socket.close();
                /** 判断是否有SHUTDOWN 指令 */
                shutdown = request.getUri().equals(Constants.SHUTDOWN_COMMAND);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
        
    }
    
}

2.4 Request类

​ 该类表示被传递给service()方法的request对象。它必须实现ServletRquest接口的方法(本次简单实现,所有方法留空,后期解决)。

​ Request类中还有两个主要方法,parse()方法用来从请求头中读出字符集,parseUri()方法是从请求头中提取出资源定位符URI的值。该类就是解析客户端发过来的HTTP请求。

package com.yc.test;

/**
 * 该类表示被传递给service()方法的request对象。
 * 它必须实现ServletRquest接口的方法(本次简单实现,所有方法留空,后期解决)。
 * Request类中还有两个主要方法,parse()方法用来从请求头中读出字符集,
 * parseUri()方法是从请求头中提取出资源定位符URI的值。
 * 该类就是解析客户端发过来的HTTP请求。
 * 
 * @author yc
 *
 */
public class Request implements ServletRequest{
    private InputStream input;
    private String uri;
    
    public Request(InputStream input) {
        this.input = input;
    }
    
    /**
     * 从Socket 中读取字符集合
     */
    void parse() {
        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());
    }
    
    /**
     * 根据http 请求头的格式,读取出请求的uri
     * @param requestString
     * @return
     */
    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;
    }
    
    String getUri() {
        return uri;
    }

	

2.5 Response类

​ 该类表示被传递给service()方法的response对象。它必须实现ServletResponse接口的方法(本地简单实现,除了getWriter()方法外,其余全部留空)。

​ 在getWriter()方法中,PrintWriter类的构造函数的第二个参数是一个Boolean类型,表示是否启用autoFlush。对第二个参数传入true表示println()方法的任何调用都会刷新输出,但是调用print()方法不会刷新输出。因此在servlet的service()方法的最后一行调用print()方法,则该输出内容不会被发送给浏览器。

​ Response类还有一个主要的方法sendStaticResource()。该方法会拿到Request类解析出的URI,去本地(服务器)查询,客户端所请求的文件,将文件分割按字节码的形式通过信道发送给客户端

package com.yc.test;

/**
 * 该类表示被传递给service()方法的response对象。
 * 它必须实现ServletResponse接口的方法
 * (本次简单实现,除了getWriter()方法外,其余全部留空)。
 * getWriter()方法中,PrintWriter类的构造函数
 * 的第二个参数是一个Boolean类型,表示是否启用autoFlush。
 * 对第二个参数传入true表示println()方法的任何调用都会刷新输出,
 * 但是调用print()方法不会刷新输出。
 * 因此在servlet的service()方法的最后一行调用print()方法,
 * 则该输出内容不会被发送给浏览器。
 * 
 * Response类还有一个主要的方法sendStaticResource()。
 * 该方法会拿到Request类解析出的URI,去本地(服务器)查询,客户端所请求的文件,
 * 将文件分割按字节码的形式通过信道发送给客户端
 * @author yc
 *
 */
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;
    }
    
    /**
     * 查询 客户端所请求的文件,将文件分割按字节码的形式通过信道发送给客户端
     * @throws IOException
     */
    void sendStaticResource() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;
        
        try {
            File file = new File(Constants.WEB_ROOT, request.getUri());
            if (file.exists()) {
                fis = new FileInputStream(file);
                
                int ch = fis.read(bytes, 0, BUFFER_SIZE);
                while (ch != -1) {
                    output.write(bytes, 0, ch);
                    ch = fis.read(bytes, 0, BUFFER_SIZE);
                }
            } else {
                /** 当打开文件发生错误时404 */
            	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());
            }
            
        } catch (Exception e) {
            System.out.println(e.toString());
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }

	@Override
	public PrintWriter getWriter() throws IOException {
		writer = new PrintWriter(output, true);
		return writer;
	}

	

2.6 StaticResourceProcessor

​ 该类用于处理对静态资源的请求。只有一个方法process().该方法会调用response的sendStaticResource()方法;

package com.yc.test;

/**
 * 该类用于处理对静态资源的请求。
 * 只有一个方法process().该方法会调用response的
 * sendStaticResource()方法;
 * @author yc
 *
 */
public class StaticResourceProcessor {

	/***
	 * 处理静态资源的请求
	 * @param request
	 * @param response
	 */
	public void process(Request request, Response response) {
		try {
			response.sendStaticResource();	
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

}

2.7 ServletProcessor

​ 该类只有一个方法:process()。接受两个参数ServletRequest实例和ServletResponse实例。该方法从 ServletRequest对象中获取URI。

​ URI格式 : /servlet/servletName。servletName是请求的servlet资源的类名。

​ 从URI中获取类名,然后创建一个类加载器,到指定路径去加载servlet类。然后创建该servlet类的实例并调用service()方法。

​ 在这里我碰到了一个问题。就是自己创建的类加载器加载时,一直显示找不到Class。最终的解决方案是在servletName前加上包名。
package com.yc.test;

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

import javax.servlet.Servlet;

/**

  • 该类只有一个方法:process()。
  • 接受两个参数ServletRequest实例和ServletResponse实例。
  • 该方法从 ServletRequest对象中获取URI。
  • URI格式 : /servlet/servletName。
  • servletName是请求的servlet资源的类名。
    *​ 从URI中获取类名,然后创建一个类加载器,到指定路径去加载servlet类。
  • 然后创建该servlet类的实例并调用service()方法。
  • @author yc

*/
public class ServletProcessor {

public void process(Request request, Response response) {
	/** 从ServletRequest中提取出URI */
	String uri = request.getUri();
	
	/** 从URI中获取servlet类名 */
	String servletName = uri.substring(uri.lastIndexOf("/") + 1);
	URLClassLoader loader = null;
	try {
		/** 创建一个URL类加载器 */
		URL[] urls = new URL[1];
		URLStreamHandler streamHandler = null;
		File classPath = new File(Constants.WEB_ROOT);
		/** 生成仓库。即类加载器加载类的路径*/
		System.out.println(classPath.getAbsolutePath());
		String respository = (new URL("file", null, classPath.getAbsolutePath() + File.separator)).toString();
		urls[0] = new URL(null, respository, streamHandler);
		loader = new URLClassLoader(urls);
	} catch (Exception e) {
		System.out.println(e.toString());
	}
	
	Class<?> myclass = null;
	
	try {
		/** 通过loadClass()方法载入servlet类 */
		myclass = loader.loadClass("com.yc.test." + servletName);
	} catch (ClassNotFoundException e) {
		System.out.println(e.toString());
	}
	
	Servlet servlet = null;
	
	try {
		/** 创建已载入的servlet类的实例,将其向下转型为servlet,并调用其service()方法。 */
		servlet = (Servlet) myclass.newInstance();
		servlet.service(request, response);
	} catch (Exception e) {
		System.out.println(e.toString());
	}
}

}

2.8 Contains
配置常量

package com.yc.test;

import java.io.File;

/**
 * 常量类
 * @author yc
 *
 */
public class Constants {
	/** WEB_ROOT 这里定义的目录是用来存放一些静态的html 文件或者txt 文件或者jpg 等静态文件*/
    public static final String WEB_ROOT = 
    	System.getProperty("user.dir") + File.separator + "webroot";
    
    /** 关闭命令 */
    public static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
}

3.修改简单的servlet容器

​ 实现的程序中有个问题,在ServletProcessor类的process()方法中,需要将request, response转型为其接口类型,这种做法是不安全的。这样会被别有用心的人向下转型为request, response类型,就可以得到实例,调用方法。

​ 第一,我们可以将request, response这两个类中的方法设为默认访问权限。这样就不能从包外对其进行访问。

​ 第二,可以用门面模式。如下:

​ 添加两个门面类。门面类实现两个接口,并在其构造方法中将request, response对象进行向上转型。这样,就算有人向下转型,拿到的也只是门面类的实例,只能调用接口的实现方法,而不能对private的成员操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值