码迷,mamicode.com
首页 > 其他好文 > 详细

原理分析——编写、实现Tomcat(一)

时间:2016-04-26 21:48:16      阅读:176      评论:0      收藏:0      [点我收藏+]

标签:

1.目的:

利用网络和线程的知识编写实现自己的Servlet容器,以Tomcat为模板

2.需求:

a)一个servlet容器,可以提供页面访问服务和servlet的服务.。

=> 根据uri请求的地址寻找文件并以流的方式输出

  =>  动态load servlet字节码,并运行它( 按生命周期)  

b)servlet容器它来控制servlet的生命周期

  c)Servlet类必须要实现一个接口  Servlet , 提供所有的Servlet都要有的方法(生命周期)

d)对于要处理的资源有两种:静态资源/动态资源.    定义一个接口,写两个实现.                                      

         动态资源:     http://localhost:8888/servlet/Hello

         GET  /servlet/hello HTTP/1.1

   静态资源:     http://localhost:8888/form.html

  GET /index.html HTTP/1.1

  =>   将这种处理定义成一个接口  Processor  (  process() )  ->   StaticProcessor

                                         ->   DynamicProcessor


3.运行流程:

a)Servlet运行流程

第一次访问:构造方法->  init()  ->  service()  ->   doGet()/doPost()

第二次访问:                    ->  service()  ->   doGet()/doPost()

b)容器运行流程:

1.等待http请求,接收请求,做一些解析  ->   uri  (静态资源/动态资源)

2. 解析http请求,构造成一个  HttpServletRequest对象, HttpServletResponse对象.

3. 判断请求的资源的类型静态的资源/动态的资源  ,静态的资源  ->   StaticProcessor

                                               动态资源    ->   DynamicProcessor,    必须要有  RequestResponse对象

4. 动态加载Servlet的字节码,并调用service()  ->  判断请求的方法,调用对应的  Servlet中的doGet()/doPost()


4.流程示意:

技术分享技术分享

6.所需接口和其实现类:

由以下的类和接口组成:

HttpServer   

ServerService

ServletRequest接口  ->   HttpServletRequest

ServletResponse接口  ->   HttpServletResponse

Processor接口 ( process( Request, Response) ) ->   静态资源   :   StaticProcessor

                    动态资源   :   DynamicProcessor

Servlet接口:  定义生命周期方法

 

TomcatConstants:  


7.源码(顺序根据程序运行流程)

HttpTomcat.java

