菜鸟模拟tomcat服务器项目初级架构。

前言:
本文为TOMCAT模拟简易程序的思路,以提供在编码过程中具体的思路。某些类的部分成员变量并没有列出,只列出
了大致的思路以及相关重要的方法,方法细节如何实现一般没有提供。

本项目外在业务逻辑非常简单,即服务端开启后,接收客户端的请求并根据对应的请求进行响应即可实现以此交互。

因此理论上该项目只分为三大块,即1,开启服务器;2,接收请求;3,响应请求。其中2,3循环运行即可。

防止嵌套过于冗杂,以下排序将按照类来进行排序。并且同一个包包含相同或类似的业务逻辑。以中文小写数字来表示。
下面简单介绍各包的职责:

一、核心包。
 该项目业务运行的基础保证。

二、网页包
 该包的类的实例表示一个具体的请求或者回应。项目内在核心处理部分。

三、业务处理包
 拓展包,里面的类代表每一个实现的具体业务。

四、配置包,网页包

一、核心
  1.建立服务器WebServer,并等待客户端连接。客户端连接后交给线程并循环接受客户端请求。
 
  1.1 new 一个ServerSocket 的连接,选择端口8080

  1.2 调用accept()方法接受客户端请求并生成一个socket和客户端交互

  1.3 将交互的行为交给一个线程ClientHandler去处理,这里考虑到多并发等问题,采用线程池ExcutorService

  1.4 将CilentHandler线程放进线程池。然后循环接收下一个客户端请求。

 

2.ClientHandler 服务端接受客户端请求后,服务端线程开始根据客户端请求响应相应的内容给客户端。

  2.1 ClientHandler实现接口Runnable,以实现线程,并重写run()方法
  由于接收请求和响应请求的业务逻辑较多,因此分别声明两个类HttpRequest和HttpResponse来代表请求和响应
  以此来书写请求和响应的代码内容 (具体类内容见3和4)

  

    2.1.1 接收请求
  new 一个HttpRequest对象,用来表示客户端的一个请求,并得到该请求的内容以此来作出对应的响应  

    2.1.2 响应请求生成响应内容
  new 一个HttpResponse对象,用来表示服务端一个响应,根据前面的请求对象作出对应的响应内容
  响应根据客户端请求一般分为:业务类、页面请求类以及空请求。


     2.1.2.1 业务类:将所有业务类抽离出业务超类,并利用反射原理以达到扩展业务的功能。该抽象超类
   有一个抽象方法service()和一个一般方法forward();各个业务的子类额外定义在servlet包中。
   当接收到客户端业务请求时,响应相应业务逻辑并反馈。

    2.1.2.2 页面请求类:根据客户端请求,找到目标文件。若目标文件不存在,需要反馈给客户端404

     2.1.2.3 若遇到空请求,额外抛出一个自定义异常EmptyRequestException,不予以处理,以保证程序
   稳定运行。

    2.1.3 无论对应的响应内容是什么,都需要将响应内容发送给客户端,将所有响应内容统一写在一个flush()
  方法中并在此调用即可。

    3. EmptyRequestException 继承Exception 用来处理客户端给服务器发送空请求。仅需继承Exception的方法即可。

    4. ServletContent 配置类。用于拓展业务。其中含有静态Map,把请求url和对应类名进行map,以实现反射来自由扩展
   业务需求。其中该Map的值通过xml文件写入

