Tomcat是怎么炼成的?(3)

重构

       在之前一讲里,我们已经构建了一个简单的Web服务器,可以很好的处理静态的网页,而我们的目标是希望能够写一个tomcat,简单的来说是可以处理那些java写的servlet程序,其实tomcat也可以被称为servlet容器。在我们为程序添加这部分功能之前,我们需要进行一次重构。
       我想作为程序员的你,对于重构这个词肯定应该不会陌生。在一个项目开发的不同阶段,由于性能、功能乃至其他原因,我们都需要对我们的程序进行不断的调整,在重构之前,我们先对我们目前程序的逻辑做一个梳理。我们的程序主要功能可以分为如下三大块:
1 接收客户端的请求
      创建一个ServerSocket对象,持续接收客户端发来的Socket连接,我们可以将这部分称为连接器,算作整个程序的“前端”部分;
2 处理客户端的请求
      通过前面得到的Socket对象,取得其中的InputStream对象,然后可以读取HTTP的协议内容,目前我们是需要读取客户端请求的文件名称,我们可以将这部分称为服务器的请求部分。
3 响应客户端的请求
      根据得到的Socket对象,取得其中的OutputStream对象,根据前面客户端处理的请求的结果,目前就是客户端请求的文件路径,调用OutputStream对象的write方法写回给客户端,我们可以将这部分称为服务器的响应部分。
      在我们之前的程序中,对应客户端请求,我们写了一个方法为parseUrl方法,用于解析HTTP协议中文件路径,对应客户端的响应部分,我们写了一个方法为processStaticResource方法,用于将指定的文件内容以二进制数组的形式发回给浏览器。从程序设计的思想上来看,我们采用的是最为直接的过程式思想,整个程序的运行“核心”是方法(函数),而另一种面向对象的编程思想我想你也应该不陌生,而关于这两种思想的优劣就不再我们的讨论范围。单就我们目前的程序的复杂程度上来说,我们使用面向对象的思想来重构,也许你觉得未必就有多好,甚至还会觉得更麻烦(确实,我第一次这么做的时候也有这样的感受),但请相信我,当我们再往后两天的话,你一定会有新的感受。
      好,现在我们就开始重构我们的代码。

      首先根据前面的分析,我们可以根据三块功能,分别对应为三个对象:连接器对象、请求对象和响应对象,这里本着简单的原则,因为我们目前还并为给连接器有什么单独的处理,所以暂时就先放在main方法中,保持原有的逻辑,而将请求对象和响应对象单独分离开来。

       请求对象,是直接的反馈用户请求的内容,比如需要显示的文件的名称等,具体的代码如下:

package com.tomcat;

import java.io.IOException;
import java.io.InputStream;

public class Request {
	private String filePath;
	private InputStream inputStream;
	
	public Request(InputStream inputStream){
		this.inputStream = inputStream;
	}
	
	public void parse(){
		StringBuilder stringBuilder = new StringBuilder();
		byte[] bytes = new byte[Constants.BufferedSize];
		int length = 0;
		try {
			length = inputStream.read(bytes);
		} catch (IOException e) {
			length = -1;
		}
		for(int i=0;i<length;i++){
			stringBuilder.append((char)bytes[i]);
		}
		parseUri(stringBuilder.toString());
	}
	
	private void parseUri(String requestHeader){
		filePath = null;
		int index1 = 0;
		index1 = requestHeader.indexOf(' ');
		if(index1>-1){
			int index2 = requestHeader.indexOf(' ', index1+1);
			if(index2>-1){
				filePath = requestHeader.substring(index1+1, index2);
			}
		}
	}
	