package com.yhj.servlet.container;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpTomcat {
	
	private int port=8888;

	public static void main(String[] args) {
		HttpTomcat tomcat=new HttpTomcat();
		tomcat.startServer();
	}
	
	public void startServer(){
		ServerSocket ss=null;
		try {
			// 其实就是一个套接字的服务器
			ss=new ServerSocket(port);
			System.out.println("服务器开启");
		} catch (IOException e) {
			e.printStackTrace();
		}
		while(true){//为了满足多个网站的访问,这里设计为一个死循环,并且在每一次接收到socket之后,开启一个新的线程来处理这个请求
			Socket s;
			try {
				s = ss.accept();
				Thread t=new Thread(new ServerService(s));//把接收到的Socket传到ServerService中,它实现了Runnable接口
				t.start();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

ServerService.java

package com.yhj.servlet.container;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;

public class ServerService implements Runnable{

	private Socket s;    				
	private InputStream iis;    		
	private OutputStream oos;
	
	private HashMap<String ,String >Session=null;
	
	public ServerService(Socket s) {
		super();
		this.s = s;//接收外部的套接字,
	}

	@Override
	public void run() {
		try{
			this.iis=this.s.getInputStream();//取到此套接字的输入输出流,
			this.oos=this.s.getOutputStream();
		}catch(IOException e){
			e.printStackTrace();
			return;
		}
		try {
			//创建请求对象
			ServletRequest request=new HttpServletRequest(this.iis);//请求获取输入流iis
			request.parse();//调用request中的解析请求头的方法
			//创建响应对象
			ServletResponse response=new HttpServletResponse(request,this.oos);//响应获取输出流oos,并传入request(已经维护好)
			//定义一个处理器
			Processor processor=null;
			//工厂模式
			if(request.getUri()!=null&&request.getUri().startsWith("/servlet/")){
				processor=new DynamicProcessor();//判断uri是否以/servlet/开头,是的,则表明是动态的请求
			}else{
				processor=new StaticProcessor();//否则实例化静态资源
			}
			//调用处理器的处理方法
			//回调
			processor.process(response, request);
			//http协议是一个无状态的,基于请求响应
			this.s.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

ServletRequest.java

package com.yhj.servlet.container;

import java.util.HashMap;

public interface ServletRequest {

	/**
	 * 获取请求发来的参数
	 * @param key  键名
	 * @return   返回一个key对应的String字符串
	 */
	public String getParameter(String key);
	
	/**
	 * Http:  GET /hello?name=yhj HTTP/1.1
	 * *****************************************
	 * 		  POST /hello HTTP/1.1
	 * 
	 * 		  name=yhj
	 * ****************************************
	 * 
	 */
	public void parse();
	
	/**
	 * 获得uri
	 * @return
	 */
	public String getUri();
	
	/**
	 * 获取提交方式 Get   Post
	 * @return
	 */
	public String getMethod();
	
	
	
	public HashMap<String ,String> getSession();
	
	
}

HttpServletRequest.java

package com.yhj.servlet.container;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class HttpServletRequest implements ServletRequest {

	/**
	 * URL是Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。
  		URL的格式由下列三部分组成:
			协议(或称为服务方式);
			存有该资源的主机IP地址(有时也包括端口号);
			主机资源的具体地址。如目录和文件名等。
		URI是以某种统一的(标准化的)方式标识资源的简单字符串,
		一般由三部分组成:
			访问资源的命名机制。
			存放资源的主机名。
			资源自身的名称,由路径表示。
	 */
	private String uri;
	/**一个请求头示例
	 * POST /examples/default.jsp HTTP/1.1  
		Accept: text/plain; text/html  
		Accept-Language: en-gb  
		Connection: Keep-Alive  
		Host: localhost  
		User-Agent: Mozilla/4.0  
		Content-Length: 33  
		Content-Type: application/x-www-form-urlencoded  
		Accept-Encoding: gzip, deflate  
  
		lastName=Franks&firstName=Michael  
	 */
	private InputStream iis;
	
	private String method;
	
	private Map<String ,String>parameter=new Hashtable<String ,String>();//形成request.getParamter("key")的效果
	
	/**
	 * 构造方法,传入一个输入流
	 * @param uri
	 * @param iis
	 */
	public HttpServletRequest( InputStream iis) {
		super();
		this.iis = iis;
	}
	public HttpServletRequest() {
		super();
	}
	/**
	 * 解析协议:最重要的就是取出uri
	 */
	public void parse(){
		//从input中读出所有的内容(http请求协议=》protocal)
		String protocal=null;
		//TODO:从iis流中取出protocal
		StringBuffer sb=new StringBuffer(1024*10);//10k大小,一次性读取
		int length=-1;
		byte[] bs=new byte[1024*10];
		
		try {
			length=this.iis.read(bs);
		} catch (IOException e) {
			e.printStackTrace();
			//读取发生了错误
			length=-1;//如果发生解析错误,则处理protocal时将其赋值为null,则后续操作将无法执行,记住对于null的操作
		}
		for(int j=0;j<length;j++){
			sb.append((char)bs[j]);
			protocal=sb.toString();
		}
		//从protocal中取出uri
		if(protocal==null||"".equals(protocal)){
			return ;
		}
		this.uri=parseUri(protocal);//解析出uri
		
		this.method=parseMethod(protocal);//解析出method
		
		this.parameter=parseParameter(protocal);//解析出所有的参数
	}
	
	/**
	 * 解析参数
	 * @param protocal
	 * @return
	 */
	//  GET /servlet/hello?name=yhj&age=21 HTTP/1.1
		
	// POST /servlet/hello
		
	//		name=yhj&age=20
	private Map<String, String> parseParameter(String protocal) {
		String parameterString=null;
		//取出参数部分,存到parameterString中取
		if(this.method.equals(TomcatConstants.REQUEST_METHOD_GET)){//如果是Get方式,则paramter接在uri后面,从protocal中取出第一个空格到第二个空格之间的字符串
			String[] ss=protocal.split(" ");
			int questionindex=ss[1].indexOf("?");
			if(questionindex>0){
				//只要有?,就截取
				parameterString=ss[1].substring(questionindex+1);
			}
		}else if(this.method.equals(TomcatConstants.REQUEST_METHOD_POST)){//如果是Post方式,则在请求头的最后
			parameterString=protocal.substring(protocal.lastIndexOf("\n")+1);
		}
		String []  keyvalues=null;
		if(parameterString!=null&¶meterString.length()>0){
			keyvalues=parameterString.split("&");//取出所有的参数对
			for(int i=0;i<keyvalues.length;i++){
				String keyvalu=keyvalues[i];//取出每一对
				String [] kv=keyvalu.split("=");//键值分开
				this.parameter.put(kv[0], kv[1]);//存到map中,返回
			}
		}
		return parameter;
	}
	private String parseMethod(String protocal) {
		String method=protocal.substring(0, protocal.indexOf(" "));//请求头是固定的,由之前给出的示例可以看出method的位置
		return method;
	}
	public String parseProtocal(String iis){
		String protocal=null;
		return protocal;
	}
	
	public String parseUri(String protocal){
		//TODO:从protocal中解析出uri
		if(protocal==null||"".equals(protocal)){
			return null;//防止之前protocal为空,将不执行任何操作
		}
		String [] ss=protocal.split(" ");
		
		if(validateProtocal(ss)!=200){//校验的操作,这里不做实现,默认验证通过
			return null;
		}
		if(ss[1].indexOf("?")>0){
			this.uri=ss[1].substring(0,ss[1].indexOf("?"));//如果有参数,则先把参数去掉,
		}else{
			this.uri=ss[1];
		}
		return this.uri;
	}
	
	public int validateProtocal(String[] s){
		return 200;
	}
	
	public String getUri(){
		return uri;
	}
	@Override
	public String getMethod() {
		return this.method;
	}
	@Override
	public String getParameter(String key) {
		return this.parameter.get(key);
	}
	@Override
	public HashMap<String, String> getSession() {
		HashMap<String, String> temp=new HashMap<String,String>();
		//temp.get(key)
		return null;
	}
	
}

ServletResponse.java

package com.yhj.servlet.container;

import java.io.PrintWriter;

public interface ServletResponse {

	/**
	 * 获取PrintWriter 
	 * @return
	 */
	public PrintWriter getWriter();
	
	public void sendRedirect();
	
}

HttpServletResponse.java

package com.yhj.servlet.container;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;

public class HttpServletResponse implements ServletResponse{
	
	private String webroot=System.getProperty("user.dir")+File.separator+"webapps";
	
	private ServletRequest request;
	/**一个响应头的示例
	 * HTTP/1.1 200 OK  
		Server: IBM/4.0  
		Date: Sat, 6 Nov 2013 13:13:00 GMT  
		Content-Type: text/html  
		Last-Modified: Sat, 5 Jan 2013 13:13:12 GMT  
		Content-Length: 112  
	 */
	private OutputStream out;
	
	/**
	 * 构造方法
	 * @param request
	 * @param out
	 */
	public HttpServletResponse(ServletRequest request,OutputStream out) {
		super();
		this.request=request;
		this.out = out;
	}
	
	public void sendRedirect(){
		/**
		 * 1.从request中取出uri
		 * 2.判断是否在本地存在uri指代的文件
		 * 3.没有404,有
		 * 4.以输入流读取这个文件
		 * 5.以输出流将文件写到客户端,要加入响应的协助
		 */
		String uri=request.getUri();
		if(uri==null){
			return;
		}
		File f=new File(webroot,uri);
		String responseprotocal=null;
		if(!f.exists()){
			//文件不存在,返回404
			String httpBody=readFile(new File(webroot,"404.html"));
			responseprotocal=gen404(httpBody.getBytes().length);
			responseprotocal+=httpBody;
		}else{
			String httpBody=readFile(f);
			responseprotocal=gen200(httpBody.getBytes().length);
			responseprotocal+=httpBody;
		}
		try{
			out.write(responseprotocal.getBytes());
			out.flush();
		}catch(Exception e){
			//输出500的错误
		}
		
	}

	private String gen200(int length) {
		String protocal200="HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: "+length+"\r\n\r\n";
		return protocal200;
	}

	private String gen404(int length) {
		String protocal404="HTTP/1.1 404 File Not Found\r\nContent-Type: text/html\r\nContent-Length: "+length+"\r\n\r\n";
		return protocal404;
	}

	private String readFile(File f) {
		FileInputStream fis=null;
		StringBuffer sb=new StringBuffer();
		try {
			fis=new FileInputStream(f);
			byte[] bs=new byte[1024];
			int length=-1;
			while(	(length=fis.read(bs,0,bs.length))!=-1	){
				sb.append(new String(bs,0,length));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			if(fis!=null){
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return sb.toString();
	}
	
	private PrintWriter output;

	@Override
	public PrintWriter getWriter() {
		this.output=new PrintWriter(out);
		return this.output;
	}
	
}

Processor.java

package com.yhj.servlet.container;

/**
 * 用来处理静态或动态的请求
 * @author Administrator
 *
 */
public interface Processor {

	/**
	 * 处理的方法
	 * @param response
	 * @param request
	 */
	public void process(	ServletResponse response,ServletRequest request 	);
}
 

StaticProcessor.java

package com.yhj.servlet.container;

public class StaticProcessor implements Processor {

	@Override
	public void process(ServletResponse response, ServletRequest request) {
		response.sendRedirect();
	}

}


DynamicProcessor.java

package com.yhj.servlet.container;

import java.io.PrintWriter;
import java.net.URL;
import java.net.URLClassLoader;

public class DynamicProcessor implements Processor {

	@Override
	public void process(ServletResponse response, ServletRequest request) {
		//取出uri    /servlet/servlet类名
		String uri=request.getUri();
		//取出servletname
		String servletName=uri.substring(uri.lastIndexOf("/")+1);
		//3.leijiazai ->仍要通过url地址来加载servlet字节码
		//	URLClassLoader
		URLClassLoader loader=null;
		
		//4.通过字节码反射成一个对象,
		Servlet servlet = null;
		try {
			URL url=new URL("file",null,TomcatConstants.BASE_PATH);
			URL [] urls=new URL[]{url};
			//创建了一个类加载器,这个加载器从项目下的webapps中读取servlet
			loader=new URLClassLoader(urls);
			//加servletname
			Class c=loader.loadClass(servletName);//因为是没有打包的java,类加载器默认加载的位置在bin下,所以可以加载到
			servlet = (Servlet)c.newInstance();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try{
			if(servlet!=null&&servlet instanceof Servlet){
				//控制生命周期=>init()=>service()
				servlet.init();
				servlet.service(response, request);
			}
		}catch(Exception e){
			String body=e.getMessage();
			body=gen500(body.getBytes().length)+body;
			PrintWriter out=response.getWriter();
			out.println(body);
			out.flush();
		}
	}
	
	private String gen500(int length) {
		String protocal404="HTTP/1.1 500 Server Internal ERROR\r\nContent-Type: text/html\r\nContent-Length: "+length+"\r\n\r\n";
		return protocal404;
	}

}

Servlet.java

package com.yhj.servlet.container;

/**
 * 自定义的Servlet接口,可以用回调来控制生命周期
 * @author Administrator
 *
 */
public interface Servlet {
	/**
	 * init方法
	 */
	public void init();
	
	/**
	 * Service方法,判断method为Get或者为POST,再对应调用
	 * @param response
	 * @param request
	 */
	public void service(	ServletResponse response,ServletRequest request 	);
	/**
	 * GET方式调用doGet
	 * @param response
	 * @param request
	 */
	public void doGet(	ServletResponse response,ServletRequest request 	);
	/**
	 * POST方式采用doPost
	 * @param response
	 * @param request
	 */
	public void doPost(	ServletResponse response,ServletRequest request 	);
}


Hello.java(自定义的Servlet)

import java.io.PrintWriter;

import com.yhj.servlet.container.Servlet;
import com.yhj.servlet.container.ServletRequest;
import com.yhj.servlet.container.ServletResponse;


public class Hello implements Servlet {
	
	public Hello(){
		System.out.println("constructor");
	}

	@Override
	public void doGet(ServletResponse response, ServletRequest request) {
		String uname=request.getParameter("uname");
		int age=Integer.parseInt(request.getParameter("age"));
		String body="<html><head></head><body>姓名:"+uname+"年龄:"+age+"</body></html>";
		String protocal="HTTP/1.1 200 OK\r\nContent-Type: text/html;charset=utf-8;\r\nContent-Length: "+body.getBytes().length+"\r\n\r\n"+body;
		PrintWriter out=response.getWriter();
		out.println(protocal);
		out.flush();
	}

	@Override
	public void doPost(ServletResponse response, ServletRequest request) {
		String uname=request.getParameter("uname");
		String age=request.getParameter("age");
		
		System.out.println(uname+"\t"+age);
		
		String body="{\"uname\":\""+uname+"\",\"age\":"+age+"}";
		String protocal="HTTP/1.1 200 OK\r\nContent-Type:  application/json;charset=utf-8;\r\nContent-Length: "+body.getBytes().length+"\r\n\r\n"+body;
		PrintWriter out=response.getWriter();
		out.println(protocal);
		out.flush();
		
//		PrintWriter out=
	}

	@Override
	public void init() {
		System.out.println("init()");
	}

	@Override
	public void service(ServletResponse response, ServletRequest request) {
		//判断method是什么,调用doGet或者doPost方法 
		if(request.getMethod().equals("GET")){
			doGet(response,request);
		}else if(request.getMethod().equals("POST")){
			doPost(response,request);
		}
	}

}

form.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>学生填表</title>
<style>
	input{
		margin:5px;
	}
</style>
<script src="js/jquery-1.11.3.min.js"></script>
</head>

<body>
	<form>
    	<label>姓名:</label><input type="text" id="uname" name="uname"/><br/>
        <label>学号:</label><input type="text" id="age" name="age"/><br/>
        <!--<input url="/servlet/Hello" type="submit" value="提交" />-->
        <input type="button" value="提交" onClick="doSubmit()"/>
    </form>
    
   <script>
   		function doSubmit(){
   			var uname=$("#uname").val();
   			var age=$("#age").val();
   			$.ajax({
	   			url:'/servlet/Hello',
	   			type:'POST',
	   			dataType:'JSON',
	   			data:{'uname':uname,'age':age},
	   			success:function(data){
   					if(data!=null&&data!=""){
   						document.write("<html><head></head><body>姓名:"+data.uname+"年龄:"+data.age+"</body></html>");
   					}
	   			}
	   		});
   		}
   </script>
</body>
</html>

8.测试:

a)浏览器访问:127.0.0.1:8888/form.html (静态访问)

技术分享技术分享

b)浏览器访问( jquery   ajax异步提交请求,Post方式):127.0.0.1:8888/form.html -->填写页面信息并提交

技术分享技术分享

c)浏览器访问(Get方式):127.0.0.1:8888/servlet/Hello?uname=yhj&age=22(结果同上)

原理分析——编写、实现Tomcat(一)

标签:

原文地址:http://blog.csdn.net/pkyyhh/article/details/51225120

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!