二、网页
  1.HttpRequest,该类每一个对象表示客户端发送的一个请求,在服务端接收请求后,需要作出响应。

   请求包含请求行,消息头和消息正文。服务端需要将客户端发送过来的请求分别解析出来,因此定义三个主要方法:
   parseRequestLine(),parseTextHeader(),parseTextContent() 并直接定义在构造方法中。

   1.1由于构造方法中调用了parseRequestLine(),而parseRequestLine()可能会解析到空请求,因此需要将这个
 空请求异常抛给调用方,ClientHandler中。
  
  1.1.1 parseRequestLine() 解析请求行 请求行内容为 method url protocol,只有一行内容。
  因此可以定义一个只读取一行请求的方法readLine(),在读取完一行,获取对应的字符串以后,按照
  空格拆分成三项 method url protocol,并传入既定的成员变量中后续使用。由于成员变量是私有的
  故再生成获取对应成员变量的getMethod() getUrl() getProtocol()三个方法

  另外,由于这里需要用到三项内容,而请求不一定能够拆成三项,若无法拆成三项,会出现数组下标越界,
  故而需要进行一次判断确保必须分成三项再进行拆分。
  
   1.1.1.1 由于客户端可能发送空请求,此时请求行就没有三项内容,因此加上判断拆出的字符串数组是否有
   三项,没有三项直接抛出空请求异常EmptyRequestException给HttpRequest的构造方法。然后后面的
   所有解析工作将不再执行。
   
   1.1.1.2 请求行成功拆成三项,并分别赋值给对应的成员变量。由于不同的请求对象其url部分是不同的,
   因此需要额外解析url部分来确定具体请求。因此需要额外声明一个方法parseUrl()
    
  1.1.2 parseUrl()用于解析url字符串。需要分情况讨论,如果字符串中包含“?”则为判断为业务请求,
  否则,是单纯的页面请求。而url字符串通过“?”拆分后,需要确保拆出右边parameters部分,如果无法
  拆出右边部分需要额外再次判断,否则可能出现空指针。而?左边的部分requestURI则可以当作真正的
  业务请求,服务器以此来作出不同的响应。
  
  由于请求行不支持除了"ISO8859-1"以外字符编码,因此这里需要先将url用URLDecoder转码后再处理分析。

   1.1.2.1 如果url字符串不包含“?”,直接将url的值赋给requestURI。这和直接处理url时结果一样。
   
   1.1.2.2 如果url字符串包含“?”,按照“?”将url拆分。拆分成requestURI,和queryString两部分。
   此时需要判断,可能“?”右边没有信息,这时queryString为空,以防数组下标越界,只有当“?”拆出
   的数组项数大于1时,才进行拆分。否则,直接将queryString赋值成空串
 
   1.1.2.3 然后再解析queryString部分。queryString若不为空,则为一系列参数,因此可以声明一个MAP
   parameters 将这些参数名称和参数值作为映射对存入该parameters当中。

   1.1.2.4 把queryString部分按照“&”拆分,拆分后的数组进行遍历,再按照“=”进行拆分。然后再将拆
   出的两项,第一项作为key,第二项作为value存入parameters当中。由于可能存在某些“=”拆不出两项
   只有一项的情况,同样防止下标越界,需要判断每一项的长度,当长度为2时,才取出数组第二项,否则将value
   值当作空串存入parameters。这里生成一个方法parseParameters()

 1.2 parseTextHeader()解析消息头
  消息头的组成为XX: XX。因此用readLine()方法每读取一行,按照“: ”将消息头每一项拆分。
  1.2.1 声明一个headers的Map,将拆分的两项信息存入该Map。
  
  1.2.2 若用readLine()读到了空串,则表示消息头解析完毕。跳出循环。

 1.3 parseTextContent()解析消息正文
  

     若网页的表单信息采取post方式发送,那么相应参数信息不会出现在requestURI中。这时消息头中会有两项信息:
  "Content-Type"和"Content-Length".因此判断headers中是否含有"Content-Length",若有,则说明存在
  消息正文,这时需要解析消息正文。

  选取Content-Length长度的字节数组将消息正文读出。并判断Content-Type的值,若该值是表单信息,用字符串
  将该信息接收,并再次调用URLDecoder来转码以确保其正确显示非ISO8859-1字符。

  最后调用parseParameters()方法即可(具体方法见1.1.2.4)
    
  
 1.4 readLine() 单独的一个方法,用于读取一行请求内容。

  2.HttpResponse,该类每一个对象表示服务端给客户端的一个响应,这个响应必须依赖前面HttpRequest对象。
 服务端响应客户端需要发送的内容。同样的,发送的内容类似的包含:状态行,响应头和响应正文。
 
 由于响应的内容必须依附于具体的请求对象,因此不能直接将发送给客户端的内容直接写在构造函数里面,而是在响应对象生成以后
 将该响应对象依据请求对象进行一系列操作以后,再统一发送给客户端。

 因此将发送给客户端的三项内容拆分成三个方法:sendStatusLine() sendResponseHeaders() sendResponseContent(),
 然后统一放在一个方法flush()当中,在ClientHandler中,依据Request对象确定具体业务逻辑后,最后调用flush()方法即可。

 2.1 发送状态行 sendStatusLine()
 状态行包含三部分内容:protocol statusCode statusResult 
 由于statusCode和statusResult 有唯一匹配关系,因此将它们的关系建立一个map STATUS_CODE_SERVLET_MAPPING,在
 HttpContent中声明并赋值。声明两个私有属性statusCode statusResult,设置statusCode的值以后,自动匹配statusResult
 的值(这里需要两个方法)。然后将protocol statusCode statusResult 采用字符串拼接,并用print()方法发送给客户端。
 
 2.2 发送响应头 sendResponseHeaders()
 响应头的结构和消息头类似,也是XX: XX的形式。定义一个Map headers,将响应头的内容存入该Map,此处仅需遍历Map,然后按照
 key: value的形式用print()方法发送给客户端即可。
 
 为了动态实现客户端请求不同的文件时,动态改变Content-Type的值。 一个静态Map MIME_MAPPING中,该变量在HttpContent中声明。
 而Map的初始化是由读取XML实现的。这里直接借用tomcat的web.xml文件,用SAXReader读取分析并将其中相应的内容存入MIME_MAPPING中。

 2.3 发送响应正文 sendResponseContent()
 用于响应给客户端发送目标文件。使用字节输出流,将目标文件转成字节码后,通过socket生成的输出流把目标文件发送给客户端接收。响应头
 的Content-Length对应的数值,也由此文件决定。

 2.4 回应客户端:flush() 仅包含上面三个方法

 2.5 print() 该方法用于发送一行信息文本。其中每一行都以CRLF结尾,当单独发送一行CRLF即空串时表示响应头结束。

 2.6 setEntity(File file)外界用于传递文件用的方法。在文件生成时,可以直接将相关的"Content-Type"和"Content-Length"
 存入headers这个Map中。
 在判断Content-Type的值时,需要根据文件名后缀来从STATUS_CODE_SERVLET_MAPPING中搜寻目标value。可能某些文件不含后缀, 
 防止数组下标越界,因此需要判断。

  3.HttpContent,该类仅包含静态方法和静态属性。用来存放发送响应相关信息的Map以及获取Map中相关信息的方法。