	public String getFilePath() {
		return filePath;
	}


	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}


	public InputStream getInputStream() {
		return inputStream;
	}
	public void setInputStream(InputStream inputStream) {
		this.inputStream = inputStream;
	}
	
}

      Request类有两个变量:
      InputStream:从连接器的Socket对象中获得的数据输入流对象;
      FilePath:客户端请求的文件路径;
      Request类有一个私有的方法:
      parseUri:用于从客户端的HTTP协议内容中提取客户端请求文件的路径,与前面一讲的方法其实是一致的。
      Request类有一个重要的公用的方法:
      parse:用于从InputStream中读取客户端的HTTP协议内容,并转换为字符串,然后调用内部私有parseUri方法获得最终客户端请求文件的路径。
      另外,Request类还对外公开了两个变量的set、get方法,这里就不再多说。

      关于方法的私有还是公开的,这个还涉及了很多面向对象的知识,可以多阅读相关的书籍并多实践,这里如果你觉得我这样的设计有任何不妥也欢迎随时指正。
      好,我们下面接着来说响应对象,主要是根据之前的请求对象响应用户的请求,比如把用户请求的文件以文件流的方式发回给客户端,具体的代码如下:

package com.tomcat;

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

public class Response {
	private OutputStream outputStream;
	private Request request;
	
	public Response(OutputStream outputStream,Request request){
		this.outputStream = outputStream;
		this.request = request;
	}
	
	public void sendStaticResource() throws IOException{
		if(request.getFilePath()!=null){
			File file = new File(Constants.WEB_ROOT, request.getFilePath());
			FileInputStream fileInputStream = null;
			try {
				fileInputStream = new FileInputStream(file);
				byte[] bytesForFile = new byte[Constants.BufferedSize];
				int ch = fileInputStream.read(bytesForFile, 0, Constants.BufferedSize);
				while(ch!=-1){
					outputStream.write(bytesForFile,0,ch);
					ch = fileInputStream.read(bytesForFile, 0, Constants.BufferedSize);
				}
			} catch (FileNotFoundException e) {
				System.out.println(e.getMessage());
				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{
				try {
					if(fileInputStream!=null){
						fileInputStream.close();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public OutputStream getOutputStream() {
		return outputStream;
	}

	public void setOutputStream(OutputStream outputStream) {
		this.outputStream = outputStream;
	}

	public Request getRequest() {
		return request;
	}

	public void setRequest(Request request) {
		this.request = request;
	}
	
}

      Response类有两个属性:
      OutputStream:从连接器的socket对象中取得的数据输出流,使用这个对象的write方法向客户端写入文件;
Request:目前我们发送静态文件的时候,只需要一个文件路径即可,但今后我们还需要接收用户的参数等内容,这些也将会作为Request对象的属性,因此这里我们就直接把Request对象作为Response对象的属性,这样今后就可以取得任何需要的内容。
      Response类有一个公开的方法:
      sendStaticResource:这个方法和之前一天我们写的send方法很相似,区别在于现在我们不需要考虑参数的传递,可以直接使用Response对象的属性取得数据输出流和文件路径,完成整个响应的过程。

      好,接下来我们就可以通过调用我们前面写的两个类,与我们的主方法组合起来,具体的代码如下:

package com.tomcat;

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

public class Main {
	public static void main(String[] args){
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(Constants.port, 0, InetAddress.getByName("127.0.0.1"));
			while(true){
				Socket socket = serverSocket.accept();
				InputStream inputStream = socket.getInputStream();
				OutputStream outputStream = socket.getOutputStream();
				Request request = new Request(inputStream);
				request.parse();
				Response response = new Response(outputStream,request);
				response.sendStaticResource();
				socket.close();
			}
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

      现在你可以明显的感觉的到,我们的主方法代码清爽了不少,在接收到客户端的请求后,通过Sokcet对象,可以取得InputStream对象和OutputStream对象,然后就是分别构建Request对象和Response对象,最后调用各自的公共方法,其他的业务处理都与之前相同。
      好了,又到了享受胜利果实的时候了,启动你的程序,打开网页,看看自己的成果?哎,看起来好像和之前也没什么区别吗?恩,别着急,下一讲我们会做点有挑战性的工作。
      特别提醒一下,对于面向对象的设计思想必须要多多揣摩,多写代码,往往我们都是看看别人的都头头是道,但到自己的时候却头脑空空的。

转载于:https://my.oschina.net/changyu496/blog/53563

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值