从零开始手写Tomcat的教程---未完待续

x33g5p2x  于2022-03-06 转载在 其他  
字(3.6k)|赞(0)|评价(0)|浏览(380)

手写Tomcat

介绍

本项目主要是手写精简版的tomcat,力争做到不遗不漏

本项目gitee仓库链接如下:https://gitee.com/DaHuYuXiXi/easy-tomcat.git

本篇文章是梳理每一小节的基本脉络,具体每小节的代码实现,自行参考gitee仓库里面的提交记录

第一节 : 一个简单的Web服务器

本节主要重点在于建立基本的socket通信服务,完成最简单的Http通信

本节主要创建了HttpServer,Request和Response三个类,分别为程序入口点,解析请求的对象和负责响应的对象,IO选择的是最简单的BIO实现

注意点

在BIO中,accept()得到的客户端socket中,如果在使用过程中关闭了通道中的输入流或者输出流,会终止当前通道的连接,这点需要特点注意。 还需要注意,数据写完记得flush一下,否则数据会遗留在缓冲区中,导致浏览器接收不到数据

第二节: 一个简单的servlet容器

创建一个Primitvie类,然后将其生产的字节码文件移动到webroot目录下面,因为类加载器加载的class字节码文件

首先,我们指定一个规则,来负责区分静态资源请求和servlet请求,先简化为如下:

  1. 静态资源请求:
  2. http://host:port/xx.html
  1. servlet请求:
  2. http://host:port/servlet/servletClass ---全类名

区分部分对应代码如下:

  1. //区分资源类型
  2. if(request.getUri().startsWith("/servlet/"))
  3. {
  4. //servlet请求资源处理
  5. ServletProcessor1 processor1=new ServletProcessor1();
  6. processor1.process(request,response);
  7. }else
  8. {
  9. //静态资源请求
  10. StaticResourceProcessor processor=new StaticResourceProcessor();
  11. processor.processor(request,response);
  12. }

下面需要做的是将Request和Response对象分别继承ServletRequest和ServletResponse对象

需要注意的是ServletResponse对象的getWriter方法重写:

  1. /**
  2. * <p>
  3. * PrintWriter构造函数第二个参数为true,
  4. * 表示是否开启自动刷新,传入true表示对println的任何方法都刷新输出,但是print方法不会
  5. * 当然这个bug后续版本会修改
  6. * </p>
  7. * @return
  8. * @throws IOException
  9. */
  10. @Override
  11. public PrintWriter getWriter() throws IOException {
  12. writer = new PrintWriter(bufferedOutputStream, true);
  13. return writer;
  14. }

创建两个处理器对象,分别处理静态资源和sevlet请求资源

  1. /**
  2. * <p>
  3. * 静态资源处理器
  4. * </p>
  5. * @author 大忽悠
  6. * @create 2022/3/5 23:09
  7. */
  8. public class StaticResourceProcessor {
  9. public void processor(Request request, Response response)
  10. {
  11. response.sendStaticResource();
  12. }
  13. }
  1. /**
  2. * <p>
  3. * servlet请求处理器
  4. * </p>
  5. * @author 大忽悠
  6. * @create 2022/3/5 23:11
  7. */
  8. public class ServletProcessor1 {
  9. public void process(Request request, Response response) {
  10. try {
  11. //1.获取servlet的名字
  12. String uri = request.getUri();
  13. String serveltName = uri.substring(uri.lastIndexOf("/") + 1);
  14. System.out.println("servelt的名字为: "+serveltName);
  15. //2.创建类加载器
  16. URLClassLoader loader=null;
  17. URL[] urls = new URL[1];
  18. URLStreamHandler urlStreamHandler=null;
  19. //类路径
  20. File classPath=new File(Constants.WEB_ROOT);
  21. //资源仓库地址
  22. //file.getCanonicalPath: 返回标准的文件绝对路径
  23. //URL : 参数一: 资源协议--file,https,ftp等 参数二:主机名,http是需要 参数三:资源路径,这里填文件路径
  24. String respository=(new URL("file",null,classPath.getCanonicalPath()+File.separator)).toString();
  25. System.out.println("仓库地址为: "+respository);
  26. // urlStreamHandler通过资源不同的来源来决定处理的逻辑--->不同前缀来识别。比如:"file :"、"http :"、"jar :"
  27. urls[0] = new URL(null, respository, urlStreamHandler);
  28. //加载指定路径下的class
  29. loader=new URLClassLoader(urls);
  30. //负责加载当前访问的servlet
  31. Class myclass=null;
  32. //这里可以直接填类名,而非全类名的条件是,类文件放在java目录下
  33. myclass = loader.loadClass(serveltName);
  34. Servlet servlet=null;
  35. servlet = (Servlet) myclass.newInstance();
  36. //执行指定servlet的方法
  37. servlet.service((ServletRequest) request,(ServletResponse) response);
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. } catch (ClassNotFoundException e) {
  41. e.printStackTrace();
  42. } catch (InstantiationException e) {
  43. e.printStackTrace();
  44. } catch (IllegalAccessException e) {
  45. e.printStackTrace();
  46. } catch (ServletException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. }

这里提两点:

  • 如果一个URL以"/"结尾,则表明它指向的是一个目录,否则,URL默认指向一个JAR文件,根据需要载入器会下载并打开这个JAR文件
  • 在servlet容器中,类加载器查询servlet类的目录称为仓库

这里对URLClassLoader不清楚的,自己去查资料

到此为止,大致思路和代码就完工了,但是完美了吗? 不不不,还有很大的问题

  1. public void process(Request request, Response response){
  2. .....
  3. //执行指定servlet的方法
  4. servlet.service((ServletRequest) request,(ServletResponse) response);
  5. }

有没有看出来问题,直接将Request对象和Response传给用户,这意味着,用户可以调用Request的parse方法和Response的sendStaticResource方法。

显然,这样是非常不安全的,但是还不能将parse方法和sendStaticResource方法设置为私有的,因为其他类还需要调用。

下面有两种解决方法:

  • Request和Response类都设置为默认的访问修饰符,这样就不能从他们所在包外访问了
  • 外观模式

这里主要讲一下外观模式的使用,因为第一种方法存在局限性

外观模式在此处使用的作用不是屏蔽系统使用的复杂性,主要是为了向用户隐蔽一些内部方法

虽然此时程序员仍然可以将servletRequest对象向下转型RequestFacade对象,但是只能访问ServletRequest对象中提供的方法,保证了Request对象中的parse方法的安全性。

在将Request和Response对象传给用户的时候,将其转换为外观对象,传入。

争取每天一节的更新速度

相关文章