JavaSE,即Java Platform Standard Edition。JavaEE是Java Platform Enterprise Edition的缩写。
JavaEE最核心的组件就是基于Servlet标准的Web服务器,开发者编写的应用程序是基于Servlet API并运行在Web服务器内部的:
1 | ┌─────────────┐ |
Web基础
HTTP协议
对于Browser来说,请求页面的流程如下:
- 与服务器建立TCP连接;
- 发送HTTP请求;
- 收取HTTP响应,然后把网页在浏览器中显示出来。
浏览器发送的HTTP请求如下:
1 | GET / HTTP/1.1 |
其中,第一行表示使用GET
请求获取路径为/
的资源,并使用HTTP/1.1
协议,从第二行开始,每行都是以Header: Value
形式表示的HTTP头,比较常用的HTTP Header包括:
- Host: 表示请求的主机名,因为一个服务器上可能运行着多个网站,因此,Host表示浏览器正在请求的域名;
- User-Agent: 标识客户端本身,例如Chrome浏览器的标识类似
Mozilla/5.0 ... Chrome/79
,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...) like Gecko
; - Accept:表示浏览器能接收的资源类型,如
text/*
,image/*
或者*/*
表示所有; - Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
- Accept-Encoding:表示浏览器可以支持的压缩类型,例如
gzip, deflate, br
。
服务器的响应如下:
1 | HTTP/1.1 200 OK |
服务器响应的第一行总是版本号+空格+数字+空格+文本,数字表示响应代码,其中2xx
表示成功,3xx
表示重定向,4xx
表示客户端引发的错误,5xx
表示服务器端引发的错误。数字是给程序识别,文本则是给开发者调试使用的。常见的响应代码有:
- 200 OK:表示成功;
- 301 Moved Permanently:表示该URL已经永久重定向;
- 302 Found:表示该URL需要临时重定向;
- 304 Not Modified:表示该资源没有修改,客户端可以使用本地缓存的版本;
- 400 Bad Request:表示客户端发送了一个错误的请求,例如参数无效;
- 401 Unauthorized:表示客户端因为身份未验证而不允许访问该URL;
- 403 Forbidden:表示服务器因为权限问题拒绝了客户端的请求;
- 404 Not Found:表示客户端请求了一个不存在的资源;
- 500 Internal Server Error:表示服务器处理时内部出错,例如因为无法连接数据库;
- 503 Service Unavailable:表示服务器此刻暂时无法处理请求。
从第二行开始,服务器每一行均返回一个HTTP头。服务器经常返回的HTTP Header包括:
- Content-Type:表示该响应内容的类型,例如
text/html
,image/jpeg
; - Content-Length:表示该响应内容的长度(字节数);
- Content-Encoding:表示该响应压缩算法,例如
gzip
; - Cache-Control:指示客户端应如何缓存,例如
max-age=300
表示可以最多缓存300秒。
HTTP请求和响应都由HTTP Header和HTTP Body构成,其中HTTP Header每行都以\r\n
结束。如果遇到两个连续的\r\n
,那么后面就是HTTP Body。浏览器读取HTTP Body,并根据Header信息中指示的Content-Type
、Content-Encoding
等解压后显示网页、图像或其他内容。
通常浏览器获取的第一个资源是HTML网页,在网页中,如果嵌入了JavaScript、CSS、图片、视频等其他资源,浏览器会根据资源的URL再次向服务器请求对应的资源。
HTTP编程是以客户端的身份去请求服务器资源。
以服务器的身份响应客户端请求,编写服务器程序来处理客户端请求通常就称之为Web开发。
HTTP Server
一个HTTP Server本质上是一个TCP服务器,我们先用TCP编程的多线程实现的服务器端框架:
1 | public class Server { |
只需要在handle()
方法中,用Reader读取HTTP请求,用Writer发送HTTP响应,即可实现一个最简单的HTTP服务器。编写代码如下:
1 | private void handle(InputStream input, OutputStream output) throws IOException { |
核心代码是,先读取HTTP请求,上述代码只处理GET /
的请求。当读取到空行时,表示已读到连续两个\r\n
,说明请求结束,可以发送响应。
发送响应的时候,首先发送响应代码HTTP/1.0 200 OK
表示一个成功的200响应,使用HTTP/1.0
协议,然后,依次发送Header,发送完Header后,再发送一个空行标识Header结束,紧接着发送HTTP Body,在浏览器输入网址就可以看到响应页面。
Servlet
JSP
MVC开发
- Servlet适合编写Java代码,实现各种复杂的业务逻辑,但不适合输出复杂的HTML;
- JSP适合编写HTML,并在其中插入动态内容,但不适合编写复杂的Java代码。
假设我们已经编写了几个JavaBean:
1 | public class User { |
在UserServlet
中,我们可以从数据库读取User
、School
等信息,然后,把读取到的JavaBean先放到HttpServletRequest中,再通过forward()
传给user.jsp
处理:
1 |
|
在user.jsp
中,我们只负责展示相关JavaBean的信息,不需要编写访问数据库等复杂逻辑:
1 | <%@ page import="com.itranswarp.learnjava.bean.*"%> |
注意几点:
- 需要展示的
User
被放入HttpServletRequest
中以便传递给JSP,因为一个请求对应一个HttpServletRequest
,我们也无需清理它,处理完该请求后HttpServletRequest
实例将被丢弃; - 把
user.jsp
放到/WEB-INF/
目录下,是因为WEB-INF
是一个特殊目录,Web Server会阻止浏览器对WEB-INF
目录下任何资源的访问,这样就防止用户通过/user.jsp
路径直接访问到JSP页面; - JSP页面首先从
request
变量获取User
实例,然后在页面中直接输出,此处未考虑HTML的转义问题,有潜在安全风险。
在浏览器访问http://localhost:8080/user
,请求首先由UserServlet
处理,然后交给user.jsp
渲染:
把UserServlet
看作业务逻辑处理,把User
看作模型,把user.jsp
看作渲染,这种设计模式通常被称为MVC:Model-View-Controller,即UserServlet
作为控制器(Controller),User
作为模型(Model),user.jsp
作为视图(View),整个MVC架构如下:
1 | ┌───────────────────────┐ |
使用MVC模式的好处是,Controller专注于业务处理,它的处理结果就是Model。Model可以是一个JavaBean,也可以是一个包含多个对象的Map,Controller只负责把Model传递给View,View只负责把Model给“渲染”出来,这样,三者职责明确,且开发更简单,因为开发Controller时无需关注页面,开发View时无需关心如何创建Model。
MVC高级开发
通过普通的Java类实现MVC的Controller?类似下面的代码:
1 | public class UserController { |
上面的这个Java类每个方法都对应一个GET或POST请求,方法返回值是ModelAndView
,它包含一个View的路径以及一个Model,这样,再由MVC框架处理后返回给浏览器。
如果是GET请求,我们希望MVC框架能直接把URL参数按方法参数对应起来然后传入:
1 |
|
如果是POST请求,我们希望MVC框架能直接把Post参数变成一个JavaBean后通过方法参数传入:
1 |
|
Filter过滤器
把一些公用逻辑从各个Servlet中抽离出来,在HTTP请求到达Servlet之前,可以被一个或多个Filter预处理,类似打印日志、登录检查等,可以放到Filter中。
最简单的EncodingFilter,强制把输入和输出的编码设置为UTF-8:
1 |
|
编写Filter时,必须实现Filter
接口,在doFilter()
方法内部,要继续处理请求,必须调用chain.doFilter()
。最后,用@WebFilter
注解标注该Filter需要过滤的URL。
添加了Filter之后,整个请求的处理架构如下:
1 | ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ |
LogFilter:
1 |
|
多个Filter会组成一个链,每个请求都被链上的Filter依次处理:
1 | ┌────────┐ |
Filter的顺序确实对处理的结果有影响
只对特定路径进行过滤的Filter,例如AuthFilter
:
1 |
|
当用户没有登录时,在AuthFilter
内部,直接调用resp.sendRedirect()
发送重定向,且没有调用chain.doFilter()
,因此,当用户没有登录时,请求到达AuthFilter
后,不再继续处理,即后续的Filter和任何Servlet都没有机会处理该请求了。
如果Filter要使请求继续被处理,就一定要调用chain.doFilter()!
Listener监听器
部署
一个具体的Web应用程序为例:
1 | webapp |
一个处理静态文件的FileServlet
:
1 |
|
使用类似Nginx这样的服务器充当反向代理和静态服务器,只有动态请求才会放行给应用服务器,所以,部署架构如下:
1 | ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ |
实现上述功能的Nginx配置文件如下:
1 | server { |
使用Nginx配合Tomcat服务器,可以充分发挥Nginx作为网关的优势,既可以高效处理静态文件,也可以把https、防火墙、限速、反爬虫等功能放到Nginx中,使得WebApp能专注于业务逻辑。
发布时间: 2020-03-13
最后更新: 2020-05-01
本文链接: https://juoyo.github.io/posts/4b7be044.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!