深入核心:一步步手撕Tomcat搭建自己的Web服务器
介绍:
servlet:处理 http 请求
tomcat:服务器
Servlet
-
servlet 接口:
- 定义 Servlet 声明周期
- 初始化:init
- 服务:service
- 销毁:destory
- 继承链:
Tomcat
-
Tomcat 和 servlet 原理:
- 浏览器向服务器发送 http 请求
- socket 接收请求,发送给请求解析器
- 请求解析器再解析自己想要的信息
- 请求地址
- 请求方式
- 浏览器类型
- Cookie
- ··········
- 解析器解析到的信息发送给映射器
- 映射器中存放:
- Web 地址
- 内存地址
- 根据请求解析器中解析的信息,找到映射器中相对应的网络地址和内存地址,根据请求方式去访问对应的程序
-
Socket 交互以及解析阶段:
package com.Tomcat; import com.sun.corba.se.spi.activation.Server; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class myTomcat { Request request = new Request(); //创建解析请求的对象 public void startUP() throws IOException { //监听端口号 ServerSocket serverSocket = new ServerSocket(7421); while(true){ Socket socket = serverSocket.accept(); //阻塞监听 System.out.println("有请求!!!!!!!"); //将每个请求开启一个线程 new Thread(new Runnable() { @Override public void run() { try { handler(socket); //调用处理信息方法 }catch (Exception e){ e.printStackTrace(); } } }).start(); } } //处理信息 public void handler(Socket socket) throws IOException { InputStream inputStream = socket.getInputStream(); //将bit流转换为文字信息 int count = 0; while(count == 0){ count = inputStream.available(); //统计输入流的长度 } //打印数据 byte[] bytes = new byte[count]; inputStream.read(bytes); //将bit信息写入到byte数组 String Context = new String(bytes); //将 byte 数组转换为字符串 System.out.println(Context); //输出信息 //拆解字符串,获取想要的信息 String[] list = Context.split("\\n"); //根据换行切割字符串 String Methed = list[0].split(" ")[0]; //在拆分的第一行中以空格再次拆分,获取第一个数据 String Path = list[0].split(" ")[1]; //在拆分的第一行中以空格再次拆分,获取第二个数据 //把截取的数据传给 Request 类 request.setMethod(Methed); request.setPath(Path); } }
-
Request 存储解析信息 ==> 继承 HttpservletRequest,为方便访问同一变量
public class Request implements HttpservletRequast { private String Method; private String Path; public String getMethod() { return Method; } public void setMethod(String method) { Method = method; } public String getPath() { return Path; } public void setPath(String path) { Path = path; } @Override public String toString() { return "Request{" + "Method='" + Method + '\'' + ", Path='" + Path + '\'' + '}'; } }
-
扫包:
/** * 扫描指定包,获取该包下所有的类的全路径信息 */ public class SearchClassUtil { //存放文件的绝对路径 public static List<String> classPaths = new ArrayList<String>(); /** * 扫描固定包下面的类 * @return */ public static List<String> searchClass(){ //需要扫描的包名 String basePack = "com.servlet"; //写需要获取包名的路径 //将获取到的包名转换为路径 //getResource():是获取类对象的方法, "/" :表示在根目录开始 //getPath():是将对象的路径转为字符串 String classPath = SearchClassUtil.class.getResource("/").getPath(); //将包名转换为文件系统路径 ---> 把 "." 替换成 系统的路径分隔符(系统不一样,分隔符也不一样) basePack = basePack.replace(".", File.separator); //把两个路径合并为文件的 绝对路径 String searchPath = classPath + basePack; //调用doPath()方法,把路径写入路径数组中 doPath(new File(searchPath),classPath); //这个时候我们已经得到了指定包下所有的类的绝对路径了。我们现在利用这些绝对路径和java的反射机制得到他们的类对象 return classPaths; } /** * 该方法会得到所有的类,将类的绝对路径写入到classPaths中 * @param file */ private static void doPath(File file,String classpath) { if (file.isDirectory()) {//当前为文件夹 //文件夹我们就递归 ---> 筛出文件夹 File[] files = file.listFiles(); for (File f1 : files) { doPath(f1,classpath); } } else {//标准文件 //标准文件我们就判断是否是class文件 if (file.getName().endsWith(".class")) { //各级拆解字符串,替换分隔符 String path = file.getPath().replace(classpath.replace("/","\\"). replaceFirst("\\\\",""),"").replace("\\","."). replace(".class",""); //如果是class文件我们就放入我们的集合中。 classPaths.add(path); } } } public static void main(String[] args) { List<String> classes = SearchClassUtil.searchClass(); for (String s: classes) { //System.out.println(s); } } }
-
注解:设置文件的访问地址 ==> HashMap 中的 key 值
@Retention(RetentionPolicy.RUNTIME) //在运行期间保留 @Target(ElementType.TYPE) //作用于类上面 public @interface Webservlet { String url() default ""; }
-
创建 Httpservlet 实现 Service 服务:
public abstract class HttpServlet { //HttpServerlet只实现了父类的service服务,其他方法没有实现,此时该类为抽象类 //子类需要使用doGet或doPost方法,在这里直接让子类去实现两个发给发 //这里需要获取Request中被解析出来的数据, //要想访问的是同一个Request对象,这里用到接口,让Request实现这个接口,传参时就会向上转型,此时request对象为同一个对象 public abstract void doGet(HttpServletRequast requast, HttpServletResponse response) throws Exception; public abstract void doPost(HttpServletRequast requast,HttpServletResponse response); //在服务中判断用户的请求方式,让子类实现向对应的方法 public void service(HttpServletRequast requast,HttpServletResponse response) throws Exception { if(requast.getMethod().equals("GET")){ doGet(requast,response); }else if(requast.getMethod().equals("POST")){ doPost(requast,response); } } }
-
HttpservletRequast:为 Httpservlet 访问对象为统一对象,让 Request 实现这个接口
public interface HttpservletRequast { String getMethod(); void setMethod(String method); String getPath(); void setPath(String path); }
-
自己创建 servlet,继承 Httpservlet 实现 service 服务 ==> 实现相关的访问方式
@WebServerlet(url = "OneServerlet") public class FirstServlet extends HttpServlet { @Override public void doGet(HttpServletRequast requast, HttpServletResponse response) throws Exception { } @Override public void doPost(HttpServletRequast requast, HttpServletResponse response) { } }
-
获取访问地址:HashMap 中的 key 值
public class getMessageUtil { public static String fund(String path) throws Exception { //创建类对象 Class clazz = Class.forName(path); //根据类对象调用 getDeclaredAnnotation() 方法找到该类的访问地址(@Webservlet()中的内容) Webservlet webservlet = (Webservlet) clazz.getDeclaredAnnotation(Webservlet.class); return webservlet.url(); } public static void main(String[] args) throws Exception { //fund(); } }
- 映射器:底层由 HashMap 容器存储
public class ServletConfigMapping { //将执行逻辑写入static代码块中,以便更好加载 //定义Servlet容器 public static Map<String,Class<HttpServlet>> classMap = new HashMap<>(); //该静态代码块应放在启动tomcat前运行 static { List<String> classPaths = SearchClassUtil.searchClass(); for (String classPath : classPaths){ try { InitClassMap(classPath); }catch (Exception e){ e.printStackTrace(); } } } //将key val 值插入到HashMap中 public static void InitClassMap(String classPath) throws Exception { //获取类对象 Class clazz = Class.forName(classPath); //获取访问地址 String url = getMessageUtil.fundUrl(classPath); //将值插入HashMap树当中 classMap.put(url,clazz); } }
Response 返回数据:
-
设置返回头工具类:
/** * 设置返回头 */ public class ResponseUtil { public static final String responseHeader200 = "HTTP/1.1 200 \r\n"+ "Content-Type:text/html \r\n"+"\r\n"; public static final String responseHeader200JSON = "HTTP/1.1 200 \r\n"+ "Content-Type:application/json \r\n"+"\r\n"; public static String getResponseHeader404(){ return "HTTP/1.1 404 \r\n"+ "Content-Type:text/html \r\n"+"\r\n" + "404"; } public static String getResponseHeader200(String context){ return "HTTP/1.1 200 \r\n"+ "Content-Type:text/html \r\n"+"\r\n" + context; } }
-
读取文件:根据提供的地址转化为文件完整地址
/** * 该类的主要作用是进行读取文件 */ public class FileUtil { public static boolean witeFile(InputStream inputStream, OutputStream outputStream){ boolean success = false ; BufferedInputStream bufferedInputStream ; BufferedOutputStream bufferedOutputStream; try { bufferedInputStream = new BufferedInputStream(inputStream); bufferedOutputStream = new BufferedOutputStream(outputStream); bufferedOutputStream.write(ResponseUtil.responseHeader200.getBytes()); int count = 0; while (count == 0){ count = inputStream.available(); } int fileSize = inputStream.available(); long written = 0; int beteSize = 1024; byte[] bytes = new byte[beteSize]; while (written < fileSize){ if(written + beteSize > fileSize){ beteSize = (int)(fileSize - written); bytes = new byte[beteSize]; } bufferedInputStream.read(bytes); bufferedOutputStream.write(bytes); bufferedOutputStream.flush(); written += beteSize; } success = true; } catch (IOException e) { e.printStackTrace(); } return success; } public static boolean writeFile(File file,OutputStream outputStream) throws Exception{ return witeFile(new FileInputStream(file),outputStream); } /** * 根据提供的地址转换为文件完整地址 * @param path * @return */ public static String getResoucePath(String path){ String resource = FileUtil.class.getResource("/").getPath(); return resource + "\\" + path; } }
-
response 返回数据:
public class Response implements HttpServletResponse { //输出流 private OutputStream outputStream; public Response(OutputStream outputStream){ this.outputStream = outputStream; } /*** * 返回动态文字信息 * @param context * @throws IOException */ public void write(String context) throws IOException { outputStream.write(context.getBytes()); //将文字信息转换为 byte流 形式 } public void WriteHtml(String Path) throws Exception { //得到文件全路径 String resoucePath = FileUtil.getResoucePath(Path); //创建文件 File file = new File(resoucePath); if(file.exists()){ System.out.println("静态资源存在"); //输出静态资源 FileUtil.writeFile(file,outputStream); }else { System.out.println("静态资源不存在"); } } }
-
HttpServletResponse 接口:
public interface HttpServletResponse { void write(String context) throws IOException; }
-
输出资源:
Response response = new Response(socket.getOutputStream()); if(request.getPath().equals("") || request.getPath().equals("/")){ //空访问 response.WriteHtml("404.html"); //抛出404页面 response.write(ResponseUtil.getResponseHeader404()); //抛出404文字信息 } else if (ServerletConfigMapping.classMap.get(request.getPath()) == null) { //静态资源 response.WriteHtml(request.getPath()); }else { //动态资源 Class<HttpServlet> httpServletClass = ServerletConfigMapping.classMap.get(request.getPath()); //获取类对象 if(httpServletClass != null){ //有类对象 HttpServlet httpServlet = httpServletClass.newInstance(); //多态创建对象 httpServlet.service(request,response); //启动service服务 }else{ //没有动态资源 response.WriteHtml("404.html"); //抛出 404页面 } }
-
整合后 Tomcat:
public class myTomcat { Request request = new Request(); //创建解析请求的对象 //提前加载容器(HashMap) static { List<String> classPaths = SearchClassUtil.searchClass(); for (String classPath : classPaths){ try { ServerletConfigMapping.InitClassMap(classPath); }catch (Exception e){ e.printStackTrace(); } } } public static void main(String[] args) throws IOException { myTomcat myTomcat = new myTomcat(); myTomcat.startUP(); } public void startUP() throws IOException { //监听端口号 ServerSocket serverSocket = new ServerSocket(8080 ); while(true){ Socket socket = serverSocket.accept(); //阻塞监听 System.out.println("有请求!!!!!!!"); //将每个请求开启一个线程 new Thread(new Runnable() { @Override public void run() { try { handler(socket); //调用处理信息方法 }catch (Exception e){ e.printStackTrace(); } } }).start(); } } //处理信息 public void handler(Socket socket) throws Exception { InputStream inputStream = socket.getInputStream(); //将bit流转换为文字信息 int count = 0; while(count == 0){ count = inputStream.available(); //统计输入流的长度 } //打印数据 byte[] bytes = new byte[count]; inputStream.read(bytes); //将bit信息写入到byte数组 String Context = new String(bytes); //将 byte 数组转换为字符串 System.out.println(Context); //输出信息 //拆解字符串,获取想要的信息 String[] list = Context.split("\\n"); //根据换行切割字符串 String Methed = list[0].split(" ")[0]; //在拆分的第一行中以空格再次拆分,获取第一个数据 String Path = list[0].split(" ")[1]; //在拆分的第一行中以空格再次拆分,获取第二个数据 //把截取的数据传给 Request 类 request.setMethod(Methed); request.setPath(Path); //判断资源类型 Response response = new Response(socket.getOutputStream()); if(request.getPath().equals("") || request.getPath().equals("/")){ //空访问 response.WriteHtml("404.html"); //抛出404页面 response.write(ResponseUtil.getResponseHeader404()); //抛出404文字信息 } else if (ServerletConfigMapping.classMap.get(request.getPath()) == null) { //静态资源 response.WriteHtml(request.getPath()); }else { //动态资源 Class<HttpServlet> httpServletClass = ServerletConfigMapping.classMap.get(request.getPath()); //获取类对象 if(httpServletClass != null){ //有类对象 HttpServlet httpServlet = httpServletClass.newInstance(); //多态创建对象 httpServlet.service(request,response); //启动service服务 }else{ //没有动态资源 response.WriteHtml("404.html"); //抛出 404页面 } } } }
Tomcat 运行原理:
-
原理:
- 浏览器发起请求
- Socket 解析输入流,获取请求头信息
- 分析请求的地址是动态资源还是静态资源
- 首先判断 HashMap 中有没有这个 Key 值
- 如果有就去访问动态资源,如果没有就去查看静态资源
- 如果也不是静态资源就返回 404
- Servlet 容器(HashMap):
- 将 @WebServlet 中的值作为 key 值,将对象作为 value 值,存入 HashMap 中
-
Servlet 容器加载时期:
- 在 Socket 启动之前启动 Servlet 容器
- 缺点:程序启动时间变长
- 优点:不易出现空指针
- 在 Socket 启动之后启动 Servlet 容器
- 在浏览器访问的同时启动 Servlet 容器
- 在 Socket 启动之前启动 Servlet 容器