java一个简单接口的手写,手写一个简易版的tomcat

@[TOC]

手写一个简易版的tomcat

前言

使用tomcat的时候当浏览器输入url之后,开始发送http请求,这个请求发送到哪儿呢,Url解析的过程中

1 先通过域名解析请求得到ip

2 然后通过ip找到对应的主机

3 再通过响应的端口找到进程

4 然后再去根据程序去处理这个请求,再到原路返回

思考

对于1,2步骤我们本地测试可以不用去扣这个,明白这么回事儿就可以,本地localhost实际上对应我们自己本机127.0.0.1

对于第三部,我们本地可以去通过一个socket去监听响应的端口,去获取到请求,然后再响应给客户端让客户端浏览器去解析我们返回的http报文,从而展示数据;

具体如下步骤:

1)提供服务,接收请求(可以使用Socket通信)

2)请求信息封装成Request(Response)

3)客户端请求资源,资源分为静态资源(html)和动态资源(Servlet)

4)资源返回给客户端浏览器

具体实现

首先新建maven工程

3cb93f813cfe

在这里插入图片描述

然后定义一个启动类Bootstrap然后实现一个启动方法start,在这个方法中启动一个socket监听8080端口

package com.udeam.v1;

import com.udeam.util.HttpUtil;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.ServerSocket;

import java.net.Socket;

/**

* 启动类入库

* 用于启动tomcat

*/

public class Bootstrap {

/**

* 监听端口号

* 用于启动socket监听的端口号

*/

private int port = 8080;

/**

* 启动方法

*/

public void start() throws IOException {

//返回固定字符串到客户端

ServerSocket socket = new ServerSocket(port);

System.out.println("--------- start port : " + port);

while (true) {

Socket accept = socket.accept();

//获取输入流

//InputStream inputStream = accept.getInputStream();

//输出流

OutputStream outputStream = accept.getOutputStream();

System.out.println(" ------ 响应返回内容 : " + result);

outputStream.write("hello world ...".getBytes());

outputStream.flush();

outputStream.close();

socket.close();

}

}

public static void main(String[] args) {

try {

new Bootstrap().start();

} catch (IOException e) {

e.printStackTrace();

}

}

}

通过这个Socket返回hello world...给客户端

我们浏览器输入127

可以看到后台代码输出信息

3cb93f813cfe

在这里插入图片描述

前台浏览器显示信息

3cb93f813cfe

在这里插入图片描述

返回信息浏览器不认,响应无效,出现这种情况是浏览器只认识http报文,故此需要包装一个返回浏览器,然后浏览器才能解析

新建一个http包装类HttpUtil包装响应信息给浏览器

这里我们只返回200和404状态的

package com.udeam.util;

/**

* 封装http响应

*/

public class HttpUtil {

/**

* 404 page

*/

private static final String content = "

404 page...

";

/**

* 添加响应头信息

*

* http响应体格式

*

* 响应头(多参数空格换行)

* 换行

* 响应体

*/

public static String addHeadParam(int len) {

String head = "HTTP/1.1 200 OK \n";

head += "Content-Type: text/html; charset=UTF-8 \n";

head += "Content-Length: " + len + " \n" + "\r\n";

return head;

}

/**

* 4040响应

*

* @return

*/

public static String resp_404() {

String head = "HTTP/1.1 404 not found \n";

head += "Content-Type: text/html; charset=UTF-8 \n";

head += "Content-Length: " + content.length() + " \n" + "\r\n";

return head + content;

}

/**

* 200响应

*

* @param content 响应内容

* @return

*/

public static String resp_200(String content) {

return addHeadParam(content.length()) + content;

}

}

然后再请求,可以看到成功返回信息

3cb93f813cfe

在这里插入图片描述

然后我们再去请求一个静态页面index.html

新建一个html页面

Title

hello tomcat....

这次前台请求Url是http://localhost:8080/index.html

还是从Socket进行监听8080端口

请求静态的html,那如何去在后台找到这个资源呢?

通过url也就是/index.html去找到这个文件,后台文件我们去放到resource下

那如何获取url呢?

浏览器在请求后台的时候发送的也是http请求,我们可以获取http请求报文

可以看一下,请求报文

3cb93f813cfe

在这里插入图片描述

从请求头中获取到url以及method等

获取输入流

Socket accept = socket.accept();

//获取输入流

InputStream inputStream = accept.getInputStream();

然后对输入流进行解析,通过解析http请求头第一行得到url和method封装到Request对象中

/**

* 封装的请求实体类

*/

public class Request {

/**

* 请求方式

*/

private String method;

/**

* 请求url

*/

private String url;

/**

* 输入流

*/

public InputStream inputStream;

public Request() {

}

//构造器输入流

public Request(InputStream inputStream) throws IOException {

this.inputStream = inputStream;

//读取请求信息,封装属性

int count = 0;

//读取请求信息

while (count == 0) {

count = inputStream.available();

}

byte[] b = new byte[count];

inputStream.read(b);

String reqStr = new String(b);

System.out.println("请求信息 : " + reqStr);

//根据http请求报文 换行符截取

String[] split = reqStr.split("\\n");

//获取第一行请求头信息

String s = split[0];

//根据空格进行截取请求方式和url

String[] s1 = s.split(" ");

System.out.println("method : " + s1[0]);

System.out.println("url : " + s1[1]);

this.method = s1[0];

this.url = s1[1];

}

//.... get set省略

}

然后根据请求的url从磁盘找到静态资源读取到然后以流的形式返回给浏览器

这里封装返回对象Response

public class Response {

/**

* 响应流

*/

private OutputStream outputStream;

public Response(OutputStream outputStream) {

this.outputStream = outputStream;

}

//输出指定字符串

public void outPutStr(String content) throws IOException {

outputStream.write(content.getBytes());

outputStream.flush();

outputStream.close();

}

}

根据url获取静态资源

public void outPutHtml(String url) throws IOException {

//排除浏览器的/favicon.ico请求

if (("/favicon.ico").equals(url)){

return;

}

//获取静态资源的绝对路径

String abPath = ResourUtil.getStaticPath(url);

//查询静态资源是否存在

File file = new File(abPath);

if (file.exists()) {

//输出静态资源

ResourUtil.readFile(new FileInputStream(abPath), outputStream);

} else {

//404

try {

outPutStr(HttpUtil.resp_404());

} catch (IOException e) {

e.printStackTrace();

}

}

}

ResourUtil 工具类

封装解析读取静态资源

/**

* 静态资源工具类

*/

public class ResourUtil {

/**

* 获取classes文件目录

*/

private static URL url = ResourUtil.class.getClassLoader().getResource("\\\\");

/**

* 获取静态资源文件路径

*

* @param path

* @return

*/

public static String getStaticPath(String path) throws UnsupportedEncodingException {

//获取目录的绝对路径

try {

String decode = URLDecoder.decode(url.getPath(), "UTF-8");

String replace1 = decode.replace("\\", "/");

String replace2 = replace1.replace("//", "");

replace2 = replace2.substring(0,replace2.lastIndexOf("/")) + path;

return replace2;

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

return null;

}

/**

* 读取静态资源文件输入流

*

* @param inputStream

*/

public static void readFile(InputStream inputStream, OutputStream outputStream) throws IOException {

int count = 0;

//读取请求信息

while (count == 0) {

count = inputStream.available();

}

int content = 0;

//读取文件

content = count;

//输出头

outputStream.write(HttpUtil.addHeadParam(content).getBytes());

//输出内容

long written = 0;

int byteSize = 1024;

byte[] b = new byte[byteSize];

//读取

while (written < content) {

if (written + 1024 > content) {

byteSize = (int) (content - written);

b = new byte[byteSize];

}

inputStream.read(b);

outputStream.write(b);

outputStream.flush();

written += byteSize;

}

}

}

socket中完整请求代码

public void start() throws IOException {

//返回固定字符串到客户端

ServerSocket socket = new ServerSocket(port);

System.out.println("--------- start port : " + port);

while (true) {

Socket accept = socket.accept();

//获取输入流

InputStream inputStream = accept.getInputStream();

//封装请求和响应对象

Request request = new Request(inputStream);

Response response = new Response(accept.getOutputStream());

response.outPutHtml(request.getUrl());

}

}

浏览器测试,可以看到正确返回

3cb93f813cfe

在这里插入图片描述

接下来实现定义请求动态资源,具体实现在java web中处理一个请求是使用servlet请求

tomcat处理servlet请求需要实现servlet规范

什么是servlet规范呢?

简单来说就是http请求在接收到请求之后将请求交给Servlet容器来处理,Servlet容器通过Servlet接口来调用不同的业务类,这一整套称作Servlet规范;

接口规范

/**

* 自定义servlet规范

*/

public interface Servlet {

void init() throws Exception;

void destory() throws Exception;

void service(Request request, Response response) throws Exception;

}

实现

/**

* 实现servlet规范

*/

public abstract class HttpServlet implements Servlet {

public abstract void doGet(Request request, Response response);

public abstract void doPost(Request request, Response response);

@Override

public void service(Request request, Response response) throws Exception {

if ("GET".equalsIgnoreCase( request.getMethod()

)) {

doGet(request, response);

} else {

doPost(request, response);

}

}

}

业务请求servlet

/**

* 业务类servelt

*/

public class MyServlet extends HttpServlet {

@Override

public void init() throws Exception {

}

@Override

public void doGet(Request request, Response response) {

//动态业务请求

String content = "

GET 业务请求

";

try {

response.outPutStr(HttpUtil.resp_200(content));

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

public void doPost(Request request, Response response) {

//动态业务请求

String content = "

Post 业务请求

";

try {

response.outPutStr(HttpUtil.resp_200(content));

} catch (IOException e) {

e.printStackTrace();

}

}

@Override

public void destory() throws Exception {

}

}

定义完之后,如何请求呢,如何根据请求Ur去得到相应的servlet

在Java web中我们是在web.xml中进行配置,同样新建web.xml,配置servlet

test

com.udeam.v4.MyServlet

test

/test

解析web.xml文件

讲url和每一个servlet对应起来存储到map中

/**

* 加载解析web.xml,初始化Servlet

*/

private void loadServlet() {

InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");

SAXReader saxReader = new SAXReader();

try {

Document document = saxReader.read(resourceAsStream);

Element rootElement = document.getRootElement();

List selectNodes = rootElement.selectNodes("//servlet");

for (int i = 0; i < selectNodes.size(); i++) {

Element element = selectNodes.get(i);

// test

Element servletnameElement = (Element) element.selectSingleNode("servlet-name");

String servletName = servletnameElement.getStringValue();

Element servletclassElement = (Element) element.selectSingleNode("servlet-class");

String servletClass = servletclassElement.getStringValue();

// 根据servlet-name的值找到url-pattern

Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");

// /test

String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();

servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());

}

} catch (DocumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

}

启动方法

根据url找到servlet去执行service方法

private static final Map servletMap = new HashMap<>();

public void start() throws IOException {

ServerSocket socket = new ServerSocket(port);

System.out.println("--------- start port : " + port);

while (true) {

Socket accept = socket.accept();

//获取输入流

InputStream inputStream = accept.getInputStream();

//封装请求和响应对象

Request request = new Request(inputStream);

Response response = new Response(accept.getOutputStream());

//静态资源

if (request.getUrl().contains(".html")) {

response.outPutHtml(request.getUrl());

} else {

if (!servletMap.containsKey(request.getUrl())) {

response.outPutStr(HttpUtil.resp_200(request.getUrl() + " is not found ... "));

} else {

HttpServlet httpServlet = servletMap.get(request.getUrl());

try {

//处理请求

httpServlet.service(request, response);

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

}

然后请求http://localhost:8080/test可以看到正确返回

3cb93f813cfe

在这里插入图片描述

这里在deget方法中增加睡眠停顿模拟业务请求时间

try {

Thread.sleep(10_000);

} catch (InterruptedException e) {

e.printStackTrace();

}

请求可以可以看到请求阻塞,这是因为同一个socket 当前test这个没有请求结束,第二个请求进来然后阻塞;

必须等到第一个请求结束后才能处理请求

3cb93f813cfe

在这里插入图片描述

然后再请求index.html

3cb93f813cfe

在这里插入图片描述

发现,并不是静态资源并不是立即返回,需要等到test请求结束后才能返回

故此需要对这个进行改造,让彼此请求互不干扰

我们可以使用多线程来进行解决,线程互不干扰,每个请求去执行

在Socket中添加方法

//1 单线程处理

MyThread myThread = new MyThread(httpServlet, response, request);

new Thread(myThread).start();

线程是宝贵的资源,频繁创建和销毁线程对开销很大,故此使用线程池来解决

/**

* 参数可以配置在xml里面

*/

private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 1, TimeUnit.HOURS, new ArrayBlockingQueue<>(500));

//2 线程池执行

threadPoolExecutor.submit(myThread);

threadPoolExecutor.shutdown();

这样子就可以立即返回响应,互不干扰;

简易版的tomcat实现就可以实现了,代码的话没有像tomcat那样子可以将war包解析之类的..

而且代码耦合性也大,tomcat和业务代码在一个Maven中...

说明

分别在指定包下如v1,v2,v3,v4每个代表一个版本

v1 简单的返回指定字符串

v2 返回静态页面

v3 单线程处理servelt请求(多个请求会阻塞)

v4 多线程处理

其中需要用到解析xml依赖

dom4j

dom4j

1.6.1

jaxen

jaxen

1.1.6

代码地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值