三、业务处理
  0.HttpServlet 抽象类,所有业务类的超类。
 目前包含一个抽象方法service(HttpRequest request,HttpResponse response)和一个具体方法forward()

  1.RegServlet 处理注册业务类
 

   1.1重写service(HttpRequest request,HttpResponse response)方法
  1.1.1 从request中得到parameters中对应用户名,密码等信息,使用RandomAccessFile写入既定的目标文件user.dat中
  
  1.1.2 调用forward()方法,反馈注册成功页面

  2.LoginServlet 处理登陆业务类
 

   2.1 重写service(HttpRequest request,HttpResponse response)方法
  2.1.1 从request中拿到parameters中对应用户名,密码等信息,使用RandomAccessFile从既定的目标文件user.dat中循环
  读取对应字节,判断该信息是否和读取到的字节转成的字符串相同。若相同,再判断密码。
 
  2.1.2 若密码不同,直接跳出循环。密码相同,调用forward(),跳转登陆成功页面,并直接结束循环。
  
  2.1.3 若用户名或密码不匹配,调用forward(),跳转登陆失败页面。

  3.UpdateServlet 处理修改密码业务类
 

   3.1  重写service(HttpRequest request,HttpResponse response)方法
  3.1.1 同2.1.1(此处可以进一步抽离方法,复用代码)

  3.1.2 密码不同,跳出循环。密码相同。seek指针到密码字节段,将新密码按照1.1.1的方式覆盖相应字段的密码。并调用forward()方法
  跳转密码修改成功页面。

  3.1.3 若没有修改成功,调用forward(),跳转密码修改失败。

四、配置包,网页包
 1.配置包 用于存放一系列配置文件。比如:存储文件后缀名对应Content-Type的值的Map的web.xml以及存储url和类名关系的Map servlets.xml等

 2.网页包 用于存放业务网页。包括主页,注册页,登陆页,修改密码页以及相关失败成功页面。 另外存一个包sys用来存放通用网页,如404网页等。
             
           
                                                                                                                                                                                            2018.4.26

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值