目录
2)如何表述我们请求哪个资源—通过URL来描述网络上的唯一一个资源
eg:通过代码,实现一个发送一次请求+读取并打印响应的HTTP客户端
JSON:JavaScript Object Notation ——JS对象记号法(JS表示对象的子集)
EG:ajax.html包含标签,js/script.js中写了ajax代码,请求了data/students.json的资源,对于这一过程的分析
1)构建发送GET /data/students.json请求
2)构建发送POST /data/students.json请求
Servlet:Servler let(服务器 小物件)—官方提供的标准
1)通过继承HttpServlet抽象类,完成我们自己的类(每个都是一个独立的Web资源)
2)重写类中方法(get post...),完成输出资源内容的过程
5.1HttpServletRequest req对象是对HTTP请求的对象化
5.2HttpServletResponse<—>HTTP响应
5.3HttpServlet是什么以及Servlet的生命周期
一、HTTP协议的概念
hyper Text Transmission Protocol
服务器上的进程一般被称为web服务器/HTTP服务器,浏览器进程和web服务器【Web应用是由一个或多个Web资源共同组成的,浏览器进程和Web服务器进程通常不在一台主机上,需要借助网络通信】之间的通信使用的是HTTP协议(应用层协议:1.基于某个特定的传输层协议之上;2.描述业务;3.非OS的代码),它是超文本传输协议。
1.这里了解的是基于TCP协议之上的HTTP协议。早期HTTP协议是基于TCP的(1.0、1.1),但现在最新版本的HTTP协议已经有开始做UDP的尝试了。
2.HTTP协议主要是一种文本协议,文本协议的好处:1.对人类友好;2.对程序不友好/性能差
客户端(浏览器进程/Postman/curl...) | 服务器(Web服务器) |
C->S:请求(Request) | |
S->C:响应(Response) | |
协议分成两个方向:客户端找服务器;服务器找客户端 |
3.HTTP协议以资源为基本单位:一次HTTP请求-响应,只能获取到一个Web资源/每个资源都有独立的HTTP请求。
一次请求: | 浏览器进程向web服务器进程请求一个web资源 |
一次响应: | web服务器进程给回浏览器进程一个web资源 |
二、HTTP协议的格式
HTTP 是一个文本格式的协议. 可以通过 Chrome 开发者工具或者 Fiddler(针对HTTP协议使用的一个抓包工具)抓包, 分析 HTTP 请求/响应的细节。
为什么 HTTP 报文中要存在 "空行"?因为 HTTP 协议并没有规定报头部分的键值对有多少个. 空行就相当于是 "报头的结束标记", 或者是 "报头和正文之间的分隔符". HTTP 在传输层依赖 TCP 协议, TCP 是面向字节流的. 如果没有这个空行, 就会出现 "粘包问题".
1.HTTP请求
1)需要C->S(客户端告诉服务器)什么内容?
a.请求哪个Web资源
b.对资源做哪些操作
c.关于请求本身的元信息
d.携带的内容(浏览器有时候携带一份内容到服务器)
2)如何表述我们请求哪个资源—通过URL来描述网络上的唯一一个资源
需要一种统一的规范给资源命名,叫做统一资源标识符URI(Unique Resource Identifier)。URI只是一种统一的描述,没有具体格式。实际中,应用的是一种具体实现的URI,被称为统一资源定位(Unique Resource Location URL),就是俗称的网址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
a.URL的具体格式
a.URL设计之初,不仅仅为HTTP协议使用。所以,需要标识出本次资源对应的协议(protocol/schema)
b.资源在哪台主机上:域名(domain)或者ip来体现(主机(host))
c.和主机上的哪个进程去获取资源:端口(port)
d.具体定位到是该进程管理的哪个资源:资源路径/路径
e.针对本次请求,除了资源本身之外的特殊要求:1)查询字符串 query string;2)文档片段:fragment
完整URL=协议+主机+端口+资源路径+请求字符串+文档片段
http:// | 本次资源是http协议 | |
www.example.jp | 域名,代表的是主机的问题,资源在哪个主机上 | 当协议是http协议&&port==80,80是可以省略的【基本上每种应用层协议都有默认端口,当端口是默认端口时可以省略端口号。http默认80,https默认443】 |
:80 | 端口,代表正在监听80端口的进程,一般就是web服务器进程 | |
/dir/index.html | 路径(path),代表HTTP管理的哪个资源 | 1.文件名是index.html;index.php;index.jsp...时,大部分web服务器兼容不写文件名 2.有绝对路径与相对路径。绝对路径以被web进程管理的资源的根目录为根 |
?... | 查询字符串,习惯上叫请求参数 | 具体格式:问号开头,一个或一组key-value对,如果是多个,用&进行分割。该内容不是必须的,可以没有 |
#... | #...开头叫做文档片段,代表要打开文档的哪部分 |
b.URL中可以省略的部分
1.出现在html中时,协议可以省略
在example.html中有:
<a href="//www.baidu.com/index.html">百度</a>
协议等同于请求example.html时的协议(用哪个协议请求的example.html就要用哪个协议请求下一个数据)
2.协议的默认端口可以省略:http默认端口80,https默认端口443
3.主机部分可以省略
example.html <a href ="http:///index.html">some</a>
主机等同于请求example.html时的主机
4.可以完全省略路径前的部分
example.html <a href="/hello.html">some</a>
协议、主机、端口都等同于请求example.html时的
5.查询字符串和文档片段可以省略
6.路径是index系列时可以省略文件名
3)请求的格式/组成⭐⭐
1.请求的操作是哪种操作:获取(GET)内容还是提交(POST)内容
2.资源是哪个资源
3.关于本次请求的元信息
4.可能存在的内容部分
请求=请求行+请求头们+可能存在的请求体(request line+request headers+[request body])
请求行=请求方法+资源URL(一般省略为路径)+请求版本\r\n
请求头们=请求头\r\n+请求头\r\n+...+请求头\r\n+\r\n(空行)
请求头=请求名字:请求头的值
请求头的格式:HTTP协议没有规定
表现在url:eg:请求GET:
请求行:GET / HTTP/1.1\r\n(\r\n是回车的意思)
请求行=请求方法+资源URL(一般省略为路径)+版本\r\n。
请求方法:GET(获取资源)、POST(发布资源)、其他( PUT、DELETE、CONNECT、OPTION)
请求头信息:
Host:www.baidu.com\r\n ——请求头
Connection:close\r\n ——请求头
每行都是一个独立的请求头,可以有多个请求头
空行(\r\n)表示请求头结束
请求体:可能存在可能不存在,并且没有格式规定
eg:通过代码,实现一个发送一次请求+读取并打印响应的HTTP客户端
package HTTP;
import java.io.*;
import java.net.Socket;
/**
* 实现HTTP客户端,观察请求效果
**/
public class HTTPClient {
public static void main(String[] args) throws IOException {
//1.HTTP客户端,要发送HTTP请求,先建立TCP连接
Socket socket=new Socket("www.baidu.com",80);
OutputStream os=socket.getOutputStream();
PrintWriter writer=new PrintWriter(new OutputStreamWriter(os,"UTF-8"));
String request="GET / HTTP/1.0\r\n\r\n";//一个请求行结束
//2.接下来写请求头部分(可以一个请求头都没有)
//3.发送请求
writer.print(request);
writer.flush();//刷新
//4.读取百度返回的HTTP响应
InputStream is = socket.getInputStream();
byte[] buf=new byte[10240];//我们知道响应不会超过10240字节
int n=is.read(buf);
String response=new String(buf,0,n,"UTF-8");
System.out.println(response);
}
}
2.响应S->C
1)服务器要告诉客户端哪些内容
1.请求结果:成功or失败,失败原因
2.本次响应的元信息
3.本次资源内容(如果有)
2)响应格式
响应行=响应版本+状态码(为什么需要:给计算机用)+状态描述(给人用)
3)响应的结果:5种情况(3位数)
1XX:请求进行时
2XX:成功
200 OK:说明请求/响应成功
206 Partial OK:部分成功
3XX:应用缓存/重定向
4XX:失败,问题是发生在客户端这边(客户端/用户问题)
404:Not Found,请求的资源路径不存在
405:Method Not Support,方法不支持
5XX:问题发生在服务器这边(Web服务器或我们的代码问题)
505:Internal Server Error,服务器内部错误
3.Content-Type(内容类型)
Content-Type:内容类型 ——》 请求体/响应体的格式
eg:请求的内容是html的,怎么让浏览器知道请求是html的呢?
需要通过一个标识去告诉,即通过Content-Type去告诉:Content-Type:text/html ,text代表它是一个文本内容,这个文本内容具体格式是html,有时候会写成:Content-Type:text/html ;charset=utf-8,代表它是一个文本内容,这个文本内容具体格式是html,它的字符集编码是UTF-8的。
内容是html | Content-Type:text/html |
Content-Type:text/html;charset=utf-8 | |
内容是css | Content-Type:text/css |
JavaScript | Content-Type: application/javascript |
text/javascript | |
纯文本(无格式) | text/plain |
jpg图 | image/jpeg |
png图 | image/png |
JSON格式 | application/json |
text/json |
JSON:JavaScript Object Notation ——JS对象记号法(JS表示对象的子集)
1.JSON格式:js对象的子集,key必须用引号引起来,并且数据类型中不能有函数。
2.JS中如何解析JSON字符串:JSON.parse(jsonString)-》变为数组形式
eg:{"key":"value","k2":"v2"}:key必须是String,value可以是数字、字符串、布尔、数组、对象。
4.Content-Length(内容长度)
如果存在请求体或者响应体,Content-Length就表示它的长度。
(TCP是面向字节流的,应用层协议设计时,需要自行考虑在协议中考虑解包信息)请求行和请求头是通过\r\n分隔开的,只要读到\r\n的肯定请求行就读完了,请求行中间怎么分:空格url空格版本号。请求行和请求体通过空行(\r\n)分割,每个请求头之间也通过\r\n分割
eg:写一个HTTP服务器(是个TCP被动打开方)
package HTTP;
import udp.dictionary_service.Server;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* @author 美女
* @date 2022/06/05 21:11
**/
public class HTTPServer {
public static void main(String[] args) throws IOException {
//我们使用短连接
ServerSocket serverSocket = new ServerSocket(81);
while (true) {
try {
Socket socket = serverSocket.accept();
//不管对方发给我们的请求是什么,一律用统一的响应回复对方
OutputStream os = socket.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, "UTF-8"));
String html = "<a href='https://www.baidu.com/'>百度一下</a>";//我们的响应体
//响应体的长度
byte[] bytes = html.getBytes("UTF-8");
int contentLength = bytes.length;
String response = "HTTP/1.0 200 OK\r\n" +
"Content-Type: text/html; charset=utf-8\r\n" +
"Content-Length:49\r\n" +
"\r\n" +
html;
writer.print(response);
writer.flush();
socket.close();
}catch (IOException exc){
exc.printStackTrace();
}
}
}
}
三、一个具体的Web服务器的使用—Tomcat服务器的使用
控制权反转:之前的代码,都是自己写main方法,控制权一直在我们手中。进入Web后,也是自己写main方法,但是会调用第三方库(别人写的代码)。Tomcat是别人写好的main方法,我们的代码是被Tomcat的代码反向控制的。
Tomcat服务器管理各种本地资源,当浏览器和它通信时要哪个资源他就想办法把哪个资源给到浏览器上去。
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>first-webapp</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
src/main下的webapp目录下放静态资源,WEB-INF下是资源的配置文件
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">
</web-app>
Servlet下,一个资源的路径=Context Path(Appliation Path)+Servlet Path
1.静态资源和动态资源
虽然资源的格式和动态或静态无关,但一般来说,html、css和js都是以静态资源处理为主。动态一般可用在前后端分离的生产关系(动态JSON格式)。
2.JS中的ajax
Asynchronous JavaScript and XML,提供JS可以发起HTTP请求的能力
通过js代码来自己发送http请求,同一个域名下才有权限【之前是通过a,form标签间接引导浏览器帮我们发送HTTP请求;同一个域名下才有权限是指百度的js不能给腾讯的后台去发ajax。域名纯粹就是字符串对比】
JS的功能:通过修改DOM树进而影响到用户看到的内容
浏览器要数据,要么用户给提供(prompt();<input>),要么服务器给提供(需要HTTP协议进行通信)。
json数据:
相对路径(绝对路径前面有斜杠)
发起ajax请求:
var xhr=new XMLHttpRequest();//利用这个对象发起HTTP请求
xhr.open("get","/data/script.js");//这里传的是绝对路径,/data是ContextPath
xhr.send();//发送HTTP请求
上面两行打印打印的是字符串,我们需要的是数组形式,所以要把json字符串解析成js的具体数据类型(数组),这样应用DOM操作想加东西就可以方便添加,这就是典型的前后端分离了,前端去读取数据,后端只需要准备好json数据给前端读即可。
【分析过程:由于html中有<script src="..."></script>标签,使得浏览器自动请求..js资源,并执行js代码,在js代码中,由于我们执行了xhr代码,会让浏览器发起"..json"的请求,当拿给数据时(响应完成时),触发xhr对象的load事件(.. onload=...),导致函数被调用,函数中针对响应进行打印(事件驱动的方式读取响应)。】
总结
1)要让浏览器通过Tomcat访问Web资源,而不是直接让浏览器打开资源,表现在url上:http://127.0.0.1:8080/路径(浏览器在和127.0.0.1主机上的绑定8080端口的进程通信(进程保证是tomcat的))
D:/某目录/路径:浏览器直接打开资源
http://localhost:63342/路径:浏览器在和127.0.0.1主机上绑定的63342的进程通信(这个进程时IDEA内部内置的一个服务器,不是tomcat)
2)一个资源的路径=Context Path+Servlet Path
Servlet Path:资源相对于Webapp的目录(静态资源)
Context Path修改了需要重启Tomcat
EG:ajax.html包含<script src="js/script.js"></script>标签,js/script.js中写了ajax代码,请求了data/students.json的资源,对于这一过程的分析
js包下有script.js,html关联了这个js,在script中又通过ajax(给了HTTP请求的能力)请求了data包下的students.json的资源。
过程中有三个行为主体:用户、浏览器(进程)、Tomcat(进程)
1)用户在浏览器中输入.../ajax.html回车
2)浏览器分析用户输入,构建HTTP请求(GET请求(当在浏览器中输入url回车后,构建的是GET请求))【此时就有请求从浏览器发向了web服务器,请求的地址是../ajax.html,然后浏览器就开始等待Tomcat应答】
3)Tomcat收到请求,根据资源路径,找到对应文件(放在Webapp/ajax.html)读取内容,准备响应并发送(200 Content-Type:text/html)【tomcat通过响应回复浏览器,控制权又到了浏览器手中】
4)浏览器读取响应体,根据Content-type决定按照html格式对待来的内容,即开始解析html中的标签。
5)在解析过程中看到了<script src="..."></script>标签,此时浏览器开始构建新的HTTP请求(GET url是/js/script.js)
6)Tomcat根据资源路径找到对应文件,读取内容,准备HTTP响应(200 Content-type:application/javascript)
7)浏览器读取响应体,根据Content-type执行js代码(js中有ajax),实例化xhr对象,send()这个过程会再次引起HTTP发送一个请求(ajax提供JS可以发起HTTP请求的能力),浏览器构建HTTP请求(GET url是/data/student.json)
8)tomcat根据资源路径找到对应文件,读取内容,准备HTTP响应(200 Content-type:application/json)
9)浏览器读取响应,执行js代码中写的xhr.onload函数(onlad:以事件驱动的方式读取响应)【JSON.parse(this.responseText):json字符串解析成具体的数据类型(遍历对象+修改DOM树(本身是字符串类型,改成数组方式了))】
10)浏览器看到最终DOM结构,显示内容
11)用户看到网页的最终形式
【由于过程中有三次请求响应,在开发者工具网络面板中可以看到3行】
3.常见的错误
1)网络层是否正常:依靠ping命令排查
我们浏览器所在电脑到要访问的主机那台电脑之间网络通不通
2)传输层是否正常:能否连接到对应端口
依靠telnet命令排查/自己写一个TCP客户端尝试。网络层没问题但传输层不通可能有几种情况:1.防火墙在起作用;2.端口没有被进程监听(该启动的服务器没有启动)
eg:浏览器无法建立到127.0.0.1:8080的TCP连接->127.0.0.1:8080端口没有绑定进程->Tomcat没有启动
3)应用层是否正常(202 404 405 500)
404问题:
1.排查8080端口是否被tomcat正确监听了
2.资源路径写错:a.单词拼错;b.Context Path写错;c.Servlet Path写错;d.问问自己Tomcat重启了吗;e.放在WEB-INF下的不是资源;f.相对路径导致的问题(确定自己身在何处,如js被html包裹的所以是跟着html路径看,html在webapp目录下所以js目录下写的相对路径data/student.json正确(data是Context Path))
4)页面白面或没有按照预期显示
针对html
1.用开发者工具查看元素界面(浏览器看到的文档),查看元素是否符合预期结构
IDEA看着没问题,但就是不对:a.IDEA的自动保存无意之中关了,所以写了没有保存;b.保存之后index.html打包到另一个目录下index.html;c.浏览器如果有缓存功能,浏览器读取的还是老的html。
2.如果我们的代码是由JS动态生成的:a.控制台有无报错;b.JS资源是不是200;c.JS对了,检查JS内容是不是最新的;d.ajax是不是200;e.ajax的响应是否符合预期;f.检查数据(eg:student.json中写的数组内容)是否符合预期
总结下来就是排查一下业务问题:请求的资源是否请求成功?JS运行是否正常?是否有缓存?是否忘记保存?
其他知识:Maven打包:
pom.xml中:<packaging>war</packaging> //打包成war包
jar包:Java ARchive war包(web上用的): Web app ARchive
4.如何构建发送GET/POST请求
1)构建发送GET /data/students.json请求
a.在地址栏输入url回车
b.通过一些标签:<img> <script> <link>会触发GET请求
这些标签里面都有路径src="...",浏览器读取到html构建请求tomcat返回响应后进一步会读到这些标签,就开始构建新的GET请求了。
c.其他标签:<a>、<form>
d.ajax:提供JS可以发起HTTP请求的能力(通过js代码来自己发送http请求,同一个域名下才有权限)
e.重定向
2)构建发送POST /data/students.json请求
a.form标签:<form method="post">
b.ajax
c.重定向redirect
GET和POST的区别
四、如何使用servlet技术开发动态资源
1.为什么需要动态资源
在某些资源路径固定的情况下,我们需要对应不同的环境、条件、产生不同的内容
Servlet:Servler let(服务器 小物件)—官方提供的标准
属于JavaEE标准。对于代码来说,就是一组抽象类或标准
Servlet当下应用如何?
基本不用,现在以学习SpringMVC为主(也是基于Servlet而诞生)
2.大体的开发流程
整个过程需要遵守标准,否则Tomcat不会认
1)通过继承HttpServlet抽象类,完成我们自己的类(每个都是一个独立的Web资源)
2)重写类中方法(get post...),完成输出资源内容的过程
3)把动态资源和路径建立绑定关系
HelloServlet<—>/hello
a.通过修改web.xml做绑定
b.使用Java中的注解语法完成绑定:@WebServlet
我们只需写类,建立绑定关系,不需要:
1)不需要我们自己实例化HelloServlet对象(Tomcat内部在实例化)
2)我们也不需要去调用我们写好的get post方法(Tomcat内部会在合适的时机去调用)
动态资源同意写在main的java包下
/**
* 类名无所谓,但是尽量按照一定规范命名
* 1.声明这个类是一个Web资源:通过让这个类继承HTTPServlet这个抽象类
* 2.看我们的动态资源支持哪些HTTP方法:eg:支持doGet方法,通过重写doGET方法
**/
//6把动态资源和一个资源路径建立绑定关系:通过@WebServlet注解修饰类,来完成绑定
@WebServlet("/first") //Servlet path【如果配置了context path在浏览器输入时还得加上这个路径】
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//3.读取req对象中的信息(都是来自HTTP请求中的信息)
//4.根据我们当下的条件
//5.准备响应数据(通过填写resp响应对象来做到)
// eg:203 text/plain "你好世界" charset=utf-8
resp.setStatus(203);//设置状态码
resp.setContentType("text/plain;charset=utf-8");
PrintWriter writer= resp.getWriter();
writer.print("你好世界");
}
}
分析:
1.@Webservlet("/third"):把资源和路径绑定;
2.extends HttpServlet:表示我们这个类是一个有效的Web资源(动态)了;
3.doGet:此资源支持HTTP GET方法访问,隐含其他HTTP方法是不支持的,如果不是GET方法访问这个资源,会405;
4.resp.setStatus(203).....writer.printer("..."):根据请求or其他条件构造响应的过程,相当于动态资源例子中写书的过程。
3.Tomcat内部的大概过程
1)Tomcat收到了一个HTTP请求
2)Tomcat根据HTTP协议的规定,解析请求,得到诸如:请求方法、资源路径、请求头们、请求体(如果有)
3)Tomcat根据资源路径(Path=Context Path+Servlet Path)
a.先根据Context Path决定这个资源交给哪个Webapp处理(Tomcat支持多个webapp同时存在)
b.当确定是哪个webapp后,再根据Servlet Path去找到对应的资源进行处理:
b.1:如果是动态资源,根据路径得到一个类名(com.wy.servlet.FirstServlet),如果这个类还没有实例化对象,则实例化对象,否则直接获取对象(单例模式)——拿到对象。根据请求方法,调用该对象doGet或者doPost或者其他方法。
b.2:doGet方法执行结束后,resp对象被填充信息(203 text/css 你好陕西),Tomcat根据这个对象,构造HTTP响应,发送给浏览器。
b.3:如果是静态资源,就根据对应的路径去查找相应文件,并响应文件内容。
b.4:如果动态or静态资源都没有找到,就会404
4.Tomcat的定位
5.Servlet的使用
HttpServletRequest类的方法了解(配合http请求格式理解)。HTTP请求对应出来就是HttpServletRequest对象,这里的方法以getXXX()为主。同理HttpServletResponse<—>HTTP响应,这里的方法以setXXX()为主。
5.1HttpServletRequest req对象是对HTTP请求的对象化
1)HTTP请求中有哪些信息?
请求方法;资源路径(url);请求版本;请求头们;请求体(如果存在)
这些信息都可以通过req对象获取到,有些是直接获取,有些是被Tomcat加工成更方便我们的格式。
2)通过req对象获取请求方法、请求路径、请求版本、请求头
@WebServlet("/req-info")
public class GetRequestInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//我们只做打印(在IDEA控制台上看到显示),不做响应(浏览器看不到,白屏)
//请求方法
String method = req.getMethod();
System.out.println("HTTP方法:"+method);//请求到的是GET方法
System.out.println();
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();//context路径(+servlet路径才......)
System.out.println("contextPath: " + contextPath);
String servletPath = req.getServletPath();//servlet路径
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();
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);
//请求头们(本质是一组key-value;不同之处:key可以重复)
final 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);
}
}
}
3)通过req对象读取请求体(了解)
GET方法"不携带"请求体,所以需要准备POST方法(form表单 ; ajax两种方式可以准备)
准备:携带请求体的POST请求:准备一个html,里面有form表单,通过form表单法案送HTTP POST请求。准备一个动态资源,仅支持POST方法,打印读取到的请求体(PS:这个动态资源不能用GRT方法访问)
form标签:
<!DOCTYPE html>
<html lang="zh-hans">
<head>
<meta charset="UTF-8">
<title>发送 HTTP POST 请求</title>
</head>
<body>
<form method="post" action="/first-webapp/req-body"><!--提交后要跳转到这个路径下,注意路径正确(资源路径=Context Path+Servlet Path)-->
<input type="text" name="username">
<input type="text" name="password">
<button>提交</button>
</form>
</body>
</html>
读取请求体:
//读取请求体
@WebServlet("/req-body")
public class ReqBodyServlet extends HttpServlet {
// 重写了 doPost 方法,所以我们这个资源支持了 POST 方法,但不支持 GET 请求
// 不能直接在浏览器里直接输入这个 URL 回车,会看到 405 的
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//方法1(以字节的形式读进来的)
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);
// //方法2(以字符形式读取)
// 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);
}
}
此时拿到请求体(注意路径问题:form表单的跳转)
URL编码(encode)和URL解码(decode)
由于URL中支持的字符是有限的,所以一些特殊字符(比如&、空格、加号等)和中文需要经过URL编码。
URL编码的大体过程:
1.将字符经过一定的字符集(通常是UTF-8)编码
比如:&->0x26 我->0xE68891
2.将每个字节都用%做前缀
反之就是URL解码:
5.2HttpServletResponse<—>HTTP响应
以setXXX()为主,可设置:
1.状态(状态码和状态描述)【响应行的信息(版本不归我们管,属于tomcat管)】
2.响应头
3.响应体
1)响应头和状态码的设置
@WebServlet("/set-resp")
public class SetRespServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应状态
resp.setStatus(202);
//设置响应头
//响应头的Name(不区分大小写):
// 1.标准响应头,比如:Content-Type(ps:Content-Length不需要我们设置)
// 2.非标准响应头,填什么都可以(中文不行)[一般用X开头]
resp.setHeader("Content-Type","text/plain;charset=utf-8");
resp.setHeader("X-My-Class","java-19");
}
}
2)响应体的设置
@WebServlet("/set-resp")
public class SetRespServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应状态
resp.setStatus(202);
// //响应体可以响应两种东西
// //1.响应体的内容是字符(使用较多)
// resp.setCharacterEncoding("utf-8");//设置响应字符集(不设置可能会乱码)
// resp.setContentType("text/plain");//也得设置,不然也是乱码
// PrintWriter writer= resp.getWriter();//设置响应字符集要在getWriter之前
// writer.print("你好");
//2.响应体的内容是字节流
OutputStream outputStream = resp.getOutputStream();
outputStream.write('H');
outputStream.write('e');
}
}
5.3HttpServlet是什么以及Servlet的生命周期
1.Servlet标准大概规定的是一个请求-响应服务器模型(不仅仅针对HTTP协议)
2.Servlet下有一个接口:interface Servlet,HttpServlet是一个抽象类(abs class HttpServlet),抽象类实现了接口。我们写的MyServlet又继承自HttpServlet,所以实际上我们也实现了这个接口。
3.Servlet的生命周期被分为三个方法/三个阶段
1)初始化阶段 | 被抽象成init(...)方法 | 有且仅有一次 |
2)处理请求-响应阶段 | service(req,resp);方法【不只是http请求响应,也可以是其他协议的】 | 每次请求-响应都会执行一次 |
3)销毁阶段 | destroy(); | 有且仅有一次 |
service是Servlet接口的,跑到HttpServlet,被其实现之后,把service变成了http的格式,也就是变成了doGet/doPost...
doGet是被service调用的,相当于就是HttpServlet类重写了service方法,内部操作就是根据请求的方法决定是调用doGet还是doPost还是其他... 【我们虽然一般说的是调用doGet,doPost,背后实际都是service在调用】
源码:
观察执行过程:
/**
* 观察servlet的生命周期
*/
@WebServlet("/lifecycle")
public class LifecycleServlet extends HttpServlet {
public LifecycleServlet() {
System.out.println("Lifecycle 的构造方法被调用");
}
@Override
public void init() throws ServletException {
System.out.println("Lifecycle.init() 被调用");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("Lifecycle.doGet() 被调用");
}
@Override
public void destroy() {
System.out.println("Lifecycle.destroy() 被调用");
}
}
//启动tomcat,没有打印,还未被实例化(懒汉模式,用到的时候才实例化)
在浏览器输入url:..../liftcycle,第一次启动,所以回车之后会有3个方法被执行(构造方法、init(...),doGet方法会被执行一次,所以有三个打印):
再次请求(输入这个url回车),构造方法就不会执行了(单例模式只会执行一次构造方法),init也不会执行了,只会执行doGet():【只要刷新就有doGet()】
观察到的现象总结:
1)init();service();destory()的被调用次数与被调用时机
2)我们的类是被Tomcat的代码实例化的
3)我们写的方法是被Tomcat代码调用的
4)我们的对象是单例模式管理的
5)Tomcat使用的是懒汉模式创建的单例对象
6)doGet、doPost等方法都是在HttpServlet中重写service然后调用的
7)我们的对象是运行在Tomcat启动管理的线程池中的
8)javax.servlet.*下的类是官方提供的;org.apache.catalina.*下的类是tomcat提供的