MiniCat 基本介绍
我们要手写实现一个 Mini 版的 Tomcat,我们要实现的是,作为一个服务器软件,可以通过我们的浏览器客户端发送HTTP请求,Minicat处理之后,处理结果可以返回给浏览器的客户端。
- 提供服务,接受请求(Socket通信)
- 请求信息封装成 Request 对象(Response对象)
- 客户端请求资源,资源分为静态资源(HTML)和动态资源(Servlet)
- 资源返回客户端浏览器
Response 请求响应类
定义了一个名为 Response 的公共类,用于封装向客户端发送数据(如 HTML、文本等)的逻辑。
public class Response {
private OutputStream outputStream;
public Response() {
}
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
public void output(String content) throws IOException {
outputStream.write(content.getBytes());
}
public void outputHtml(String path) throws IOException {
String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
File file = new File(absoluteResourcePath);
if (file.exists() && file.isFile()) {
StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
} else {
output(HttpProtocoUtil.getHttpHeader404());
}
}
}
OutputStream 是 Java I/O 的基类,用于向外部(如客户端的 socket)输出字节数据。它的典型使用场景是在 Web 服务中向浏览器写入响应内容。
Response 类的主要职责包括:
- 封装输出流:把 OutputStream 作为类的核心资源,用于向客户端写数据
- 文本输出:可以直接输出一个字符串到客户端(如返回 HTTP 响应头和正文)
- HTML输出:从文件系统中读取静态 HTML 文件并写回客户端
- 404处理:文件不存在时自动返回标准的 404 响应
Servlet 接口
public interface Servlet {
void init() throws Exception;
void destroy() throws Exception;
void service(Request request, Response response) throws Exception;
}
StaticResourceUtil 静态资源
public class StaticResourceUtil {
public static String getAbsolutePath(String path) {
String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
return absolutePath.replace("\\\\", "/") + path;
}
public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
int count = 0;
while (count == 0) {
count = inputStream.available();
}
int resourceSize = count;
outputStream.write(HttpProtocoUtil.getHttpHeader200(resourceSize).getBytes());
long written = 0;
int byteSize = 1024;
byte[] bytes = new byte[byteSize];
while (written < resourceSize) {
if (written + byteSize > resourceSize) {
byteSize = (int) (resourceSize - written);
bytes = new byte[byteSize];
}
inputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush();
written += byteSize;
}
}
}
获取资源的绝对路径:getAbsolutePath 会获取到当前类所在的类路径(classpath 根目录),获取的是该目录的实际路径,最终将 path 拼接在 classpath 后,返回静态资源的绝对路径。
输出静态资源内容:outputStaticResource 中 inputStream.available() 试图获取文件长度,但是用 while (count == 0) 等待它非 0 的方式是不稳定的。available() 并不等价于”文件大小”,它是”当前可读字节数”,不适合用于预估资源总长度。
整体的小总结:
- 类名:StaticResourceUtil,工具类
- 功能:获取资源路径、输出文件内容到 HTTP
- 亮点:模拟 Web Server 发送 HTML 等静态文件
- 关键问题:误用 available()、不检查读取字节数、频繁分配数组
- 优化方向:用 readAllBytes() 或 while(read != -1) 模式代替原始读取逻辑
WzkServlet 实现类
这里就是对之前的内容的使用,建立了一自己的Servlet,然后启动服务即可!
public class WzkServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) {
String content = "<h1>WzkServlet GET Hello World!</h1>";
try {
response.output(HttpProtocoUtil.getHttpHeader200(content.getBytes().length) + content);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void doPost(Request request, Response response) {
String content = "<h1>WzkServlet POST Hello World!</h1>";
try {
response.output(HttpProtocoUtil.getHttpHeader200(content.getBytes().length) + content);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void init() throws Exception {
}
@Override
public void destroy() throws Exception {
}
}
资源文件
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>wzk-icu</title>
</head>
<body>
<h1>
wzk-icu 测试页面!
</h1>
</body>
</html>
web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<servlet>
<servlet-name>wzkicu</servlet-name>
<servlet-class>icu.wzk.WzkServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>wzkicu</servlet-name>
<url-pattern>/wzkicu</url-pattern>
</servlet-mapping>
</web-app>
启动测试
启动服务后,访问8080端口,可以看到控制台输出了对应的内容,浏览器成功显示了页面。