实现迷你的Tomcat
学完JavaSE向JavaWeb过渡后,总有种不习惯的感觉。就像从C语言向Java过渡一样,感觉好多事情都不必自己实现,学起来总不踏实。所以我就有了想要简单了解一下tomcat作为容器到底是怎样工作的的想法
首先我们得了解一下http请求的结构
一会我们会参照这种结构手动构造响应消息
打开IDEA,创建一个新的项目
结构如下:
在项目下新增一个WebContent文件夹,在文件夹中创建一个.properties配置文件和其他静态资源,如Html文件
在src下创建一个包,然后创建MyTomcat类,和一个Servlet接口,所有的servlet都必须实现这个接口
代码如下(附注释):
package com.mytomcatv2;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
public class TestServer {
//创建静态全局变量存储该项目的绝对路径,WebContent为静态资源(Html文件)位置getProperty()为系统方法
public static String WEB_ROOT = System.getProperty("user.dir") + "/" +"WebContent";
//url用来保存通过解析Http请求头得到的请求资源名,Http请求头结构为上图所示
public static String url = "";
//存储配置信息(配置信息为键值对,通过读取配置文件获得)
private static Map<String,String> map = new HashMap<>();
//静态代码块,在main方法之前运行,可保证程序首先读取配置文件
static {
Properties properties = new Properties();
try{
properties.load(new FileInputStream(WEB_ROOT+"/conf.properties"));
Set set = properties.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext())
{
String key = (String) iterator.next();
String value = (String) properties.get(key);
map.put(key,value);
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
//服务端套接字绑定8080端口
ServerSocket serverSocket = new ServerSocket(8080);
//初始化套接字,后面会用来保存客户端套接字
Socket socket = null;
//输入输出流通过一会接收到的套接字获得
InputStream inputStream = null;
OutputStream outputStream = null;
try {
//设死循环可以是服务端接受多个请求
while (true)
{
//serverSocket调用accept时阻塞,直到监听到客户端请求,将其用socket保存
socket = serverSocket.accept();
//通过socket获得客户端的输入输出流
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
//输入流中保存着来自客户端的Http请求,用函数parseToURL(自己实现的函数都在下面)处理,将请求的资源解析出来保存至全局变量url
parseToURL(inputStream);
//判断是否正确得到url
if (url!=null)
{
//如果indexOf的反回值不为-1,则说明url中存在".",即为.html等静态资源,调用senStaticResource(自写函数)处理
if(url.indexOf(".")!=-1)
{
sendStaticResource(outputStream);
} else {
//不含".",则说明请求的是动态资源servlet,调用该函数通过反射加载Servlet来处理请求
sendDynamicResource(inputStream,outputStream);
}
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (inputStream!=null)
{
inputStream.close();
inputStream = null;
}
if (outputStream!=null)
{
outputStream.close();
outputStream = null;
}
if (socket!=null)
{
socket.close();
socket = null;
}
}
}
private static void sendDynamicResource(InputStream inputStream,OutputStream outputStream) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
//向响应中写入Http格式响应头,格式不了解的话对比最上面的图片
outputStream.write("HTTP/1.1 200 OK\nContent-Type:text/html;charset=utf-8\nServer:Apache-Coyote/1.1\n\n".getBytes());
//判断map中是否含有该url,有的话就可得到对应的Servlet路径
if (map.containsKey(url))
{
//value保存url对应的Servlet的路径名
String value = map.get(url);
//加载该Servlet
Class clazz = Class.forName(value);
//Servlet是一个接口,所有的Servlet都必须实现该接口,就是为了这一步不管url对应的哪个servlet都能成功赋值Servlet接口的实例
Servlet servlet = (Servlet)clazz.newInstance();
//调用Servlet接口的init方法
servlet.init();
//调用servlet的服务方法,我们平时在Tomcat中写的代码就放在这里面
servlet.Service(inputStream,outputStream);
}
}
private static void sendStaticResource(OutputStream outputStream) throws IOException {
byte[] buffer = new byte[2048];
FileInputStream fileIn = null;
try{
File file = new File(WEB_ROOT,url);
if (file.exists())
{
//文件存在就向浏览器返回成功的消息
outputStream.write("HTTP/1.1 200 OK\nContent-Type:text/html;charset=utf-8\nServer:Apache-Coyote/1.1\n\n".getBytes());
fileIn = new FileInputStream(file);
//将url对应的静态资源读至字节数组
int ch = fileIn.read(buffer);
while (ch!=-1)
{
outputStream.write(buffer,0,ch);
ch = fileIn.read(buffer);
}
}else {
//url对应的静态资源不存在就返回响应404
outputStream.write("HTTP/1.1 404 not found\nContent-Type:text/html;charset=utf-8\nServer:Apache-Coyote/1.1\n\n\nfile not found".getBytes());
}
}
catch(Exception e)
{
e.printStackTrace();
}finally {
if (fileIn!=null){
fileIn.close();
fileIn = null;
}
}
}
private static void parseToURL (InputStream inputStream) throws IOException{
StringBuffer content = new StringBuffer();
byte[] buffer = new byte[2048];
int i = -1;
//将浏览器请求读至字节数组,请求一般不会多于2048字节,int i 保存请求大小
i = inputStream.read(buffer);
//循环i次将请求保存至content
for (int j=0;j<i;j++)
content.append((char)buffer[j]);
//将请求变成字符串传给该函数,该函数解析请求获得url
parseURL(content.toString());
}
private static void parseURL(String content) {
//由开篇图片的格式得,url位于第一个空格和第二个空格之间,所以通过两个空格的下标得到url
int index1,index2;
index1 = content.indexOf(" ");
if (index1!=-1) {
index2 = content.indexOf(" ", index1 + 1);
if (index1<index2)
{
url = content.substring(index1+2,index2);
}
}
}
}
Servlet接口代码:
package com.mytomcatv2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public interface Servlet {
//所有的servlet都必须实现该接口,就相当于我们平常使用的HttpServlet
public void init();
public void Service(InputStream in, OutputStream out) throws IOException;
public void destroy();
}
配置文件内容如下:
aa=com.mytomcatv2.AAServlet
bb=com.mytomcatv2.BBServlet
即如果在URL中请求aa(localhost:8080/aa),程序就会加载aa对应路径的类(servlet),就相当于平常的注解或者在XML文件中的配置