目录
一、动态页面 vs 静态页面
- 静态页面也就是内容始终固定的页面. 即使 用户不同/时间不同/输入的参数不同 , 页面内容也不会发生变化. (除非网站的开发人员修改源代码, 否则页面内容始终不变)
- 动态页面指的就是 用户不同/时间不同/输入的参数不同, 页面内容会发生变化
二、Servlet
- 构建动态页面的技术有很多, 每种语言都有一些相关的库/框架来做这件事. Servlet 就是 Tomcat 这个 HTTP 服务器提供给 Java 的一组 API, 来完成构建动态页面这个任务
通过继承HttpServlet抽象类完成我们类的编写
重写其中的(get()、post()),输出资源内容的过程
把动态资源和路径建立绑定关系1.通过web.xml2.使用Java中的注解语法@WebServlet完成
类似下图
1.编写一个Servlet代码
package com.first_webapp.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author happy
*/
// 6.2 通过用 @WebServlet 注解修饰类,来实现绑定
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(202);
resp.setContentType("text/css; charset=utf-8");
PrintWriter writer = resp.getWriter();
writer.print("你好世界");
}
}
- 1、创建一个类HelloServlet , 继承自 HttpServlet
- 2、在这个类上方加上 @WebServlet("/hello") 注解, 表示 Tomcat 收到的请求中, 路径为 /hello 的请求才会调用 HelloServlet 这个类的代码. (这个路径未包含 Context Path)
- 3、重写 doGet 方法. doGet 的参数有两个, 分别表示收到的 HTTP 请求 和要构造的 HTTP 响应. 这个方法会在 Tomcat 收到 GET 请求时触发
- 4、HttpServletRequest 表示 HTTP 请求. Tomcat 按照 HTTP 请求的格式把 字符串 格式的请求转成 了一个 HttpServletRequest 对象. 后续想获取请求中的信息(方法, url, header, body 等) 都是通 过这个对象来获取.
- 5、HttpServletResponse 表示 HTTP 响应. 代码中把响应对象构造好(构造响应的状态码, header, body 等)
- 6、resp.getWriter() 会获取到一个流对象, 通过这个流对象就可以写入一些数据, 写入的数据会被 构造成一个 HTTP 响应的 body 部分, Tomcat 会把整个响应转成字符串, 通过 socket 写回给浏览器.
@WebServlet("")中填写的资源路径的问题
1.必须使用 / 开头
2.资源路径不能重复
3.资源路径可以写/hello /hello/h /hello.jpg /hello.html
4.可以使用通配符 *.do 后缀名是.do的资源都可以访问;只出现 / 说明所有的URL都可以访问;只出现 @WebServlet("") 代表在根目录下
2.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="false">
<servlet>
<servlet-name>MyFirstServlet</servlet-name>
<servlet-class>com.first_webapp.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyFirstServlet</servlet-name>
<url-pattern>/first</url-pattern>
</servlet-mapping>
</web-app>
3.在浏览器中输入127.0.0.1:8088/hello
4.Tomcat内部的大概流程
1. Tomcat 收到一个HTTP请求
2. Tomcat 根据 HTTP 协议的规定,解析请求,得到请求方法、资源路径、请求头、请求体等信息
3. Tomcat 根据资源路径(Path = Context Path + Servlet Path)
3.1先根据Context Path决定这个资源交给哪个Webapp来处理(Tomcat支持多个Webapp 同时存在)
3.2当确定好Webapp时,根据 Servlet Path找到对应的资源进行处理
3.2.1如果是动态资源,根据资源得到一个类名(上面例子指
com.first_webapp.servlet.HelloServlet)
3.2.2doGet执行结束后,resp对象得到信息(状态码202,Content-Type是text/css), 如果这个类没有实例化对象就实例化对象,否则直接获取对象,根据请求方法 调用该对象的doGet/doPost
3.2.3如果是静态资源,就根据对应的路径去查找对应的文件,并响应文件内容
3.2.4如果动态资源/静态资源都没有找到就会404
5.Servlet运行原理
当浏览器给服务器发送请求的时候, Tomcat 作为 HTTP 服务器, 就可以接收到这个请求. HTTP 协议作为一个应用层协议, 需要底层协议栈来支持工作
5.1接受请求
用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求
这个 HTTP 请求会经过网络协议栈逐层进行 封装 成二进制的 bit 流, 最终通过物理层的硬件设备转 换成光信号/电信号传输出去
这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达目标主机(这个过程也需要 网络层和数据链路层参与)
服务器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成 HTTP 请求. 并交给 Tomcat 进程进行处理(根据端口号确定进程)
Tomcat 通过 Socket 读取到这个请求(一个字符串), 并按照 HTTP 请求的格式来解析这个请求, 根据 请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的 类. 再根据当前请 求的方法 (GET/POST/...), 决定调用这个类的 doGet 或者 doPost 等方法. 此时我们的代码中的 doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息
5.2根据请求计算响应
我们编写的 doGet / doPost 方法中, 就执行到了我们自己的代码. 我们自己的代码会根据请求中的一 些信息, 来给 HttpServletResponse 对象设置一些属性. 例如状态码, header, body 等.
5.3返回响应
我们的 doGet / doPost 执行完毕后, Tomcat 就会自动把 HttpServletResponse 这个我们刚设置好 的对象转换成一个符合 HTTP 协议的字符串, 通过 Socket 把这个响应发送出去
此时响应数据在服务器的主机上通过网络协议栈层层 封装, 最终又得到一个二进制的 bit 流, 通过物 理层硬件设备转换成光信号/电信号传输出去
这些承载信息的光信号/电信号通过互联网上的一系列网络设备, 最终到达浏览器所在的主机(这个过 程也需要网络层和数据链路层参与)
浏览器主机收到这些光信号/电信号, 又会通过网络协议栈逐层进行 分用, 层层解析, 最终还原成 HTTP 响应, 并交给浏览器处理
浏览器也通过 Socket 读到这个响应(一个字符串), 按照 HTTP 响应的格式来解析这个响应. 并且把 body 中的数据按照一定的格式显示在浏览器的界面上
三、HttpServletRequest 对象的使用
图源http://c.biancheng.net/servlet2/httpservletrequest.html
Servlet 的生命周期主要为三个步骤初始化 init() 、业务处理 service() 、 销毁 destory()
1.读取请求信息
@WebServlet("/req-info")
public class GetRequestInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
System.out.println("HTTP 方法: " + method);
System.out.println();
String requestURI = req.getRequestURI();
System.out.println("requestURI: " + requestURI);
StringBuffer requestURL = req.getRequestURL();
System.out.println("requestURL: " + requestURL.toString());
String pathInfo = req.getPathInfo();
System.out.println("pathInfo: " + pathInfo);
String contextPath = req.getContextPath();
System.out.println("contextPath: " + contextPath);
String servletPath = req.getServletPath();
System.out.println("servletPath: " + servletPath);
String pathTranslated = req.getPathTranslated();
System.out.println("pathTranslated: " + pathTranslated);
String queryString = req.getQueryString();
System.out.println("queryString: " + queryString);
System.out.println();
String serverName = req.getServerName();
System.out.println("serverName: " + serverName);
int serverPort = req.getServerPort();
System.out.println("serverPort: " + serverPort);
String scheme = req.getScheme();
System.out.println("schema: " + scheme);
String protocol = req.getProtocol();
System.out.println("protocol: " + protocol);
System.out.println();
// 请求头们(本质就是一组 Key-Value;不同之处 Key 是可以重复的)
Enumeration<String> headerNames = req.getHeaderNames();
// Enumeration 类似 Iterator(hasNext() + next())
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = req.getHeader(name);
System.out.println(name + " := " + value);
}
}
}
2.读取请求体
GET方法是不携带请求体的,我们需要写一个POST方法来获取请求体
send-post.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="/req-body">
<input type="text" name="username">
<input type="text" name="password">
<button>提交</button>
</form>
</body>
</html>
3.1按字节读取
@WebServlet("/req-body")
public class ReqBodyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
InputStream inputStream = req.getInputStream();
byte[] buf = new byte[1024];
int n = inputStream.read(buf);
// byte[] -> String 字符集解码
String reqBody = new String(buf, 0, n, "UTF-8");
System.out.println(reqBody);
}
}
3.2按字符读取
@WebServlet("/req-body")
public class ReqBodyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BufferedReader reader = req.getReader();
char[] buf = new char[1024];
int n = reader.read(buf); // n 单位是字符
String reqBody = new String(buf, 0, n);
System.out.println(reqBody);
}
}
我们输入的时候输入的是中文字符,为什么输出就会变成乱码?
事实上并不是乱码,是URL编码
四、HttpServletResponse 对象的使用
1.读取网页中用户填写的信息
- GET方法的参数放在query string中
- POST方法的参数在query string 和 request body中,必须是form表单提交的POST请求,请求的Content-Type 是 application/x-www-form-urlencoded类型
1.1获取GET请求信息 --- form表单姓名密码
1.请求参数放在URL的query string中,格式是name=value&password=value
2.value不管是中文还是英文都可以正常读取
3.如果只有name或者password没有value读到的是一个空字符串
4.如果name或password都没有那么读取到的是null
@WebServlet("/get-param")
public class GetParam extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("|" + username + "|");
System.out.println("|" + password + "|");
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="get" action="/get-param">
<input type="text" name="username">
<input type="text" name="password">
<button>提交</button>
</form>
</body>
</html>
1.2获取POST请求信息 --- form表单姓名密码
1.请求参数可以放在URL的query string中,也可以放在request body中,用form表单提交
2.value是英文可以正确读取,如果是中文需要设置请求的字符集 设置请求的字符集编码 req.setCharacterEncoding("UTF-8");
3.如果只有name或者password没有value读到的是一个空字符串
4.如果name或password都没有那么读取到的是null
@WebServlet("/post-param")
public class PostParam extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("|" + username + "|");
System.out.println("|" + password + "|");
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="/post-param">
<input type="text" name="username">
<input type="text" name="password">
<button>提交</button>
</form>
</body>
</html>
输入的是中文为什么会出现乱码?
Tomcat在解析时是不知道请求体的字符集编码是什么,只能按照默认的编码,默认的字符集编码不是UTF-8的所以会出现乱码,在代码中加上req.setCharacterEncoding("UTF-8"); 设置请求的字符集编码
1.3标准的读取参数
public class StandardParam extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8"); // 虽然对 get 没用,但写上没副作用,所以一律都写上
String username = req.getParameter("username");
// String.trim() 去除字符串两边的空格 " hello ".trim() -> "hello" " ".trim() -> ""
if (username == null || username.trim().isEmpty()) {
// 用户没有正确地填写需要的信息
// 进行一定的错误处理
// 比如:响应 400 或者 重定向其他页面
}
// 正常地使用 username 了
// 读取到的 value 一定 String 类型,如果需要其他类型,比如 int,需要使用 Integer.parseInt(...) 转换
// 但是转换的时候可能出异常 Integer.parseInt("hello") 就会异常
// 如果还有其他的严格要求,需要代码自行处理
}
}
五、GET方法和POST方法的区别
post请求和get请求都是HTTP的请求方式,本质上来说并无区别,底层实现都是基于TCP/IP协议。
1. 根本区别就是语义不同,GET代表的是获取的语义,POST代表的是提交的语义
2.GET请求放在查询字符串中,POST请求放在查询字符串或者请求体中(由form表单提交),GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。
由于各个浏览器的实现,URL的长度限制短于请求体的限制,URL一般会记录在日志中,请求体不会记录日志,POST会比GET稍微安全一点,要想实现真正的安全,使用HTTPS协议
3.为了提升访问速度,HTTP响应支持缓存,GET请求支持缓存,POST不支持缓存
4.GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。