简易版Tomcat 实现过程
在JavaSE中,我们的每一个程序都是依赖main函数来执行的,而到了JavaEE后,发现main方法不见了,而是转为使用Servlet来进行响应来自浏览器的请求,而Servlet又是装在Tomcat中的,Tomcat底层到底做了些啥呢?
1、Tomcat所涉及的部分技术
- HTTP协议格式
- JavaSE的Socket编程
2、动手实现
2.1 模拟浏览器获取服务器资源
- 建立一个和服务端对应的Socket对象,并指明连接服务端指定的域名和端口号
- 通过Socket获取到指向服务端的输出流对象
- 通过Socket获取到一个输入流
- 将HTTP协议的请求部分发送到服务器
- 接收来自服务端的数据并输出
- 释放资源
public class TestClient {
public static void main(String[] args) throws IOException {
//1。建立一个Sockety对象
Socket socket = null;
InputStream is = null;
OutputStream ops = null;
try {
socket = new Socket("www.itcast.cn",443);
//2.获取到输出流对象
ops = socket.getOutputStream();
//3.获取到输入流对象
is = socket.getInputStream();
//4.将HTTP协议的请求部分发送到客户端
ops.write("GET /subject/about/index.html HTTP/1.1\\n".getBytes());
ops.write("HOST:www.itcast.cn\\n".getBytes());
ops.write("\\n".getBytes());
//5.读取来自客户端的数据打印到控制台
int len = 0;
while ((len = is.read()) != -1){
System.out.print((char)len);
}
} catch (Exception e){
e.printStackTrace();
} finally {
if (null != is){
is.close();
}
if (null != ops)
ops.close();
if (socket != null)
socket.close();
}
}
}
结果如下
2.2 模拟服务端向客户端响应数据
- 创建ServerSocket对象,监听本机的8080端口
- 获取socket对象
- 通过socket对象获取输出流对象
- 将HTTP协议的响应部分发送到客户端
- 释放资源
public class TestServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream ops = null;
try {
//创建ServerSocket对象,监听8080端口
serverSocket = new ServerSocket(8080);
while(true){
//获取客户端对应的socket
socket = serverSocket.accept();
//获取输入输出流对象
ops = socket.getOutputStream();
//通过获取到的输出流对象将HTTP协议的响应部分发送客户端
ops.write("HTTP/1.1 200 OK\n".getBytes());
ops.write("Server:apache-Coyote/1.1\n".getBytes());
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
//写入响应体
StringBuffer buf = new StringBuffer();
buf.append("<html>");
buf.append("<head><title>我是标题</title></head>");
buf.append("<body>");
buf.append("<h1>i am a head1</h1>");
buf.append("<h1>i am a head1</h1>");
buf.append("</body>");
buf.append("</html>");
ops.write(buf.toString().getBytes());
}
} catch (Exception e){
e.prinStackTrace();
} finally{
if(null!=ops){
ops.close();
ops=null;
}
if(null!=socket){
socket.close();
socket=null;
}
}
}
}
2.3 Tomcat1.0
- 在WebContent下发布静态资源demo.html,demo02.html
- 启动Tomcat服务器
- 当客户端对服务端发起不同的请求,localhost:8080/demo.html
- 服务端可以将对应的html页面响应到客服端
附上Project Structure
demo01.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
demo01.html
</body>
</html>
demo02.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
demo02.html
</body>
</html>
服务端
public class TestServer {
//System.getProperty("user.dir") 为获取用户当前的工作目录
//定义一个变量,用于存放服务端WebContent目录的绝对路径
private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";
//存放本次请求的静态页面名称
private static String url = "";
public static void main(String[] args) throws IOException {
//定义流和Socket对象
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream ops = null;
InputStream is = null;
try {
serverSocket = new ServerSocket(8050);
while (true){
//获取客户端对应的socket
socket = serverSocket.accept();
//获取输入输出流对象
ops = socket.getOutputStream();
is = socket.getInputStream();
//获取HTTP协议的请求部分,截取要获取的资源名称,并将这个名称赋给URL
parse(is);
//发送静态资源
sendStaticResource(ops);
}
}catch (Exception e){
e.printStackTrace();
} finally {
if (null != socket){
socket.close();
socket= null;
}
if (null != ops){
ops.close();
ops = null;
}
if (null != is){
is.close();
is = null;
}
}
}
/**
* 通过获取Socket的输入流来获取HTTP协议请求数据
* @param is
* @throws IOException
*/
public static void parse(InputStream is) throws IOException {
//存放HTTP协议请求部分数据
StringBuffer sb = new StringBuffer();
byte[] buffer = new byte[2048];
int len = -1;
len = is.read(buffer);
for (int j=0;j<len;j++){
sb.append((char)buffer[j]);
}
//打印HTTP协议请求数据
System.out.println(sb);
//传入HTTP协议请求数据,截取客户端要请求的资源路径,赋值给URL
parseURL(sb.toString());
}
/**
* 通过传入的HTTP协议请求数据,截取客服端请求的资源路径,赋值给URL
* @param content
*/
public static void parseURL(String content) {
int index1,index2;
//获取第一个空格和第二个空格的位置,请求的资源路径是在第一个空格和第二个空格之间的
index1 = content.indexOf(" ");
if (index1 != -1){
index2 = content.indexOf(" ",index1+1);
if (index2>index1){
url = content.substring(index1+2,index2);
}
}
System.out.println(url);
}
/**
* 通过Socket的输出流向客户端响应资源
* @param ops
* @throws IOException
*/
public static void sendStaticResource(OutputStream ops) throws IOException {
//存放本次请求的静态资源的内容
byte[] bytes = new byte[2048];
//用户获取静态资源的内容
FileInputStream fis = null;
try {
//代表本次请求的静态资源文件
File file = new File(WEB_ROOT,url);
//文件存在
if (file.exists()){
//向客户端输出响应行于响应头
ops.write("HTTP/1.1 200 OK\n".getBytes());
ops.write("Server:apache-Coyote/1.1\n".getBytes());
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
//获取字节流输入对象
fis = new FileInputStream(file);
//读取静态资源到数组
int ch = -1;
while ((ch = fis.read(bytes)) != -1){
ops.write(bytes,0,ch);
}
}
//文件不存在
else {
ops.write("HTTP/1.1 404 not found\n".getBytes());
ops.write("Server:apache-Coyote/1.1\n".getBytes());
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
ops.write("file not found".getBytes());
}
} catch (Exception e){
e.printStackTrace();
} finally {
if (null != fis){
fis.close();
fis = null;
}
}
}
}
注意:端口号自己设定。
2.4 Tomcat 2.0
当客户端发送请求到服务器的时候,可以运行服务端的一段java代码,并且可以向客户端响应数据
- 服务器启动读取配置数据到map中(即常使用的web.xml的简化,做映射用)
- 浏览器向服务器发起请求,localhost:8050/MyTomcat/aa
- 获取HTTP请求部分,解析本次请求路径aa,从map获取相应的路径
- 通过反射将aa路径所对应的AServlet对象加载到内存
- 向客户端发送HTTP协议响应头/响应行
- 调用AServlet对象的init,service,destroy方法
定义Servlet接口
public interface Servlet {
public void init();
public void service(InputStream is, OutputStream ops) throws IOException;
public void destroy();
}
配置文件 conf.properties
aa=AServlet
bb=BServlet
服务端
public class TestServer {
//System.getProperty("user.dir") 为获取用户当前的工作目录
//定义一个变量,用于存放服务端WebContent目录的绝对路径
private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "WebContent";
//存放本次请求的静态页面名称
private static String url = "";
//用于存储配置文件中的信息
private static Map<String,String> map = new HashMap<>();
static {
//服务器启动前将配置信息加载到MAP中
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 = properties.getProperty(key);
map.put(key,value);
}
} catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
Socket socket = null;
OutputStream ops = null;
InputStream is = null;
try {
serverSocket = new ServerSocket(8050);
while (true){
//获取客户端对应的socket
socket = serverSocket.accept();
//获取输入输出流对象
ops = socket.getOutputStream();
is = socket.getInputStream();
//获取HTTP协议的请求部分,截取要获取的资源名称,并将这个名称赋给URL
parse(is);
//判断本次请求是请求静态demo,还是Servlet
if (null != url){
if (url.indexOf(".") != -1){
//发送静态资源
sendStaticResource(ops);
} else {
//发送动态资源
sendDynamicResource(is,ops);
}
}
}
} catch (Exception e){
e.printStackTrace();
} finally {
if (null != socket){
socket.close();
socket= null;
}
if (null != ops){
ops.close();
ops = null;
}
if (null != is){
is.close();
is = null;
}
}
}
public static void sendDynamicResource(InputStream is, OutputStream ops) throws Exception {
//输出响应行和响应头
ops.write("HTTP/1.1 200 OK\n".getBytes());
ops.write("Server:apache-Coyote/1.1\n".getBytes());
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
if (map.containsKey(url)){
String value = map.get(url);
Class clazz = Class.forName(value);
Servlet servlet = (Servlet)clazz.newInstance();
servlet.init();
servlet.service(is,ops);
servlet.destroy();
}
}
public static void sendStaticResource(OutputStream ops) throws IOException {
//存放本次请求的静态资源的内容
byte[] bytes = new byte[2048];
//用户获取静态资源的内容
FileInputStream fis = null;
try {
//代表本次请求的静态资源文件
File file = new File(WEB_ROOT,url);
//文件存在
if (file.exists()){
//向客户端输出响应行于响应头
ops.write("HTTP/1.1 200 OK\n".getBytes());
ops.write("Server:apache-Coyote/1.1\n".getBytes());
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
//获取字节流输入对象
fis = new FileInputStream(file);
//读取静态资源到数组
int ch = -1;
while ((ch = fis.read(bytes)) != -1){
ops.write(bytes,0,ch);
}
}
//文件不存在
else {
ops.write("HTTP/1.1 404 not found\n".getBytes());
ops.write("Server:apache-Coyote/1.1\n".getBytes());
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
ops.write("file not found".getBytes());
}
} catch (Exception e){
e.printStackTrace();
} finally {
if (null != fis){
fis.close();
fis = null;
}
}
}
public static void parse(InputStream is) throws IOException {
//存放HTTP协议请求部分数据
StringBuffer sb = new StringBuffer();
//存放HTTP协议请求部分数据
byte[] buffer = new byte[2048];
int len = -1;
len = is.read(buffer);
for (int j=0;j<len;j++){
sb.append((char)buffer[j]);
}
//打印HTTP协议请求数据
System.out.println(sb);
//截取客户端要请求的资源路径,赋值给URL
parseURL(sb.toString());
}
public static void parseURL(String content) {
int index1,index2;
//获取第一个空格和第二个空格的位置
index1 = content.indexOf(" ");
if (index1 != -1){
index2 = content.indexOf(" ",index1+1);
if (index2>index1){
url = content.substring(index1+2,index2);
}
}
System.out.println(url);
}
}
Q.E.D.