web 基本概念
web 资源分类
- 静态资源:所有用户访问后,得到的结果都是一样的。静态资源可以直接被浏览器解析(html、css、js)。
- 动态资源:不同用户访问相同的资源后,得到的结果可能是不一样的。原理是动态资源被访问后, 先转换为静态资源,再返回给浏览器解析(servlet/jsp、php、asp),所以并不存在真正的动态资源。
HTTP 工作流程
- 用户在页面进行某一操作
- 浏览器和服务器建立连接
- 浏览器给服务器发送请求
- 服务器处理请求,并将执行结果响应给浏览器
- 浏览器解析服务器返回的结果,并展示给用户
HTTP 服务器
能够处理 HTTP 请求的服务器就是 HTTP 服务器,市面上常见的服务器有以下几款:
- 收费:webLogic(oracle 公司)、webSphere(IBM 公司)、JBOSS(JBoss 公司),这三款都是大型的 JavaEE 服务器,支持所有的 JavaEE 规范。
- 免费:Tomcat(Apache 基金组织),中小型 JavaEE 服务器,仅支持少量的 JavaEE 规范的 servlet/jsp。
Tomcat 架构
HTTP 请求处理
图1:由 HTTP 服务器直接调用具体业务类处理 HTTP 请求,需要在 HTTP 服务器中编写代码,服务器和业务类紧耦合。
图2:HTTP 服务器不直接调用业务类,而是把请求交给容器来处理,容器通过 Servlet 接口调用业务类。因此 Servlet 接口和 Servlet 容器的出现,达到了 HTTP 服务器和业务类解耦的目的。而 Servlet 接口和 Servlet 容器的这一整套规范叫作 Servlet 规范(JaveEE 13 规范之一)。
Tomcat 按照 Servlet 规范的要求实现了 Servlet 容器,同时它也具有 HTTP 服务器的功能。如果要实现新的业务功能,只需要实现一个 Servlet 接口,并把它注册到 Tomcat(Servlet 容器)中,剩下的事情就由 Tomcat 帮我们处理了。
Servlet 容器工作流程
当客户请求某个资源时,HTTP 服务器会用一个 ServletRequest 对象把客户的请求信息封装起来,然后传递给 Servlet 容器,Servlet 容器拿到请求后,根据请求的 URL 和 Servlet 的映射关系,找到相应的 Servlet,如果 Servlet 还没有被加载,就用反射机制创建这个 Servlet,并调用 Servlet 的 init 方法来完成初始化,接着调用 Servlet 的 service 方法(get、post ...)来处理请求,并将 ServletResponse 对象返回给 HTTP 服务器,HTTP 服务器会把响应发送给客户端。
Tomcat 整体架构
综上所述,我们可以发现 Tomcat 有两个核心功能:
- 处理 socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
- 加载和管理 Servlet,以及具体处理 Request 请求。
因此 Tomcat 设计了两个核心组件:连接器(Connector)和容器(Container)。
连接器负责对外交流,容器负责内部处理。一个容器可以对应多个连接器,连接器和容器都不能单独的对外提供服务,所以 Tomcat 以 service 的形式对外提供服务,一个 service 中至少包含一个连接器和一个容器。
Tomcat 包含模块如下:
连接器、容器
Coyote
Coyote 是 Tomcat 服务器提供的供客户端访问的外部接口,客户端通过 Coyote 与服务器建立连接、发送请求并接受响应。
Coyote 封装了底层的网络通信(Socket,请求以及响应处理),为 Catalina 容器提供了统一的接口,使 Catalina 容器与具体的请求协议以及 IO 操作方式完全解耦。
需要注意的是,Coyote 作为独立模块,负责具体协议和 IO 的相关操作,与 Servlet 规范实现没有直接的关系,因此即便是 Request 和 Response 对象也并未实现 Servlet 规范对应的接口,而是在 Catalina 中将他们进一步封装为 ServletRequest 和 ServletResponse。
IO 模型与协议
连接器组件
EndPoint:Coyote 通信监听接口,具体的 Socket 接收和发送处理器,是对传输层的抽象。
Processor:Coyote 协议处理接口,实现 HTTP 协议。接收来自 EndPoint 的 Socket,读取字节流解析成 Request 和 Response 对象,是对应用层协议的抽象。
Adapter:由于 Request 并没有实现 Servlet 规范,而 Servlet 容器只能接收 ServletRequest,因此需要引入一个适配器将 Request 转换成 ServletRequest,再传递给 Servlet 容器。
Catalina
整体架构
如上图所示,Catalina 负责管理 Server,而 Server 表示整个服务器,Server 下面有很多服务 Service,每个服务都包含着多个连接器组件 Connector(Coyote 实现)和一个容器组件 Container。在 Tomcat 启动时,会初始化一个 Catalina 的实例。
Container 结构
时序图
启动流程时序图
请求调用时序图
用户一个请求从 URL 到定位到某一个具体的 Servlet,是由 Mapper 组件来完成这个任务的。它的工作原理是保存了 Web 应用的配置信息(容器组件与访问路径的映射关系),比如 Host 容器里配置的域名、Context 容器里的 Web 应用路径以及 Wrapper 容器里 Servlet 的映射的路径,这个信息可以想象成一个多层次的 Map。
假设来自客户的请求为:http://localhost:8080/wsota/wsota_index.jsp。
- 请求被发送到本机端口 8080,被在那里侦听的 Coyote HTTP/1.1 Connector 获得
- Connector 把该请求交给它所在的 Service 的 Engine 来处理,并等待来自 Engine 的回应
- Engine 获得请求 localhost/wsota/wsota_index.jsp,匹配它所拥有的所有虚拟主机 Host
- Engine 匹配到名为 localhost 的 Host(即使匹配不到也把请求交给该 Host 处理,因为该 Host 被定义为该 Engine 的默认主机)
- localhost Host 获得请求 /wsota/wsota_index.jsp,匹配它所拥有的所有 Context
- Host 匹配到路径为 /wsota 的 Context
- path=/wsota 的 Context 获得请求 /wsota_index.jsp,在它的 mapping table 中寻找对应的 servlet
- Context 匹配到 url pattern 为 *.jsp 的 servlet,对应于 JspServlet 类
- 构造 HttpServletRequest 对象和 HttpServletResponse 对象,作为参数调用 JspServlet 的 doGet 或 doPost 方法
- Context 把执行完了之后的 HttpServletResponse 对象返回给 Host
- Host 把 HttpServletResponse 对象返回给 Engine
- Engine 把 HttpServletResponse 对象返回给 Connector
- Connector 把 HttpServletResponse 对象返回给客户 browser
Jasper
客户端访问一个 jsp 文件,最终接收到的响应还是 html、css 和 js 代码,因此 jsp 可以看做是一个运行在服务端的脚本。
而 Jasper 的作用就是对 jsp 语法进行解析,生成 servlet 并生成 class 字节码文件,最终将访问的结果直接响应客户端。
因此客户端发送请求之后就会刷新页面,是因为 jasper 直接返回了一个全新的 html 代码。
Jasper 配置
Tomcat 在默认的 web.xml 中配置了一个 JspServlet,用于处理所有的 .jsp 或者 .jspx 结尾的请求:
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet><servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
jsp 请求处理流程
生成 java、class 流程图:
Tomcat 配置
server.xml
- Executor:Connector 线程池配置。若不配置,每个 Connector 使用自己的线程池;可以通过配置使 Connector 使用共享线程池。
- Engine:默认访问名为 localhost 的主机下的资源。
- Host:指定主机名(若配置为 www.xxxx.com,需要能被 host 或者 dns 解析)、资源位置。
- Context:指代 Web 应用,Web 应用基于 WAR 文件,或 WAR 文件解压后对应的目录。由于 Tomcat 能够自动部署,所以该标签的 docBase 属性不需要指定,由 Tomcat 根据目录名称自动推导。
web.xml
tomcat 配置文件中有一份,用户自己的项目工程中也可以配置一份。
- context-param:配置 tomcat 初始化参数。
- session-config:配置 session 相关的参数,甚至可以修改默认的 JSESSIONID 名字。
- error-page:可以配置 error-code 或者 exception-type。
tomcat-users.xml
可以配置用户。访问 webapps 中的 host-manager 和 manager 时,需要在该文件中配置指定的用户角色。
Tomcat 集群
环境搭建
Nginx + Tomcat:
- Nginx 配置 serverpool
- Tomcat 三个端口号修改
负载均衡:权重、ip_hash(根据 ip 计算出一个 hash 值,决定访问哪台 Tomcat)
session 共享策略:ip_hash、Tomcat 配置广播(如果集群很大,会造成广播风暴)、SSO 单点登录(推荐)。
关于 session 共享,现在也有 JWT — — 即服务端放弃 session,客户端使用一个 JSON 字符串保存客户信息,每次发送请求发送到服务端。
安全配置
- 删除 webapps 目录下的所有文件,禁用 tomcat 管理界面。
- 删除 tomcat-uesrs.xml 文件呢id所有用户权限。
- 更改关闭 tomcat 指令(例如 8005 端口的 shundown 指令可以关闭 Tomcat)
- 定义错误页面,不让用户看到错误信息,从而推测出系统的架构信息。
应用安全
基本包含两个部分:认证(登录/单点登录)和授权(功能权限、数据权限)。
推荐框架 SpringSecurity、Apache Shiro。
传输安全
对存在敏感信息的页面,使用 HTTPS 协议。
性能测试
免费工具:ApacheBench、ApacheJMeter
收费:WCAT、WebPloygraph、LoadRunner
吞吐量:每秒执行的请求数。
配置调优
JVM 内存、垃圾挥手配置
Tomcat 连接器配置:最大连接数(超过之后进入等待队列,和机器性能挂钩)、最大等待数(超过之后不再接收请求)
WebSocket(补充)
轮询与 Comet
HTTP 协议是一个请求 - 响应模式的协议,由客户端发起请求,服务端接收到请求从而给出响应。那么服务端是无法主动给客户端发送数据的。
为了解决这个问题,出现两种思路:
1、轮询:在客户端通过 js 脚本,每隔一段时间给服务端发送一个请求,询问服务端是否有新消息要发送。缺点:服务端的压力大,消息存在延迟。
2、Comet:本质上是一种异步的轮询,仍然存在缺点。
WebSocket
是 HTML5 新增的标准,让浏览器和服务器之间可以建立无限制的全双工通信,任何一方都可以主动发消息给对方。WebSocket 并不是全新的协议,而是利用 HTTP 协议来建立连接。
WebSocket 请求信息:
Tomcat 的 WebSocket 支持
Java WebSocket 中使用 Endpoint 对象代表 WebSocket 连接的一端,可以使用两种方式定义 Endpoint:
- 继承 javax.websocket.Endpoint 并实现其方法;
- 定义一个 POJO,并添加 @ServletEndpoint 相关注解。
Endpoint 实例在 WebSocket 握手时创建,并在客户端与服务端连接过程中有效。生命周期方法如下:
- onOpen:当开启一个新的会话时调用(注解@OnOpen)
- onClose:会话关闭时调用(注解@OnClose)
- onError:连接过程中异常调用(注解 @OnError)