手写 minitomcat
目标:可以通过浏览器客户端发送 http 请求,minicat 可以接受请求并进行处理,处理后的结果可以返回给客户端。
- 接受请求,socket 通信
- 请求信息封装成 Request 对象,返回的信息封装成 Response 对象
- 客户端请求资源,分为静态资源和动态资源
- 资源返回给浏览器
好吧稍微有点标题党,不过对于理解tomcat的原理机制,这样一个mini版的tomcat自己写出来,还是很有帮助的,话不多说,进入正题。
1.0版本
需求:请求 8080 端口,固定返回字符串。
创建 BootStrap 类
端口号暂时写死 8080,编写 start 方法,监听端口,获取请求,返回资源。
通过 main 方法启动,代码如下:
public class BootStrap {
//定义 socket 监听端口号(写死)
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
/**
* 初始化操作
*/
public void start() throws IOException {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("-------->mini-tomcat start on port: " + port);
while (true){
Socket accept = serverSocket.accept();
OutputStream outputStream = accept.getOutputStream();
outputStream.write("Hello mini-tomcat!".getBytes());
accept.close();
}
}
//程序入口
public static void main(String[] args) {
BootStrap bootStrap = new BootStrap();
try {
bootStrap.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
什么?太简单了?
但你会发现,此时浏览器访问,并不能实现这个功能,因为返回信息的时候,我们没有按照 http 的规范,浏览器无法识别。
增加一个 httpProtocolUtil 类
封装返回信息
/**
* http 协议工具类
* 提供响应头信息。只提供 200 和 404 即可
*/
public class HttpProtocolUtil {
public static String getHttpHeader200(long contentLength) {
return "HTTP/1.1 200 OK \n" +
"Content-Type: text/html \n" +
"Content-Lenght: " + contentLength + " \n" +
"\r\n";
}
public static String getHttpHeader404() {
String str404 = "<h1>404 not found</h1>";
return "HTTP/1.1 404 NOT Found \n" +
"Content-Type: text/html \n" +
"Content-Lenght: " + str404.getBytes().length + " \n" +
"\r\n" + str404;
}
}
再改造之前的 start 方法
while (true){
Socket accept = serverSocket.accept();
OutputStream outputStream = accept.getOutputStream();
String responseText = HttpProtocolUtil.getHttpHeader200("Hello mini-tomcat!".getBytes().length)+"Hello mini-tomcat!";
outputStream.write(responseText.getBytes());
accept.close();
}
再次访问 localhost:8080
bingo~成功
容易吧,不过这只是入门版本而已,太low了,返回的内容都是固定的,看上去好像也没什么技术含量,不着急,2.0版本马上来~
2.0版本
需求:通过发送请求,返回静态资源。
首先。我们先弄清楚,浏览器发请求过来,到底发了一些什么东西?
在后台的逻辑中,先获取请求内容进行控制台打印,看看请求的信息都有是什么,代码如下:
//2.0 版本
while (true){
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
int count = 0;
while (count == 0){
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
System.out.println("------>请求信息:"+ new String(bytes));
OutputStream outputStream = accept.getOutputStream();
accept.close();
}
执行,发送请求 localhost:8080
发现后台打印的是这样:
这么复杂?其实我们要关注的就只是第一行而已:
没错,这里的GET代表请求方式是GET请求,第二个斜杠“/”代表的就是访问路径了,因为我们请求地址后边什么都没有,所以这里就只有一个“/”,不信,你可以试试请求localhost:8080/index.html 看看后台打印出来的是不是 /index.html 请自行实验~
所以,接下来,我们对请求信息进行解析,封装成一个我们需要的对象:
Request request = new Request(inputStream);
这样把请求url封装到request对象里,需要的时候取就可以了。
Request类的定义如下
/**
* 把请求信息封装为Request对象(根据InputSteam输入流封装)
*/
public class Request {
private String method; // 请求方式,比如GET/POST
private String url; // 例如 /,/index.html
private InputStream inputStream; // 输入流,其他属性从输入流中解析出来
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public Request() {
}
// 构造器,输入流传入
public Request(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
// 从输入流中获取请求信息
int count = 0;
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes