总结下模拟Tomcat

java入门--模拟实现一个tomcat服务器 为了让新同学深入的理解tomcat的运行原理,动手实现一个简单的tomcat服务器。文章比较长,而且需要跟着动手练习,才能有所收获。思路模型图:项目结构图:web.xml映射文件中的内容,用于定义URL请求映射路径和java类之间的对应关系 2 Entity实体类的处理 3 Mapping映射类的处理 4 DOM4j解析xml映射文件 导入DOM4J jar包,帮助我们解析web.xml文件 5处理ServletContext类 全局容器,目前用于存储映射关系 6 编写Web 阅读详情

前言

最近重新复习了一遍tomcat的简单模拟,为啥是简单的呢?只能实现一些简单的静态页面及完成简单的登录功能。不过登录功能那块并没有展示到这篇博客中。

先了解下HTTP吧

在模拟之前还是有必要看看HTTP的,虽然也涉及到Socket的知识,但是我就当你们都会哈哈,真的不会的话也可以看看我的那篇:利用Java的Socket网络编程实现小型聊天室。

HTTP协议概述

协议是干啥的?协议就是一种约定的规则。网络协议呢?网络协议就是数据在网站上传输的规则比如http、pop3、pop、imap、ftp、流媒体协议等。而HTTP协议的全称是htyper text transform protocal即超文本传输协议:如何在互联网上传输超文本,而超文本是啥呢?超文本就是HTML(超文本标记语言 htyper text markup language)页面。

组成部分

HTTP协议分为请求部分,相应部分。

请求部分

先看下例子(访问我的一篇转载博客时的例子,只截取了少部分):
在这里插入图片描述

我复制出来是这样(截取了少部分):

Host: blog.youkuaiyun.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Referer: https://editor.youkuaiyun.com/md?articleId=105137409
Upgrade-Insecure-Requests: 1
Connection: keep-alive
Cookie: uuid_tt_dd=10_20854976790-1584620675004-265380; 
Cache-Control: max-age=0
...

这个是请求头部分,还有请求行和请求体。请求行可以看这个:
在这里插入图片描述

这个不是请求行,但是将请求行中数据这样显示出来了。总的来说,请求部分的示例如下:

// 这个是请求行,末尾是\n
GET /CodingNO1/article/details/105137409 HTTP/2
// 接下来是请求头,末尾是\n,最后一行的末尾是\n\n
Host: blog.youkuaiyun.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Referer: https://editor.youkuaiyun.com/md?articleId=105137409
Upgrade-Insecure-Requests: 1
Connection: keep-alive
Cookie: uuid_tt_dd=10_20854976790-1584620675004-265380; 
Cache-Control: max-age=0
// 最后是请求体(注意:请求头和请求体之间有个空行,这也是为什么请求头最后一行是两个换行符)
// 如果是get请求,则请求体是空的,post则请求体中有数据




第一部分是请求行,用来说明请求类型,要访问的资源(GET请求的数据就是在资源路径名后)以及所使用的HTTP版本。
第二部分是请求头部,用来说明服务器要使用的附加信息
第三部分是空行,请求头部后面的空行是必须的,无论第四部分的请求数据是否为空。
第四部分是请求数据也交请求体,可以添加任意长度的其他数据

响应部分

响应部分和请求部分类似,也分为响应行,响应头,响应体。
直接看例子吧:

HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8

<html>
      <head></head>
      <body>
            <!--body goes here-->
      </body>
</html>

第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
第二部分:消息报头,用来说明客户端要使用的一些附加信息
第三部分:空行,消息报头后面的空行是必须的
第四部分:响应正文,服务器返回给客户端的文本信息。空行后面的html部分为响应正文。

Tomcat的模拟

第一个版本

这个版本代码没有进行封装,全弄到一个类中了,且只能发送静态页面。

package com.yuer.mytomcat;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 模拟服务端
 * 这个版本只能发送静态文件
 * 
 * @author Yuer
 *
 */
public class TestServer {

	// 代表静态资源路径
	private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "src\\main\\webapp";

	// 代表请求的文件路径
	private static String url = "";

	public static void main(String[] args) throws IOException {
		ServerSocket serverSocket = null;
		Socket socket = null;
		OutputStream ops = null;
		InputStream is = null;

		try {
			// 第一步,创建ServerSocket对象,监听本机的8080端口号
			serverSocket = new ServerSocket(8080);

			while (true) {
				// 等待来自客户端的请求和获取客户端对象对应的Socket对象
				socket = serverSocket.accept();

				// 通过获取到的Socket对象获取输出流对象和输入流对象
				ops = socket.getOutputStream();
				is = socket.getInputStream();

				// 获取请求体中的文件路径并赋值给url
				parse(is);

				// 发送静态资源给客户端
				sendStatic(ops);

			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			if (null != ops) {
				ops.close();
				ops = null;

			}
			// 有可能是这里的输入没有关闭导致请求一直出问题
			if (null != is) {
				is.close();
				is = null;
			}
			if (null != socket) {
				socket.close();
				socket = null;
			}
		}

	}

	/**
	 * 获取请求部分中的文件路径并赋值给url
	 * 
	 * @param is
	 * @throws IOException
	 */
	private static void parse(InputStream is) throws IOException {
		// 这里读取完的时间是在游览器停止后会导致报错
//		int i = 0;
//		StringBuffer temp = new StringBuffer();
//
//		while ((i = is.read()) != -1) {
//			temp.append((char) i);
//		}
		StringBuffer content = new StringBuffer();

		byte[] buffer = new byte[2048];

		int i = -1;

		i = is.read(buffer);

		for (int j = 0; j < i; j++) {
			content.append((char) buffer[j]);
		}

		System.out.println(content.toString());

		// 接下来进行请求部分的解析
		parseUrl(content.toString());

	}

	/**
	 * 将请求部分中的请求行的资源路径解析出来
	 * 
	 * @param temp
	 */
	private static void parseUrl(String temp) {
		String str = "";
		str = temp.substring(temp.indexOf(" ") + 1);
		url = str.substring(0, str.indexOf(" "));

		System.out.println(url);
	}

	/**
	 * 发送静态资源给客户端
	 * 
	 * @param ops
	 * @throws IOException
	 */
	private static void sendStatic(OutputStream ops) {
		FileInputStream fis = null;
		File file = new File(WEB_ROOT + url);
		try {

			if (file.exists()) {
				// 响应行
				ops.write("HTTP/1.1 200 OK\n".getBytes());
				// 响应头
				ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
				ops.write("Server:Yuer\n".getBytes());
				// 响应体上的换行
				ops.write("\n".getBytes());

				int i = 0;
				StringBuffer buff = new StringBuffer();
				fis = new FileInputStream(file);

				while ((i = fis.read()) != -1) {
					buff.append((char) i);
				}

				// 响应体
				ops.write(buff.toString().getBytes());

			} else {
				// 响应行
				ops.write("HTTP/1.1 404 NotFound\n".getBytes());
				// 响应头
				ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
				ops.write("Server:Yuer\n".getBytes());
				// 响应体上的换行
				ops.write("\n".getBytes());

				ops.write("file not found".getBytes());
			}

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (null != fis) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}
	}

}

注意:这里有个bug,可能需要说下,对于parse()方法:

private static void parse(InputStream is) throws IOException {
		// 这里读取完的时间是在游览器停止后会导致报错
//		int i = 0;
//		StringBuffer temp = new StringBuffer();
//
//		while ((i = is.read()) != -1) {
//			temp.append((char) i);
//		}
		StringBuffer content = new StringBuffer();

		byte[] buffer = new byte[2048];

		int i = -1;

		i = is.read(buffer);

		for (int j = 0; j < i; j++) {
			content.append((char) buffer[j]);
		}

		System.out.println(content.toString());

		// 接下来进行请求部分的解析
		parseUrl(content.toString());

	}

parse()方法注释的那几行,是我第一次写时用的读取请求部分信息的IO方式,但是由于这个while()中的判断问题,导致出现游览器一直在发请求,但是收不到回复,如果手动关闭请求后,服务端报错:
在这里插入图片描述这里原因是判断有问题,所以有两个解决方式(都不太完美,这是在没有考虑请求体中有数据的前提):
第一个。不用判断即直接使用一个固定长度的字节数组去一次性读取完数据就没问题了,这里为什么不怕没有读取完呢?一般来说,请求部分数据不会超过2KB,所以这里使用长度为2048的字节数组。
第二个。使用判断,这种情况就不能继续使用InputStream,可以使用字符流的判断。

String line = "";
while((line=read.readLine()) != null) {
	if ("".equals(line)) { // 说明读到空行了
		break;
	}
	//... 其他处理
}

这种是使用的判断每一行,读到空行代表请求头以及读完了,对于不利用模拟Tomcat进行数据交互的情况下基本够用。

注意:这两种方式都是没有考虑到请求体中有数据,如果请求体中大量数据,则第一种就有可能超过2KB的数据量,第二种根本不会读取这些数据信息。如果有更好的解决方式,请联系我一下,感激不尽-.-

第二个版本

这里通过读取配置文件中的请求信息进行反射获取Servlet(自定义的),实现了一定程度上的动态哈哈。
配置文件:
在这里插入图片描述先看下Servlet接口:

package com.yuer.mytomcat.dymatic;

import java.io.InputStream;
import java.io.OutputStream;

public interface Servlet {
	
	
	public void init();
	
	public void service(InputStream is, OutputStream ops);
	
	public void destroy();
	
	

}

两个实现类:
AA:

package com.yuer.mytomcat.dymatic;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class AAServlet implements Servlet {

	public void init() {
		// TODO Auto-generated method stub
		
	}

	public void service(InputStream is, OutputStream ops) {
		try {
			ops.write("我是AA Servlet".getBytes());
			ops.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	public void destroy() {
		// TODO Auto-generated method stub
		
	}

}

BB:

package com.yuer.mytomcat.dymatic;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class BBServlet implements Servlet {

	public void init() {
		// TODO Auto-generated method stub
		
	}

	public void service(InputStream is, OutputStream ops) {
		try {
			ops.write("我是BB Servlet".getBytes());
			// 这里不确定是否要刷新
			ops.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	public void destroy() {
		// TODO Auto-generated method stub
		
	}

}

服务端:
package com.yuer.mytomcat.dymatic;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * 模拟服务端
 * 这个版本可以发送静态和动态,下个版本进行一些封装
 * 
 * @author Yuer
 *
 */
@SuppressWarnings("rawtypes")
public class Server {

	// 代表静态资源路径
	private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "src\\main\\webapp";

	// 代表请求的文件路径
	private static String url = "";

	// 还要将配置文件中的信息读取到map中
	private static Map<String, String> map = new HashMap<String, String>();

	// 服务器启动之前初始化配置信息到map中
	static {
		// 使用javaAPI读取配置文件信息
		Properties prop = new Properties();

		try {
			// 先加载
			prop.load(new FileInputStream(WEB_ROOT + "\\conf.properties"));

			// 将配置文件中的数据读取到map中、
			Set set = prop.keySet();
			Iterator iterator = set.iterator();
			while (iterator.hasNext()) {
				String key = (String) iterator.next();
				String value = prop.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对象,监听本机的8080端口号
			serverSocket = new ServerSocket(8080);

			while (true) {
				// 等待来自客户端的请求和获取客户端对象对应的Socket对象
				socket = serverSocket.accept();

				// 通过获取到的Socket对象获取输出流对象和输入流对象
				ops = socket.getOutputStream();
				is = socket.getInputStream();

				// 获取请求体中的文件路径并赋值给url
				parse(is);

				// 先判断是否是/或没有 则直接访问web.xml中的欢迎页
				// 这里还没做。。。。
				

				// 判断本次请求的是静态页面还是servlet程序
				if (null != url) {
					// 看是否存在. 即是否访问静态
					if (url.indexOf(".") != -1) {
						// 发送静态资源给客户端
						sendStatic(ops);
					} else {
						// 发送动态的
						sendDynamic(is, ops);
						
					}
				}
				
				// 关闭会立马释放资源,设置为null,只是说明我不再指向这个对象
				socket.close();
//				socket = null;

				

			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			if (null != ops) {
				ops.close();
				ops = null;

			}
			// 有可能是这里的输入没有关闭导致请求一直出问题
			if (null != is) {
				is.close();
				is = null;
			}
			if (null != socket) {
				socket.close();
				socket = null;
			}
		}

	}

	

	/**
	 * 获取请求部分中的文件路径并赋值给url
	 * 
	 * @param is
	 * @throws IOException
	 */
	private static void parse(InputStream is) throws IOException {

		StringBuffer content = new StringBuffer();

		byte[] buffer = new byte[2048];

		int i = -1;

		i = is.read(buffer);

		for (int j = 0; j < i; j++) {
			content.append((char) buffer[j]);
		}

		// 做测试
		System.out.println(content.toString());

		// 接下来进行请求部分的解析
		parseUrl(content.toString());

	}

	/**
	 * 将请求部分中的请求行的资源路径解析出来
	 * 
	 * @param temp
	 */
	private static void parseUrl(String temp) {
		String str = "";
		// 这里没考虑啥都没有的情况,即localhost:8080 或local host:8080/(游览器自动省略/)
		str = temp.substring(temp.indexOf(" ") + 2);
		url = str.substring(0, str.indexOf(" "));

		// 做测试
		System.out.println(url);
		System.out.println(url + "-----");
	}
	
	/**
	 * 发送动态资源
	 * @param ops
	 * @throws IOException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws ClassNotFoundException 
	 */
	private static void sendDynamic(InputStream is, OutputStream ops) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
		
		// 接下来判断这次请求的url是否存在于map中
		if (map.containsKey(url)) {
			
			// 先发送响应行和响应头
			// 响应行
			ops.write("HTTP/1.1 200 OK\n".getBytes());
			// 响应头
			ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
			ops.write("Server:Yuer\n".getBytes());
			// 响应体上的换行
			ops.write("\n".getBytes());
			
			// 如果包含指定的key,获取到map中key对应的value部分
			String value = map.get(url);
			
			// 通过反射调用对应的Servlet 这里后面通过web.xml或者注解
			Class clazz = Class.forName(value);
			Servlet servlet = (Servlet) clazz.newInstance();
		
			// 执行init方法
			servlet.init();
			// 执行service
			servlet.service(is, ops);
			// 执行销毁 销毁不知道是在什么时候
//			servlet.destroy();
			
		} else {
			// 响应行
			ops.write("HTTP/1.1 404 NotFound\n".getBytes());
			// 响应头
			ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
			ops.write("Server:Yuer\n".getBytes());
			// 响应体上的换行
			ops.write("\n".getBytes());

			ops.write("reuqest is bad".getBytes());
		}
		
	}
	

	/**
	 * 发送静态资源给客户端
	 * 
	 * @param ops
	 * @throws IOException
	 */
	private static void sendStatic(OutputStream ops) {
		FileInputStream fis = null;
		File file = new File(WEB_ROOT,url);
		try {

			if (file.exists()) {
				// 响应行
				ops.write("HTTP/1.1 200 OK\n".getBytes());
				// 响应头
				ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
				ops.write("Server:Yuer\n".getBytes());
				// 响应体上的换行
				ops.write("\n".getBytes());

				int i = 0;
				StringBuffer buff = new StringBuffer();
				fis = new FileInputStream(file);

				while ((i = fis.read()) != -1) {
					buff.append((char) i);
				}

				// 响应体
				ops.write(buff.toString().getBytes());
				

			} else {
				// 响应行
				ops.write("HTTP/1.1 404 NotFound\n".getBytes());
				// 响应头
				ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
				ops.write("Server:Yuer\n".getBytes());
				// 响应体上的换行
				ops.write("\n".getBytes());

				ops.write("file not found".getBytes());
			}
			
			ops.flush();

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (null != fis) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

		}
	}

}

第三版本

这里对每个逻辑都进行了封装,将请求路径对应的配置信息弄到类似于my.xml中了,还有默认访问的界面也写在配置文件my.xml中了。且将每一个客户端即游览器访问的请求封装为一个线程了,再在服务端使用了线程池进行了管理。

相关文件

my.xml

<?xml version="1.0" encoding="UTF-8"?>


<web-app>
	<servlet-mapping>
		<servlet-name>AAServlet</servlet-name>
		<servlet-class>com.yuer.mytomcat.dymatic.pro.AAServlet</servlet-class>
		<url-pattern>aa</url-pattern>
	</servlet-mapping>


	<servlet-mapping>
		<servlet-name>BBServlet</servlet-name>
		<servlet-class>com.yuer.mytomcat.dymatic.pro.BBServlet</servlet-class>
		<url-pattern>bb</url-pattern>
	</servlet-mapping>





	<welcome-file-list>
		<welcome-file>demo1.html</welcome-file>
	</welcome-file-list>

</web-app>

application.propeties(设置端口之类的信息):

#配置端口
port=8080




处理properties和xml配置文件的两个类:

parseXml.java:

package com.yuer.mytomcat.dymatic.pro;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

/**
 * 这里使用jsoup解析xml
 * 
 * @author Yuer
 *
 */
public class ParseXml {

	// 还要将配置文件中的信息读取到map中
	private static Map<String, String> map = new HashMap<String, String>();
	
	// 这里将配置文件中的欢迎页读取出来
	private static String welcome = "";

	
	// 使用之前就初始化好了
	static {
		Document doc;
		Elements as;
		
		try {
			doc = Jsoup.parse(new File("src/main/webapp/my.xml"), "utf-8");
			
			// 先将欢迎页初始化
			Element ele = doc.getElementsByTag("welcome-file").first();
			welcome = ele.text();
			
			// 再将请求与类路径进行映射
			// 获取Elements的方式有很多种:根据id,class,tag,属性(好像是看是否存在这个属性)都可
			as = doc.getElementsByTag("servlet-mapping");
			for (Element e : as) {
				// 这个是请求路径
				Element a = e.child(2);
				Element b = e.child(1);
				map.put(a.text(), b.text());

			}
		} catch (IOException e1) {
			e1.printStackTrace();
		}

		
	}

	public static Map<String, String> getMap() {
		return map;
	}

	public static void setMap(Map<String, String> map) {
		ParseXml.map = map;
	}

	public static String getWelcome() {
		return welcome;
	}

	public static void setWelcome(String welcome) {
		ParseXml.welcome = welcome;
	}

}

ReadConfig:

package com.yuer.mytomcat.dymatic.pro;


import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 读取properties配置文件
 * @author Yuer
 *
 */
public class ReadConfig extends Properties{
	private static final long serialVersionUID = 4896442440078075125L;
	private static ReadConfig instance = new ReadConfig();
	
	private ReadConfig() {
		try(InputStream is = this.getClass().getClassLoader().getResourceAsStream("application.properties");) {
			this.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static ReadConfig getInstance() {
		return instance;
	}
}


Servlet

Servlet和第二版本的一致,就不重复粘贴了。

Request
package com.yuer.mytomcat.dymatic.pro;

import java.io.IOException;
import java.io.InputStream;

/**
 * 获取HTTP的请求头所有信息并截取URL地址返回
 * 
 * @author Yuer
 *
 */
public class Request {

	// 代表请求的文件路径
	private  String url = "";
	
	private InputStream is = null;
	
	public Request() {
		
	}
	
	public Request(InputStream is) {
		this.is = is;
		try {
			parse();//解析请求
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
	
	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public InputStream getIs() {
		return is;
	}

	public void setIs(InputStream is) {
		this.is = is;
	}

	/**
	 * 获取请求部分中的文件路径并赋值给url
	 * 
	 * @param is
	 * @throws IOException
	 */
	public  void parse() throws IOException {
		// 这里读取完的时间是在游览器停止后会导致报错
		// 这里read是返回下一个字节,而read(byte[] b)则是返回读入缓冲区b的总字节数
//		int i = 0;
//		StringBuffer temp = new StringBuffer();
//
//		while ((i = is.read()) != -1) {
//			temp.append((char) i);
//		}
		
		/*OutputStream out = new BufferedOutputStream(response.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while ((len = in.read(bytes)) != -1) {
            out.write(bytes, 0, len);
        }
        in.close();
        out.flush();
        out.close();*/
		StringBuffer content = new StringBuffer();

		// 这里为什么是2048且一次性读取完(不怕文件大读不完?这里不是读文件而是请求信息)
		// get 是通过URL提交数据,因此GET可提交的数据量就跟URL所能达到的最大长度有直接关系。很多文章都说GET方式提交的数据最多只能是1024字节,而 实际上,URL不存在参数上限的问题,HTTP协议规范也没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制 是2083字节(2K+35字节)。对于其他浏览器,如FireFox,Netscape等,则没有长度限制,
		// 这个时候其限制取决于服务器的操作系统。即 如果url太长,服务器可能会因为安全方面的设置从而拒绝请求或者发生不完整的数据请求。
		// 所以一般而言请求体的信息2048应该够了,直接一次读取即可
		byte[] buffer = new byte[2048];

		int i = -1;

		i = is.read(buffer);

		for (int j = 0; j < i; j++) {
			content.append((char) buffer[j]);
		}

		// 做测试
		System.out.println(content.toString());

		// 接下来进行请求部分的解析
		parseUrl(content.toString());

	}
	
	
	/**
	 * 将请求部分中的请求行的资源路径解析出来
	 * 
	 * @param temp
	 */
	private  void parseUrl(String temp) {
		String str = "";
		// 这里没考虑啥都没有的情况,即localhost:8080 或local host:8080/(游览器自动省略/)
		str = temp.substring(temp.indexOf(" ") + 2);
		url = str.substring(0, str.indexOf(" "));

		// 做测试
		System.out.println(url);
		System.out.println(url + "-----");
	}
}

Response
package com.yuer.mytomcat.dymatic.pro;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import com.yuer.mytomcat.util.CloseAll;

/**
 * 响应请求读取文件并写回到浏览器
 * 
 * @author Yuer
 *
 */
@SuppressWarnings("rawtypes")
public class Response {

	private OutputStream ops = null;

	private Request request = null;
	// 代表静态资源路径
	private static String WEB_ROOT = System.getProperty("user.dir") + "\\" + "src\\main\\webapp";

	// 还要将配置文件中的信息读取到map中
	private  Map<String, String> map = new HashMap<String, String>();
	

	public Response() {

	}

	public Response(Request request, OutputStream ops) {
		this.request = request;
		this.ops = ops;
		this.map = ParseXml.getMap();
	}

	public Request getRequest() {
		return request;
	}

	public void setRequest(Request request) {
		this.request = request;
	}

	public OutputStream getOps() {
		return ops;
	}

	public void setOps(OutputStream ops) {
		this.ops = ops;
	}
	
	
	/**
	 * 重定向
	 * @throws IOException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws ClassNotFoundException 
	 */
	public void sendRedirect() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {
		
		String url = request.getUrl();
		// 先判断是否是/或没有 则直接访问my.xml中的欢迎页
		if (url == null || "".equals(url)) {
			sendStatic(true);
		} else { // 判断本次请求的是静态页面还是servlet程序
			// 看是否存在. 即是否访问静态
			if (url.indexOf(".") != -1) {
				// 发送静态资源给客户端
				sendStatic(false);
			} else {
				// 发送动态的
				sendDynamic();
				
			}
		}
		
	}

	/**
	 * 发送动态资源
	 * 
	 * @param ops
	 * @throws IOException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 * @throws ClassNotFoundException
	 */
	public void sendDynamic()
			throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {

		String url = request.getUrl();

		// 接下来判断这次请求的url是否存在于map中
		if (map.containsKey(url)) {

			// 先发送响应行和响应头
			// 响应行
			ops.write("HTTP/1.1 200 OK\n".getBytes());
			// 响应头
			ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
			ops.write("Server:Yuer\n".getBytes());
			// 响应体上的换行(响应头或请求头到后面的数据部分数据之前必须有空的一行,所以这里加了一个\n表示一个空行)
			ops.write("\n".getBytes());

			// 如果包含指定的key,获取到map中key对应的value部分
			String value = map.get(url);

			// 通过反射调用对应的Servlet 这里后面通过web.xml或者注解
			Class clazz = Class.forName(value);
			Servlet servlet = (Servlet) clazz.newInstance();

			// 执行init方法
			servlet.init();
			// 执行service
			servlet.service(request, this);
			// 执行销毁 销毁不知道是在什么时候
//			servlet.destroy();

		} else {
			// 响应行
			ops.write("HTTP/1.1 404 NotFound\n".getBytes());
			// 响应头
			ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
			ops.write("Server:Yuer\n".getBytes());
			// 响应体上的换行
			ops.write("\n".getBytes());

			ops.write("reuqest is bad".getBytes());
		}

	}

	
	/**
	 * 发送静态资源给客户端
	 * @param flag 代表是否是默认
	 */
	public void sendStatic(Boolean flag) {
		String url = "";
		// 是默认即地址栏啥都没写
		if (flag) {
			// 读取欢迎页
			url = ParseXml.getWelcome();
		} else {
			url = request.getUrl();
		}

		FileInputStream fis = null;
		File file = new File(WEB_ROOT, url);
		try {

			if (file.exists()) {
				// 响应行
				ops.write("HTTP/1.1 200 OK\n".getBytes());
				// 响应头
				ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
				ops.write("Server:Yuer\n".getBytes());
				// 响应体上的换行
				ops.write("\n".getBytes());

				int i = 0;
				StringBuffer buff = new StringBuffer();
				fis = new FileInputStream(file);

				while ((i = fis.read()) != -1) {
					buff.append((char) i);
				}

				// 响应体
				ops.write(buff.toString().getBytes());

			} else {
				// 响应行
				ops.write("HTTP/1.1 404 NotFound\n".getBytes());
				// 响应头
				ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
				ops.write("Server:Yuer\n".getBytes());
				// 响应体上的换行
				ops.write("\n".getBytes());

				ops.write("file not found".getBytes());
			}

			// 这里不确定是否要刷新
			ops.flush();

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			CloseAll.close(fis);
		}
	}
	

}

HttpSession
package com.yuer.mytomcat.dymatic.pro;

import java.net.Socket;

/**
 * 用来作为线程封装每个游览器访问的动作
 * @author Yuer
 *
 */
public class HttpSession  implements Runnable{
	
	private Socket socket = null;
	
	
	public HttpSession(Socket socket) {
		this.socket = socket;
	}

	@Override
	public void run() {
		Request request;
		Response response;
		try {
			request = new Request(socket.getInputStream());
			response = new Response(request, socket.getOutputStream());
			response.sendRedirect();
			socket.close();
		} catch (Exception e) {
			e.printStackTrace();
		} 
		
		
	}

}

Server
package com.yuer.mytomcat.dymatic.pro;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.yuer.mytomcat.util.CloseAll;

/**
 * 模拟服务端
 * 这个版本可以发送静态和动态,并将一些操作封装到request和response
 * 
 * @author Yuer
 *
 */
public class Server {


	public static void main(String[] args) throws IOException {
		new Server().start();
	}
	
	
	private void start() {
		ServerSocket serverSocket = null;
		Socket socket = null;

		try {
			Integer port = Integer.parseInt(ReadConfig.getInstance().getProperty("port"));
			// 第一步,创建ServerSocket对象
			serverSocket = new ServerSocket(port);
			
			//创建一个线程池,大小为20
			ExecutorService service = Executors.newFixedThreadPool(20);

			while (true) {
				// 等待来自客户端的请求和获取客户端对象对应的Socket对象
				socket = serverSocket.accept();

				service.execute(new HttpSession(socket));
				
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			CloseAll.close(socket);
		}

	}

	

	

	
	
	

}

模拟tomcat接收GET、POST请求 模拟实现Tomcat,包含核心类和工作流程。关键类包括:MyServer(服务器主类)、MyRequest/MyResponse(请求/响应处理)、MyMapping(URL映射)以及MyHttpServlet/MyServlet(Servlet实现)。当访问http://localhost:10086/mytomcat时,服务器会解析请求,通过映射找到对应Servlet处理并返回响应。该项目展示了Web服务器的基本工作原理,包括请求解析、Servlet调用和响应构建等核心机制。 阅读详情

相关推荐

模拟Tomcat服务器

用Eclipse,编写代码熟悉和模拟Tomcat服务器。希望给你们带来帮助,不对的地方,请指正,大家共同进步。

Java学习之仿写Tomcat

这样的设计有一个问题就是:服务器每次处理来自客户端的数据都必须新建一个线程,如果客户端B一直连接但不是发送数据,那么相应的子线程就会一直处于阻塞状态。因为这是一个BIO,所以需要在服务端用while来防止单个客户端阻塞线程我们应该以byte[]数组的方式收发数据而不是用bit数据流是因为byte[]数组有规整的长度(8bit)分割便于解析,而bit是无法分割的。

m0_48829811的博客 577

使用JAVA开发类似Tomcat的服务器

使用JAVA开发类似Tomcat的服务器 有Tomcat的部分功能 实现基本的报文接受 和 响应

java手写简易Tomcat服务器+Servlet(思路流程图+超详细注释)一步步实现,简单易懂最后有源码和视频讲解

手动实现 Tomcat 底层机制+ 自己设计 Servlet。模拟Tomcat底层实现并能调用我们自己设计的Servlet实现一个简单的小案例。一步步循序渐进,图解+分析+代码分三个阶段来实现。源码和视频在最后

luohanisme的博客 2418

Tomcat服务器模拟(TCP/IP)

模拟Tomcat应用服务器的工作原理,有利于理解服务器的工作的模式。简单模拟,不考虑并发性等等因素。

模拟tomcat部署项目

模拟tomcat部署项目 博客中有介绍。

tomcat原理模拟和tomcat优化

servlet 没有主方法main,依赖tomcat才能运行,因为tomcat 有主方法main,由java编写servlet中doGet和doPost方法属于非静态方法,只能依托new对象存在,tomcat无法new出来对象,因此tomcat无法事先知道他们的存在。而任何语言都可以通过类的所在的路径或目录获取类信息,去某个目录下遍历所有子文件,能够获取所有文件的路径信息。tomcat可以通过servlet注解,找到对应的类,servlet注解相当于给类加了标记。

weixin_47480200的博客 1322

使用 VPN 访问太理内部网相关问题总结【太原理工大学】

电脑连接 VPN,访问校园内部网相关问题总结,太原理工大学。

m0_52861684的博客 9878

使用java模拟简单的tomcat

使用java模拟最简单的tomcat,让tomcat不再那么神秘

秃头披风侠的博客 886

RK3588多路MIPI摄像头采集与WiFi低延迟传输实战指南

本文详细介绍了基于RK3588平台实现多路MIPI摄像头同步采集并通过WiFi进行低延迟传输的完整实战方案。文章深入剖析了从V4L2驱动配置、多线程采集、环形缓冲区设计到UDP网络传输优化的核心技术,旨在帮助开发者在工业检测、智能安防等场景下构建高并发、高实时性的视频流系统,实现端到端延迟控制在100毫秒以内的目标。

tensor9flow的博客 787

Windows下Python调用D435i相机避坑指南:从SDK版本选择到RealSense Viewer报错解决

本文详细介绍了在Windows环境下使用Python调用Intel D435i相机的完整避坑指南。重点解决了SDK版本选择问题,推荐使用稳定的Intel RealSense SDK 2.54.2版本,并提供了从环境配置、常见报错解决到实际应用案例的全流程实践指导,帮助开发者高效完成相机部署与开发。

palm99的专栏 924

手写模拟一个tomcat

通过自己手写模拟tomcat的核心功能,能够让我们更好的理解tomcat的运行机制,底层也是通过jdk提供的api进一步步封装实现而来,我们作为使用者而言,可能更多关注使用上的方便、敏捷性,通过手写源码,相信我们会对tomcat有更加深刻的理解。

qq_36481984的博客 664

由浅入深的详细解读汽车PDU与BDU

本文详细解读新能源汽车的PDU高压配电单元和BDU电池包断路单元,涉及电气件参数设计、选型、结构设计、电气安全、散热设计、继电器控制与诊断,提供汽车电子知识和技术解析。

让你爱上电路设计 1万+

android模拟器和本机tomcat之间模拟从后台取数据

一、从tomcat里取到资源安装好tomcat,配置好环境变量,在tomcat的bin文件夹中,点击startup.bat启动tomcat,打开浏览器,搜索http://localhost:8080/,如果出现下图所示,则启动成功。把资源放在tomcat的webapps文件夹里的ROOT文件夹中。之后直接搜索http://localhost:8080/mine_cache_one.png等等就可以了

费..的博客 1775

Close-By Site Assignment,门店近邻关系在 SAP Retail 物料搜索里的真实价值

SAP零售模块中的"Close-By Site Assignment"功能常被低估,但它在门店现场业务中极为关键。该功能允许预先定义各门店间的近邻关系,当导购或店长查询商品时,系统会自动展示附近门店的库存、价格和促销信息。与单纯的地理距离不同,这种近邻关系可基于调拨频率、业务关联度等业务逻辑定义。维护入口在事务码WSD_CBP中,支持按应用场景(如销售、采购)分别设置不同近邻关系。该功能不同于组织结构,专注于增强搜索体验,特别适合多门店运营场景,能有效提升跨店查询效率。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 4852

STM32F4 SPI FLASH(W25Q128)驱动详解(含DMA+完整读写擦除+低功耗)

本文实现了一款基于STM32 HAL库的SPI Flash驱动,以W25Q128为例,提供了完整的初始化、读写、擦除、ID读取等功能。驱动集成了DMA传输、超时保护、页对齐写入等特性,支持扇区擦除、整片擦除、页写入及跨页缓冲写入。代码结构清晰,易于移植到其他SPI Flash型号(如W25Q64、W25Q16等),适用于数据存储、固件升级、文件系统等嵌入式场景。

kevin1499的博客 96

安卓模拟器访问不了Tomcat

导致安卓模拟器访问不了Tomcat,主要是因为android模拟器本身的IP地址就是127.0.0.1或者localhost了,而在Tomcat中默认的host就是电脑的localhost,所以最好的方法就是将Tomcat的server.xml文件中的host配置成电脑的真实IP地址。 1、找到自己的IP地址 1) 2) 3) 4) 2、找到{Tomcat

xyllp的博客 2748

【100分】【等和子数组最小和】

逆序排序,这样能够使得在分组时优先选取较大的元素,以便将元素分配到较小的子集中,从而使得每个子集的和相对均衡。给定一个数组nums,将元素分为若干个组,使得每组和相等,求出满足条件的所有分组中,组内元素和的最小值。分成若干个组,使得每组的和相等,并求出满足条件的所有分组中,组内元素和的最小值。在递归的过程中,我们需要注意一个优化点,即避免将相同和的桶重复计算。4 个子集(5),(1,4),(2,3),(2,3)2 个子集(5, 1, 4),(2,3, 2,3)个子集,则返回子集和的最小值,如果不能,则将。

weixin_54707168的博客 1168

模拟springboot内嵌tomcat

模拟springboot内嵌tomcat简述导入依赖第一步,编写启动类第二步,实现SpringApplication,内嵌tomcat并启动tomcat第三步,创建WebApplicationInitializer的实现类第四步,创建一个配置类第五步,创建一个controller最终项目结构如下图所示启动项目进行测试 简述 springboot可以打包成jar包部署是由于内嵌了tomcat,现在我们就来模拟下springboot内嵌tomcat。 导入依赖 下面是pom种需要的依赖 <dependen

weixin_48190926的博客 429

RAG系统中Reranker模型选型实战

本文详细介绍了RAG系统中Reranker模型选型实战,希望能对学习RAG的同学们有所帮助。 文章目录 1. 前言 2. 思路解析 3. 测评数据集构建 3.1 步骤一 3.2 步骤二 3.3 数据示例 3.4 总结 4. 实战代码 4.1 本地模型 4.2 使用第三方的Reranker模型API

herosunly的博客 17万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

java使用socket模拟tomcat访问资源

模拟tomcat之前需要知道HTTP协议状态码的一些信息 HTTP协议状态码,是指在HTTP协议运行中由客户端发出请求连接,服务端建立连接;客户端发出HTTP请求(Request),服务端返回响应信息(Respond),而在这个过程中由于客户端或服务端的问题会返回相应的错误代码并显示给用户,对应的错误代码表示不同的错误信息,根据这个信息用户可以调整相应的操作来修改出现的错误,最终避免错误的再现。 ...

jiatai_的博客 720

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

一个简单的Tomcat模拟

简单模拟tomcat Tomcat帮我们实现了网页与服务器之间的信息交互,这里我实现一下在浏览器中输入ip:端口/资源,出现一个网页的。 我们在写Servlet的时候doGet方法中有两个参数request与response,req是接受网页信息,resp是向网页返回数据。现在我就是基于这两个写代码。 import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.F

童_erp的博客 4361

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

android模拟器无法访问本地的tomcat

原文地址:http://blog.sina.com.cn/s/blog_5ef524f50100w5dr.html 对于android初学者,经常会碰到android模拟器无法访问本地的tomcat的问题,网上的解决方案也很多。比如使用android内置的ip 10.0.2.2来访问,或者通过内网ip来访问都是可以的。但是今天我碰到一个问题,用android内置的ip和内网ip都

进击的小巨人 2464

模拟简易 Tomcat

TCP(英语:Transmission Control Protocol,传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议。是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。客户端程序创建一个套接字,并尝试连接服务器的套接字。,该方法将一直等待,直到客户端连接到服务器上给定的端口。主要内容来源以下代码,可自己 debug 试试。模拟的 tomcat,底层使用了。三次握手的目的:保证消息的可靠。,表示通过服务器上的端口通信。提供了两台计算机之间的。

小菜的博客 604

Android模拟器访问tomcat(夜神的看过来)

Android模拟器访问tomcat接口 一般模拟器访问接口将ip和端口由 localhost:8080 或者 127.0.0.1:8080 改为 10.0.2.2:8080 就可以成功访问了 除非端口号改了 不是8080 但是!!!夜神模拟器真是奇怪, 需要将ip改为 电脑的本机IP 如图: 然后就可以成功访问tomcat部署的接口了 查询本机ip:cmd里面输入 ipconfig 找到上面截图那个ipv4就ok ...

木頭子 1200

Genymotion模拟器连接不上本地tomcat的解决方法

当我们启动tomcat,在本地浏览器访问http://localhost:8080/,可以得到正确访问到tomcat主页。 但是,但我们在Genymotion模拟器中的浏览器中输入http://10.0.2.2:8080/去访问tomcat主页时,却不能访问到。 解决方法我们打开模拟器网络配置,选择修改网络 在弹出的窗口中,选择高级选项,将无 改为手动 然后窗口继续被展开,在展开的列表中,设

D先生的博客 724
上一篇: 怎么解决输入流和输出流编码问题?
下一篇: SpringBoot怎样通过地址栏直接访问到静态页面?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值