Java开发网络相关知识

Web相关

本文整合了Java工程师所需要用到的Web开发相关的基础知识,供参考

一、基本概念

1、mac地址、ip地址

终端输入ifconfig en0可以查看

image-20210420171424350

其中ether指的就是mac地址、inet 地址就是ip。

mac地址也叫物理地址和局域网地址,主要用于确认网上设备的地址,类似于身份证号,具有唯一标识,每一个网卡制作完成之后就带有一个mac地址,永远都不会改变。

ip地址,类似于你的现住址,是标记你在网络中的具体位置,一个网卡的ip地址是可以改变的。

2、计算机之间是怎么连接的

2.1 双绞线

如果只是两台计算机,我能就可以使用双绞线(网线)连载一起,就能互相发送消息,组成一个小网络。

2.2 集线器

如果有多台计算机,可以使用hub,叫做集线器,一个电脑发送信息到集线器,集线器负责广播到其他的计算机。

image-20210420171958113

2.3 交换机

由于集线器的问题,我们经常需要发送信息到特定的计算机而不是广播,所以一个新的设备就出现了叫做交换机(switch)。

交换机可以记录每一个设备的弟子和接口的对应关系。

思考问题,交换机要将内容发送给指定的计算机,那么内部一定维护了一张表,记录了哪个电脑链接了我的哪个口。交换机只能识别MAC地址。MAC地址是物理地址,ip地址交换机并不感兴趣

image-20210420172038615

思考一个问题:

交换机啊是怎么知道这个表的:

image-20210420172110207

找不到就全员广播,交换机效率比较高,而且可以进行桥接。

image-20210420172144325

2.4 路由器

思考:使用交换机可以建立一个超大型的网络吗?

一般的交换机的地址表也就能存个几千个地址,当网络内的设备多起来以后,只要交换机找不到对应设备就会广播,地址表如果满了,新地址还会覆盖就地址就会导致重新寻找效率比较低。所以有引入了一个设备叫路由器,谁也听过的一个设备,一般家里都有。

注意:路由器不是猫,猫是调制解调器,调制解调器的作用是将进来的光信号转化为电信号

于是提出了以下的设计

image-20210420172236973

这里就有了网络的概念了。以上的几种,哪怕是交换机的桥接也没有设计ip地址这个概念,都是基于mac地址进行数据传输。这里有了网络这个抽象概念之后ip地址就应用而生了,IP地址只要是用来表示计算机的网络位置,他处于哪一个网络。IP地址和子网掩码共同帮助我们定位一个计算机在网络中的位置。

ip地址和子网掩码其实是个32位的二进制数字

image-20210420172310601

此时发送信息就会再包一个消息的头部

image-20210420172332366

家用的路由器,有个wan口,有好几个lan口,wan口用来来接互联网端,lan用来连接家庭设备,这连个口都有一个网卡,一个网卡属于互联网网络,ip可能是10.25.23.65,另一个属于内部网络比如192.168.0.1。内部网络和外部互联网的数据转发由路由器内部实现。

image-20210420172408830

路由器内有路由表。

网络内传输,有了网关的概念了。 ip不能直接进行传输,应为网络内的交换机不支持ip地址,所以通信要转化为mac地址,根据ip查找mac 地址。

非常重要的arp协议,要进行广播,问一问哪个mac地址的ip是192.168.0.5,他收到就会回应

来一个栗子,192.168.100.100 发送信息到 192.168.200.101

image-20210420172446386

第一步: 192.168.100.100 -> 192.168.200.101

利用地址解析协议(ARP)协议对应IP地址到MAC地址,因为交换机只认识MAC地址,同时在传输过程中MAC地址会一直变化,如下图所示,头部会变

image-20210421153534811

添加一条路由

route add -host 192.168.110.202 gw 192.168.120.202 
route del -host 192.168.110.202 gw 192.168.120.202
tcpdump -i eth1 host 192.168.1.123 -w /tmp/xxx.cap

ICMP(InternetControlMessageProtocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。

image-20210421104754669

image-20210421105345780

2.5 NAT协议

每台设备都分配一个ip当然能够实现网络的建立,但是事实上我们只有数量有限的IP地址,能给我们用的大概只有40亿左右,于是一般的做法是家里只有一个ip地址,所有的设备通过路由器连接。连接在内网。那么问题来了,你的手机的地址是192.168.0.10,别人家的也可能是一样的百度怎么区分呀?

image-20210420172704178

这个叫NAT协议 NAT(Network Address Translation,网络地址转换)。

现在家里的路由器都是带有交换功能的,能使用的IP地址 25.68亿 共有40多亿

3、ip地址的分类

1、A类IP地址

一个A类IP地址由1字节的网络地址和3字节主机地址组成,网络地址的最高位必须是“0”, 地址范围从 1.0.0.0 到126.0.0.0。可用的A类网络有126个,每个网络能容纳1亿多个主机。

2、B类IP地址

一个B类IP地址由2个字节的网络地址和2个字节的主机地址组成,网络地址的最高位必须是“10”,地址范 围从128.0.0.0到191.255.255.255。可用的B类网络有16382个,每个网络能容纳6万多个主机 。

3、C类IP地址

一个C类IP地址由3字节的网络地址和1字节的主机地址组成,网络地址的最高位必须是“110”。范围从 192.0.0.0到223.255.255.255。C类网络可达209万余个,每个网络能容纳254个主机。

4、D类地址

用于多点广播(Multicast)。 D类IP地址第一个字节以“1110”开始,它是一个专门保留的地址。它并不指向特定的网络,目前这一类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算 机,它标识共享同一协议的一组计算机。224.0.0.0到239.255.255.255用于多点广播 。

5、E类IP地址

以“11110”开始,为将来使用保留。240.0.0.0到255.255.255.254,255.255.255.255用于广播地址,全零 (“0.0.0.0”)地址对应于当前主机。全“1”的IP地址(“255.255.255.255”)是当前子网的广播 地址。

IP私有地址:

在IP地址3种主要类型里,各保留了3个区域作为私有地址,其地址范围如下:

A类地址:10.0.0.0~10.255.255.255

B类地址:172.16.0.0~172.31.255.255

C类地址:192.168.0.0~192.168.255.255

A类地址的第一组数字为1~126。注意,数字0和127不作为A类地址,数字127保留给内部回送函数,而数字0则表示该地址是本地宿主机,不能传送。

B类地址的第一组数字为128~191。

C类地址的第一组数字为192~223。

image-20210420172857859

4、域名DNS

你真的能记住每个网站的ip地址吗? 那我们输入的www.github.com是什么是域名,我们可以去阿里云等云服务提供商购买域名。

image-20210420173003852

image-20210420173025265

域名是需要解析的,指向一个ip地址

为了让你容易记忆引入了域名的概念,你不需要记忆,有人帮您记录

DNS解析

image-20210420173112302

4.1、国内的dns

第一名 114DNS:★★★★★

114DNS开启DNS高可靠服务时代的大幕。114DNS开始同时为公众提供高速、稳定、可信的DNS递归解析服务;为网站提供强大抗攻击能力的权威智能DNS解析服务;为ISP提供可靠的DNS灾备及外包服务,作为国内用户量最大的老牌DNS,访问速度快,各地区设有节点,负载各运营商用户,DNS防劫持能力,自然也是名列前茅。

DNS 服务器 IP 地址:

首选:114.114.114.114

备选:114.114.114.115

2017公共DNS服务器地址评估—DNS推荐

第二名 DNSPod DNS:★★★★★

DNSPod创始于2006年3月,是中国最大的第三方域名服务商,全球排名第四位。DNSPod是国内最早提 供免费智能DNS产品的网站,致力于为各类网站提供高质量的电信、网通、教育网双线或者三线智能DNS 免费解析,作为114DNS竞争对手之一,无论是访问速度,还是各地区节点覆盖率以及防劫持能力都是 顶级的。

DNS 服务器 IP 地址:

首选:119.29.29.29

备选:182.254.116.116

2017公共DNS服务器地址评估—DNS推荐

第三名 阿里 DNS:★★★★★

阿里公共DNS是阿里巴巴集团推出的DNS递归解析系统,作为国内最大的互联网基础服务提供商,阿里 巴巴在继承多年优秀技术的基础上,通过提供性能优异的公共DNS服务,为广大互联网用户提供最可靠 的面向互联网用户提供“快速”、“稳定”、“智能”的免费DNS递归解析服务。

DNS 服务器 IP 地址:

首选:223.5.5.5

备选:223.6.6.6

2017公共DNS服务器地址评估—DNS推荐

4.2、域名的分类

语种分类

英文域名、中文域名、日文域名、韩文域名等等。

现在我们常见的就是英文域名。

英文域名:baidu.com

中文域名:百度.com 、百度.中国、baidu.中国

其它语言域名同上。

地区分类

中国、美国、英国、日本等等。

在国内我们常用的就是.cn域名,.cn是中国大陆的国家一级域名。另外还 有.com.cn、.net.cn、.org.cn等等。

国内不同省市也有自己的顶级域名,例如内蒙古的顶级域名就是:.nm.cn。

美国国家顶级域名是.us 、日本的是.jp、香港的是.hk。

机构分类

.com 商业性的机构或公司

.org 非盈利的组织、团体 apache.org

.gov 政府部门 http://www.shanxi.gov.cn/

.net 从事Internet相关的的机构或公司

域名虽然有很多分类,但是我们平时在使用的时候,并没有过多的遵循这个原则。比如.com的也有 很多作为个人网站、.net很多也用来做了公司网站。

4.3、层级分类

顶级域名(一级域名)

baidu.com baidu.cn

二级域名

www.baidu.com jingyan.baidu.com

三级域名

wangshangyingxiao.club.1688.com hhz.bbs.53hui.com

二、网络七层协议

image-20210420173658725

image-20210420173722089

image-20210420173742375

image-20210609162902357

image-20210609163017056

image-20210609163330263

image-20210611114939511

image-20210611115531956

image-20210611115715297

1、Socket

image-20210610185953431

image-20210610191338814

image-20210610191651004

image-20210611102845612

image-20210611111622941

2、C/S & B/S

  • CS和BS含义: CS即Client/Server(客户机/服务器)结构。C/S结构在技术上很成熟,它的主要特点是交互性强、具有安全的存取模式、网络通信量低、响应速度快、利于处理大量数据。但是该结构的程序是针对性开发,变更不够灵活,维护和管理的难度较大。通常只局限于小型局域网,不利于扩展。并且,由于该结构的每台客户机都需要安装相应的客户端程序,分布功能弱且兼容性差,不能实现快速部署安装和配置,因此缺少通用性,具有较大的局限性。要求具有一定专业水准的技术人员去完成。 BS即Browser/Server(浏览器/服务器)结构,就是只安装维护一个服务器(Server),而客户端采用浏览器(Browse)运行软件。B/S结构应用程序相对于传统的C/S结构应用程序是一个非常大的进步。B/S结构的主要特点是分布性强、维护方便、开发简单且共享性强、总体拥有成本低。但数据安全性问题、对服务器要求过高、数据传输速度慢、软件的个性化特点明显降低,这些缺点是有目共睹的,难以实现传统模式下的特殊功能要求。例如:通过浏览器进行大量的数据输入或进行报表的应答、专用性打印输出都比较困难和不便。此外,实现复杂的应用构造有较大的困难。
  • 区别:
    • 1、开发维护成本 cs开发维护成本高于bs。因为采用cs结构时,对于不同的客户端要开发不同的程序,而且软件安装调试和升级都需要在所有客户机上进行。 bs只需要将服务器上的软件版本升级,然后从新登录就可以了。
    • 2、客户端负载 cs客户端负载大。cs客户端不仅负责和用户的交互,收集用户信息,而且还需要通过网络向服务器发出请求。 bs把事务处理逻辑部分交给了服务器,客户端只是负责显示。
    • 3、安全性 cs安全性高。cs适用于专人使用的系统,可以通过严格的管理派发软件。 bs使用人数多,不固定,安全性低。
    • 4、作用范围 Client/Server是建立在局域网的基础上的。Browser/Server是建立在广域网的基础上的。

3、可靠数据传输的原理

image-20210611154205977

image-20210611154251038

rdt具有不同的情况,见PDF第三章第七页

三、TCP/UDP/HTTP协议

1、tcp协议

传输依靠什么进行控制

image-20210420173839440

源端口和目的端口字段

  • TCP源端口(Source Port):源计算机上的应用程序的端口号,占 16 位。
  • TCP目的端口(Destination Port):目标计算机的应用程序端口号,占 16 位。

序列号字段

CP序列号(SequenceNumber):占32位。它表示本报文段所发送数据的第一个字节的编号。在TCP连接中,所传送的字节流的每一个字节都会按顺序编号。当SYN标记不为1时,这是当前数据分段第一个字母的序列号;如果SYN的值是1时,这个字段的值就是初始序列值(ISN),用于对序列号进行同步。这时,第一个字节的序列号比这个字段的值大1,也就是ISN加1。

确认号字段

TCP 确认号(Acknowledgment Number,ACK Number):占 32 位。它表示接收方期望收到发送方 下一个报文段的第一个字节数据的编号。其值是接收计算机即将接收到的下一个序列号,也就是下一个 接收到的字节的序列号加1。

数据偏移字段

TCP 首部长度(Header Length):数据偏移是指数据段中的“数据”部分起始处距离 TCP 数据段起始处 的字节偏移量,占 4 位。其实这里的“数据偏移”也是在确定 TCP 数据段头部分的长度,告诉接收端的应 用程序,数据从何处开始。

保留字段

保留(Reserved):占 4 位。为 TCP 将来的发展预留空间,目前必须全部为 0。

标志位字段

  • CWR(CongestionWindowReduce):拥塞窗口减少标志,用来表明它接收到了设置ECE标志的TCP包。并且,发送方收到消息之后,通过减小发送窗口的大小来降低发送速率。
  • ECE(ECNEcho):用来在TCP三次握手时表明一个TCP端是具备ECN功能的。在数据传输过程中,它也用来表明接收到的TCP包的IP头部的ECN被设置为11,即网络线路拥堵。
  • URG(Urgent):表示本报文段中发送的数据是否包含紧急数据。URG=1 时表示有紧急数据。当 URG=1 时,后面的紧急指针字段才有效。
  • ACK:表示前面的确认号字段是否有效。ACK=1 时表示有效。只有当 ACK=1 时,前面的确认号字 段才有效。TCP 规定,连接建立后,ACK 必须为 1。
  • PSH(Push):告诉对方收到该报文段后是否立即把数据推送给上层。如果值为 1,表示应当立即 把数据提交给上层,而不是缓存起来。
  • RST:表示是否重置连接。如果RST=1,说明TCP连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。
  • SYN:在建立连接时使用,用来同步序号。当 SYN=1,ACK=0 时,表示这是一个请求建立连接的 报文段;当 SYN=1,ACK=1 时,表示对方同意建立连接。SYN=1 时,说明这是一个请求建立连接 或同意建立连接的报文。只有在前两次握手中 SYN 才为 1。
  • FIN:标记数据是否发送完毕。如果 FIN=1,表示数据已经发送完成,可以释放连接。

窗口大小字段

窗口大小(Window Size):占 16 位。它表示从 Ack Number 开始还可以接收多少字节的数据量,也 表示当前接收端的接收窗口还有多少剩余空间。该字段可以用于 TCP 的流量控制。

TCP 校验和字段

校验位(TCPChecksum):占16位。它用于确认传输的数据是否有损坏。发送端基于数据内容校验生成一个数值,接收端根据接收的数据校验生成一个值。两个值必须相同,才能证明数据是有效的。如果两个值不同,则丢掉这个数据包。Checksum是根据伪头+TCP头+TCP数据三部分进行计算的。

紧急指针字段

紧急指针(Urgent Pointer):仅当前面的 URG 控制位为 1 时才有意义。它指出本数据段中为紧急数据 的字节数,占 16 位。当所有紧急数据处理完后,TCP 就会告诉应用程序恢复到正常操作。即使当前窗 口大小为 0,也是可以发送紧急数据的,因为紧急数据无须缓存。

可选项字段

选项(Option):长度不定,但长度必须是 32bits 的整数倍。

image-20210615205125038

image-20210616101803429

image-20210616103716046

2、握手

SYN–标志位

Seq–随机序列号

ACK–应答报文

ack–确认序列号(ack=x+1)

image-20210420174213945

image-20210420174236651

image-20210420174308237

image-20210615212011080

image-20210615212132528

image-20210615213000278

具体的见PDF第三章26页

在数据发送的过程中,利用滑动窗口进行数据的发送

image-20210422104504154

3、udp协议

image-20210421095544693

讲解一下:

*UDP头部很简单,包括源端口,目的端口,UDP总长度,校验和,各占16位/2字节,共8字节。

*源端口:长度16位,指定发送方所使用的端口号,若不需要对方回发消息,则可全置为0。

*目的端口:长度16位,指定接收方所使用的端口号。

*UDP总长度:长度16位,指定了UDP数据报的总长度。

*校验和:长度16位,用于UDP的差错检测,防止UDP报文出错,同时伪首部参与计算,避免UDP用户 数据报传送到错误的目的地。UDP的首部,数据部分,伪首部都会参与检验和的计算,各字段是按照16 比特为单位进行计算的,因此数据部分是要保证是16比特的倍数,不够用0填充。

tcp与udp的区别

image-20210422105701215

4、http协议

1、HTTP协议简介

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和 超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。

HTTP的发展是由蒂姆·伯纳斯-李于1989年在欧洲核子研究组织(CERN)所发起。HTTP的标准制定由万 维网协会(World Wide Web Consortium,W3C)和互联网工程任务组(Internet Engineering Task Force,IETF)进行协调,最终发布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定 义了HTTP协议中现今广泛使用的一个版本——HTTP 1.1。

2014年12月,互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工作小组将 HTTP/2标准提议递交至IESG进行讨论,于2015年2月17日被批准。 HTTP/2标准于2015年5月以RFC 7540正式发表,取代HTTP 1.1成为HTTP的实现标准。

2、HTTP协议概述

HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。

通过使用网页浏览器或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为 80)。

我们称这个客户端为用户代理程序(user agent)。

应答的服务器上存储着一些资源,比如HTML文件和图像。我们称这个应答服务器为源服务器(origin server)。

通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口,https是443)的连接。HTTP服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如”HTTP/1.1 200 OK”,以及返回的内容,如请求的文件、错误消息、或者其它信息。

3、HTTP工作原理

HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户 端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、 URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、 成功或者错误代码、服务器信息、响应头部和响应数据。

image-20210610193946860

在端口号为80的本地建立一个守护socket,当任意一个请求来的时候,再建立一个socket,但守护socket一直存在。

以下是 HTTP 请求/响应的步骤:

  1. 1.客户端连接到Web服务器浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址,一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如, http://www.luffycity.com

  2. 发送HTTP请求 通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。

  3. 服务器接受请求并返回HTTP响应 Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响 应由状态行、响应头部、空行和响应数据4部分组成。

  4. 释放连接TCP连接 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若 connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

  5. 客户端浏览器解析HTML内容

    客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应 头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据 HTML的语法对其进行格式化,并在浏览器窗口中显示。

image-20210421095847308

http协议是基于TCP/IP协议之上的应用层协议

基于 请求-响应 的模式

HTTP协议规定,请求从客户端发出,最后服务器端响应该请求并 返回。换句话说,肯定是先从客户端开 始建立通信的,服务器端在没有 接收到请求之前不会发送响应,服务端不能主动说话

image-20210421095923805

无状态

HTTP是一种不保存状态,即无状态(stateless)协议。HTTP协议 自身不对请求和响应之间的通信状态 进行保存。也就是说在HTTP这个 级别,协议对于发送过的请求或响应都不做持久化处理

image-20210421095953726

使用HTTP协议,每当有新的请求发送时,就会有对应的新响应产生。协议本身并不保留之前一切的请求或响应报文的信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把HTTP协议设计成如此简单的。可是,随着Web的不断发展,因无状态而导致业务处理变得棘手的情况增多了。比如,用户登录到一家购物网站,即使他跳转到该站的其他页面后,也需要能继续保持登录状态。针对这个实例,网站为了能够掌握是谁送出的请求,需要保存用户的状态。HTTP/1.1虽然是无状态协议,但为了实现期望的保持状态功能,于是引入了Cookie技术。有了Cookie再用HTTP协议通信,就可以管理状态了

无连接

无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间,并且可以提高并发性能,不能和每个用户建立长久的连接,请求一次相应一次,服务端和客户端就中断了。但是无连接有两种方式,早期的http协议是一个请求一个响应之后,直接就断开了,但是现在的http协议1.1版本不是直接就断开了,而是等几秒钟,这几秒钟是等什么呢,等着用户有后续的操作,如果用户在这几秒钟之内有新的请求,那么还是通过之前的连接通道来收发消息,如果过了这几秒钟用户没有发送新的请求,那么就会断开连接,这样可以提高效率,减少短时间内建立连接的次数,因为建立连接也是耗时的,默认的好像是3秒中现在,但是这个时间是可以通过咱们后端的代码来调整的,自己网站根据自己网站用户的行为来分析统计出一个最优的等待时间

image-20210610194254771

来个总结 http 是一种基于请求和响应的无状态、无连接的协议

简单的HTTP通信代码(基于TCP)

客户端

public class Client {
    public static void main(String[] args) throws Exception {

        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(InetAddress.getByName("127.0.0.1"),4000));
        Scanner scanner = new Scanner(System.in);
        String msg = scanner.next();
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(msg.getBytes());
        outputStream.flush();
        outputStream.close();
        socket.close();
    }
}

服务器

多线程并发模式采用一个连接一个线程的方式,优点是确实一定程度上提高了服务器的吞吐量,因为之前的请求在read读阻塞后不会影响到后续的请求,由于它们在不同的线程中,而且一个线程只能对应一个套接字socket,每一个套接字socket都是阻塞的,所以一个线程中只能处理一个套接字。就算accept多个socket,如果前一个socket被阻塞其后的socket是无法被执行到的

public class Server {
    public static void main(String[] args) throws IOException {
        //创建服务器
        ServerSocket server = new ServerSocket();
        //绑定端口
        server.bind(new InetSocketAddress(4000));
        System.out.println("服务器已启动,监听4000端口");
        //进行监听
        while(true) {
            Socket accept = server.accept();
          	//利用多线程通信
            new MsgHandler(accept).start();
        }
    }
}
//多线程处理,如果是多个客户端连接,端口号不一样,IP相同
public class MsgHandler extends Thread{

    Socket accept = null;

    public MsgHandler(Socket accept) {
        this.accept = accept;
    }

    @Override
    public void run() {

        InputStream inputStream = null;

        try {
            //拿到数据进行读取
            inputStream = accept.getInputStream();
            byte[] buf = new byte[1024];
            int len;
            while ((len = inputStream.read(buf)) != -1) {
                System.out.println(new String(buf, 0, len));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4、HTTP请求格式(请求协议)

其实就是个字符串:

image-20210421100133351

image-20210421100146022

请求头里面的内容举个例子:这个length表示请求体里面的数据长度,其他的请求头里面的这些键值对,陆续我们会讲的,大概知道一下就可以了,其中有一个user-agent,算是需要你记住的吧,就是告诉你的服务端,我是用什么给你发送的请求,是个啥客户端。

image-20210421100214644

image-20210421100226816

image-20210421100240863

看一个爬虫的例子,爬京东的时候没问题,但是爬抽屉的时候必须带着user-agent,因为 抽屉对user-agent做了判断,来判断你是不是一个正常的请求,算是反扒机制的一种

image-20210421100307753

打开我们保存的demo.html文件,然后通过浏览器打开看看就能看到页面效果。

写上面这些内容的意思是让你知道有这么个请求头的存在,有些是有意义的,请求头我们 还可以自己定义,就在requests模块里面那个headers={},这个字典里面加就行

5、HTTP请求方法

HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源:

  • GET

向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,常用语查询数据的请求。

  • POST

向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。常用于对数据的增删改操作。

请求方式: get与post请求(通过form表单我们自己写写看)

  • GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456.(请求头里面那个content-type做的这种参数形式,后面讲)POST方法是把提交的数据放在HTTP包的请求体中
  • GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制
  • GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了

6、URL

超文本传输协议(HTTP)的统一资源定位符将从因特网获取信息的五个基本元素包括在一个简单的地址中:

  • 传送协议
  • 层级URL标记符号(为[//],固定不变)
  • 访问资源需要的凭证信息(可省略)
  • 服务器(通常为域名,有时为IP地址)
  • 端口号(以数字方式表示,若为HTTP的默认值“:80”可省略)
  • 路径(以“/”字符区别路径中的每一个目录名称)
  • 查询(GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数 据,通常以UTF8的URL编码,避开字符冲突的问题)
  • 片段,以“#”字符为起点
以http://www.xinzhi.com:80/news/index.html?id=250&page=1 为例, 其中:

http,是协议; 
www.xinzhi.com,是服务器; 
80,是服务器上的默认网络端口号,默认不显示; 
/news/index.html,是路径(URI:直接定位到对应的资源); 
?id=250&page=1,是查询条件。 
大多数网页浏览器不要求用户输入网页中“[http://”的部分,因为绝大多数网页内容是超文本传输协议文件。 
“80”是超文本传输协议文件的常用默认端口号,因此一般也不必写明。一般来说用户只要键入统一资源定位符的一部分

Java中的三板斧

byte[] buf = new byte[1024];
int len;
while ((len = inputStream.read(buf)) != -1) {
  System.out.println(new String(buf, 0, len));
}

7、请求中常见的ContentType

就是告诉服务器,我给你发了个参数,参数是什么类型,你应该怎么接受,怎么解析

application/x-www-form-urlencoded

这应该是最常见的POST提交数据的方式了。浏览器的原生form表单,如果不设置enctype属性,那么最终就会以application/x-www-form-urlencoded方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了)

格式 : name=’lisi’&age=13

application/json

application/json 这个 Content-Type 作为响应头大家肯定不陌生。实际上,现在越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串。由于 JSON 规范的流行,除了低版本 IE 之外的各大浏览器都原生支持 JSON.stringify,服务端语言也都有处理 JSON 的函数,使用 JSON 不会遇上什么麻烦。

格式: {“name”:”lisi”,”age”:13}

multipart/form-data

这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 form 的 enctyped 等于这 个值。直接来看一个请求示例:

8、HTTP响应格式(响应协议)

image-20210421100814365

image-20210421100827723

9、HTTP状态码

所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码,以及描述状态 的短语,彼此由空格分隔。

状态代码的第一个数字代表当前响应的类型:

  • 1xx消息——请求已被服务器接收,继续处理
  • 2xx成功——请求已成功被服务器接收、理解、并接受
  • 3xx重定向——需要后续操作才能完成这一请求
  • 4xx请求错误——请求含有词法错误或者无法被执行,客户端
  • 5xx服务器错误——服务器在处理某个正确请求时发生错误,500

虽然 RFC 2616 中已经推荐了描述状态的短语,例如”200 OK”,”404 Not Found”,但是WEB开发者仍 然能够自行决定采用何种短语,用以显示本地化的状态描述或者自定义信息

image-20210421100922367

状态码(Status Codes)

2xx:成功 3xx:重定向 4xx:客户端错误 5xx:服务器错误
200 成功 301 永久重定向 400 错误请求 500 服务器错误
201 创建 304 资源未修改 401 未授权 501 尚未实施
410 已删除 502 网关错误
403 禁止访问 503 服务不可用
404 未找到 504 网关超时
405 请求方法不对 505 HTTP版本不支持

10、响应中常见的contentType

text/html

告诉浏览器,我给你返回的是html,请渲染

application/json

告诉浏览器,我给你返回的是json数据 ,一般会让js去处理

常见的contenttype对照表

https://tool.oschina.net/commons/

11、一些常见的响应头

常见的请求头对照表

http://tools.jb51.net/table/http_header

Server: Apache-Coyote/1.1:服务器的版本信息; 
Content-Type: text/html;charset=UTF-8:响应体使用的编码为UTF-8; 
Content-Length: 724:响应体为724字节; 
Set-Cookie: JSESSIONID=C97E2B4C55553EAB46079A4F263435A4; Path=/hello:响应给客户端的Cookie; Date: Wed, 25 Sep 2012 04:15:03 GMT:响应的时间,这可能会有8小时的时区差;
Last-Modified:最后的修改时间; 
If-Modified-Since:把上次请求的index.html的最后修改时间还给服务器;
//告诉浏览器不要缓存: 
Expires: -1; 
Cache-Control: no-cache; 
Pragma: no-cache; 
**自动刷新**响应头,浏览器会在3秒之后请求http://www.itcast.cn: 
Refresh: 3;url=http://www.itcast.cn

image-20210421101507448

image-20210421101526912

四、Tomcat

Web 应⽤服务器:Tomcat、Jboos、Weblogic、Jetty

tomcat目录

  • bin:存放各个平台下启动和停⽌ Tomcat 服务的脚本⽂件
  • conf:存放各种 Tomcat 服务器的配置⽂件
  • lib:存放Tomcat服务器所需要的 jar
  • logs:存放Tomcar服务运⾏的⽇志
  • temp:Tomcat 运⾏时的临时⽂件
  • webapps:专门用来存放部署的Web工程
  • work:是 Tomcat 工作时的目录,用来存放 Tomcat运行时jsp翻译为Servlet的源码,和Session钝化的目录

image-20210421100828862

Http工作原理

HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议,HTTP是基于TCP/IP协议来传递数 据的(HTML文件、图片、查询结果等),HTTP协议不涉及数据包(Packet)传输,主要规定了客户端 和服务器之间的通信格式。

image-20210423205515478

Http服务器请求处理

浏览器发给服务端的是一个HTTP格式的请求,HTTP服务器收到这个请求后,需要调用服务端程序来处 理,所谓的服务端程序就是你写的Java类,一般来说不同的请求需要由不同的Java类来处理。

image-20210423205608522

  • 图1 , 表示HTTP服务器直接调用具体业务类,它们是紧耦合的
  • 图2,HTTP服务器不直接调用业务类,而是把请求交给容器来处理,容器通过Servlet接口调用业务 类。因此Servlet接口和Servlet容器的出现,达到了HTTP服务器与业务类解耦的目的。而Servlet接口和 Servlet容器这一整套规范叫作Servlet规范。Tomcat按照Servlet规范的要求实现了Servlet容器,同时它 们也具有HTTP服务器的功能。作为Java程序员,如果我们要实现新的业务功能,只需要实现一个Servlet,并把它注册到Tomcat(Servlet容器)中,剩下的事情就由Tomcat帮我们处理了

Servlet容器工作流程

为了解耦,HTTP服务器不直接调用Servlet,而是把请求交给Servlet容器来处理

当客户请求某个资源时,HTTP服务器会用一个ServletRequest对象把客户的请求信息封装起来,然后 调用Servlet容器的service方法,Servlet容器拿到请求后,根据请求的URL和Servlet的映射关系,找到 相应的Servlet,如果Servlet还没有被加载,就用反射机制创建这个Servlet,并调用Servlet的init方法来 完成初始化,接着调用Servlet的service方法来处理请求,把ServletResponse对象返回给HTTP服务 器,HTTP服务器会把响应发送给客户端

image-20210423205747804

Tomcat整体架构

我们知道如果要设计一个系统,首先是要了解需求,我们已经了解了Tomcat要实现两个核心功能:

1) 处理Socket连接,负责网络字节流与Request和Response对象的转化。

2) 加载和管理Servlet,以及具体处理Request请求。

因此Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理

image-20210423205846503

Tomcat 服务器配置

Tomcat 服务器的配置主要集中于 tomcat/conf 下的 catalina.policy、catalina.properties、 context.xml、server.xml、tomcat-users.xml、web.xml 文件

server.xml

server.xml 是tomcat 服务器的核心配置文件,包含了Tomcat的 Servlet 容器(Catalina)的所有配 置。由于配置的属性特别多,我们在这里主要讲解其中的一部分重要配置。

Server

Server是server.xml的根元素,用于创建一个Server实例,默认使用的实现类是 org.apache.catalina.core.StandardServer

<Server port="8005" shutdown="SHUTDOWN">

... </Server>

port : Tomcat 监听的关闭服务器的端口

shutdown: 关闭服务器的指令字符串

Connector

Connector 用于创建链接器实例。默认情况下,server.xml 配置了两个链接器,一个支持HTTP协议, 一个支持AJP协议。因此大多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进 行优化。

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"

redirectPort="8443" />

属性说明:

1)port:端口号,Connector用于创建服务端Socket并进行监听,以等待客户端请求链接。如果该属性设置为0,Tomcat将会随机选择一个可用的端口号给当前Connector使用

2) protocol : 当前Connector 支持的访问协议。 默认为 HTTP/1.1 。

3) connectionTimeOut : Connector 接收链接后的等待超时时间, 单位为毫秒。 -1 表示不超时

4) URIEncoding : 用于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 。

完整的配置如下:

<Connector port="8080"

protocol="HTTP/1.1" executor="tomcatThreadPool" maxThreads="1000"

minSpareThreads="100" acceptCount="1000"

maxConnections="1000" connectionTimeout="20000" compression="on" compressionMinSize="2048" disableUploadTimeout="true" redirectPort="8443"

URIEncoding="UTF-8" />

Tomcat配置负载均衡

16.负载均衡集群

# 0.准备多个tomcat
	 tar -zxvf apache-tomcat-8.5.46.tar.gz #解压缩一个新的tomcat安装包
	 mv apache-tomcat-8.5.46 tomcat1 			 #将名称改为tomcat1
	 cp -r tomcat1/ tomcat2								 #复制一份
	 cp -r tomcat1/ tomcat3                #复制一份

# 1.此时当前目录中有三个服务器,如下:
	[root@localhost ~]# ls -l
	总用量 12248
	-rwxrwxrwx. 1 root root  11623939 10月 13 12:25 apache-tomcat-8.5.46.tar.gz
	drwxr-xr-x. 9 root root       220 10月 14 21:28 tomcat1
	drwxr-xr-x. 9 root root       220 10月 14 21:38 tomcat2
	drwxr-xr-x. 9 root root       220 10月 14 21:38 tomcat3

配置三个tomcat的端口号

分别修改端口,http协议端口,AJP协议端口

# 2.修改tomcat1端口号:(伪分布式)
		vim tomcat1/conf/server.xml,命令修改如下内容:
		a.<Server port="8001" shutdown="SHUTDOWN">   ---关闭端口
		b.<Connector port="8888" protocol="HTTP/1.1" ---http协议端口
               connectionTimeout="20000"
               redirectPort="8443" />
		c.<Connector port="10010" protocol="AJP/1.3" redirectPort="8443" /> ---AJP协议端口
# 3.修改tomcat2端口号:(伪分布式)
		vim tomcat2/conf/server.xml,命令修改如下内容:
  	a.<Server port="8002" shutdown="SHUTDOWN">
		b.<Connector port="8889" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
   	c.<Connector port="10011" protocol="AJP/1.3" redirectPort="8443" />
# 4.修改tomcat3端口号:(伪分布式)
		vim tomcat2/conf/server.xml,命令修改如下内容:
  	a.<Server port="8003" shutdown="SHUTDOWN">
		b.<Connector port="8890" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
   	c.<Connector port="10012" protocol="AJP/1.3" redirectPort="8443" />
# 5.将多个tomcat启动:
		tomcat1/bin/startup.sh 
		tomcat2/bin/startup.sh 
		tomcat3/bin/startup.sh
    
# 6.查看tomcat是否启动成功
		ps -aux|grep tomcat

image-20191014215035543

# 7.在windows中分别访问tomcat,都看到主页代表启动成功:
	
	http://10.15.0.8:8888/
	http://10.15.0.8:8889/
	http://10.15.0.8:8890/
	
	注意:这步一定要关闭网路防火墙
# 8.将多个tomcat配置到nginx的配置文件中:
	vim /conf/nginx.conf
	
## 在server标签上加入如下配置:
    upstream tomcat-servers {
      server 10.15.0.8:8888;
      server 10.15.0.8:8889;
      server 10.15.0.8:8890;
    }
## 将配置文件中 location /替换为如下配置:
		location / {
			 proxy_pass http://tomcat-servers;
			 proxy_redirect    off;
			 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			 proxy_set_header X-Real-IP $remote_addr;
			 proxy_set_header Host $http_host;
			 proxy_next_upstream http_502 http_504 error timeout invalid_header;
		   }

image-20191014215825240

# 9.进入nginx安装目录sbin目录加载配置文件的形式启动nginx
	./nginx -c /usr/nginx/conf/nginx.conf
# 10.访问nginx,看到其中一个tomcat画面:
	http://10.15.0.8/ 

image-20191014220145379

Nginx负载均衡策略

默认策略:每个请求会按时间顺序逐一分配到不同的后端服务器

# 以下配置都在vim /conf/nginx.conf中
# weight 权重
	说明: weight参数用于指定轮询几率,weight的默认值为1,;weight的数值与访问比率成正比 
    upstream tomcat-servers {
        server localhost:8888   weight=2;  
        server localhost:8889;  
        server localhost:8890   backup;  
    }
注意:1.权重越高分配到需要处理的请求越多。2.此策略可以与least_conn和ip_hash结合使用主要用于后端服务器性能不均

# ip_hash 
	 说明:指定负载均衡器按照基于客户端IP的分配方式,这个方法确保了相同的客户端的请求一直发送到相同的服务器,以保证session会话。这样每个访客都固定访问一个后端服务器,可以解决session不能跨服务器的问题。
	 upstream tomcat-servers {
        ip_hash;    #保证每个访客固定访问一个后端服务器
        ......
    }
# least_conn
	说明: 把请求转发给连接数较少的后端服务器。轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同;但是,有些请求占用的时间很长,会导致其所在的后端负载较高。这种情况下,least_conn这种方式就可以达到更好的负载均衡效果。
	upstream tomcat-servers{
        least_conn;    #把请求转发给连接数较少的后端服务器
    }

Session共享

Memcached Session Manager

Memcached Session Manager基于memcache缓存的session共享.即使用cacheDB存取session信息,应用服务器接受新请求将session信息保存在cache DB中,当应用服务器发生故障时,调度器会遍历寻找可用节点,分发请求,当应用服务器发现session不在本机内存时,则去cacheDB中查找,如果找到则复制到本机,这样实现session共享和高可用。

# 1.安装memcached
yum install -y memcached

# 2.启动memcached(port:11211)
memcached -p 11211 -vvv -u root

# 3.tomcat安装的lib目录中放入与memcache整合jar包
		cp *.jar tomcat1/lib
		cp *.jar tomcat2/lib
		cp *.jar tomcat3/lib

# 4.配置tomcat目录中conf目录中context.xml(所有tomcat均需要配置)
<Context>
 <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
        memcachedNodes="n1:10.15.0.8:11211,n2:...." --对应name:msm的ip:port,可以加多台做分布式
        sticky="false"  
    		sessionBackupAsync="false"  
        requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"        transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
        />
</Context>

# 5.然后启动tomcat和nginx并放入测试项目进行测试

Redis

同样利用tomcat与nginx搭建,但是利用Redis集群进行管理,redis的session管理是利用spring提供的session管理解决方案,将一个应用session交给Redis存储,整个应用中所有session的请求都会去redis中获取对应的session数据

五、Nginx

Nginx 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP 服务。

Nginx 是由伊戈尔·赛索耶夫为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004104 日。

Nginx 特点是占有内存少,并发能力强。

事实上 nginx 的并发能力确实在同类型的网页服务器中表现较好,一般来说,如果我们在项目中引入了 Nginx ,我们的项目架构可能是这样:

img

在这样的架构中 , Nginx 所代表的角色叫做负载均衡服务器或者反向代理服务器,所有请求首先到达 Nginx 上,再由 Nginx 根据提前配置好的转发规则,将客户端发来的请求转发到某一个 Tomcat 上去。

那么这里涉及到两个概念:

  • 负载均衡服务器

就是进行请求转发,降低某一个服务器的压力。负载均衡策略很多,也有很多层,对于一些大型网站基本上从 DNS就开始负载均衡,负载均衡有硬件和软件之分,各自代表分别是 F5Nginx (目前 Nginx 已经被 F5 收购),早些年,也可以使用 Apache 来做负载均衡,但是效率不如 Nginx ,所以现在主流方案是 Nginx

  • 反向代理服务器:

另一个概念是反向代理服务器,得先说正向代理,看下面一张图:

img

在这个过程中,Google 并不知道真正访问它的客户端是谁,它只知道这个中间服务器在访问它。因此,这里的代理,实际上是中间服务器代理了客户端,这种代理叫做正向代理。

那么什么是反向代理呢?看下面一张图:

img

在这个过程中,10086 这个号码相当于是一个代理,真正提供服务的,是话务员,但是对于客户来说,他不关心到底是哪一个话务员提供的服务,他只需要记得 10086 这个号码就行了。

所有的请求打到 10086 上,再由 10086 将请求转发给某一个话务员去处理。因此,在这里,10086 就相当于是一个代理,只不过它代理的是话务员而不是客户端,这种代理称之为反向代理。

Nginx 的优势

在 Java 开发中,Nginx 有着非常广泛的使用,随便举几点:

  • 使用 Nginx 做静态资源服务器:Java 中的资源可以分为动态和静态,动态需要经过 Tomcat 解析之后,才能返回给浏览器,例如 JSP 页面、Freemarker 页面、控制器返回的 JSON 数据等,都算作动态资源,动态资源经过了 Tomcat 处理,速度必然降低。对于静态资源,例如图片、HTML、JS、CSS 等资源,这种资源可以不必经过 Tomcat 解析,当客户端请求这些资源时,之间将资源返回给客户端就行了。此时,可以使用 Nginx 搭建静态资源服务器,将静态资源直接返回给客户端。
  • 使用 Nginx 做负载均衡服务器,无论是使用 Dubbo 还是 Spirng Cloud ,除了使用各自自带的负载均衡策略之外,也都可以使用 Nginx 做负载均衡服务器。
  • 支持高并发、内存消耗少、成本低廉、配置简单、运行稳定等。

六、Servlet

简介

  • Servlet 是 JavaEE 规范之一。规范就是接口
  • Servlet 就 JavaWeb 三大组件之一。三大组件分别是:Servlet 程序、Filter 过滤器、Listener 监听器
  • Servlet 是运行在服务器上的一个 java 小程序,它可以接收客户端发送过来的请求,并响应数据给客户端

功能

  • 接受请求
  • 处理请求
  • 完成相应

Servlet加载时机

在默认情况下,当Web客户第一次请求访问某个Servlet时,Web容器会创建这个Servlet的实例。

当设置了web.xml中的子元素后,Servlet容器在启动Web应用时,将按照指定顺序创建并初始化这个 Servlet。设置的数值大于0即可。例如:

<servlet> 
  <servlet-name>HelloServlet</servlet-name> 
  <servlet-class>com.langsin.servlet.HelloServlet</servlet-class> 
  <load-on-startup>2</load-on-startup> 
</servlet>

Servlet的生命周期

先看与Servlet生命周期有关的三个方法:init(), service(), destroy(). Servlet生命周期可被定义为从创建 直到毁灭的整个过程。以下是三个方法分别对应的Servlet过程:

  • 执行 Servlet 构造器方法
  • 执行 init 初始化方法
    • 第一、二步,是在第一次访问的时候创建 Servlet 程序会调用
  • 执行 service 方法
    • 第三步,每次访问都会调用
  • 执行 destroy 销毁方法
    • 第四步,在 web 工程停止的时候调用

手动实现 Servlet 程序

  • 编写一个类去实现 Servlet 接口
  • 实现 service 方法,处理请求,并响应数据
  • 到 web.xml 中去配置 servlet 程序的访问地址
    • 浏览器不能直接访问 Servlet ⽂件,只能通过映射的⽅式来间接访问 Servlet,映射需要开发者⼿动配置,有两种配置⽅式
public class HelloServlet implements Servlet {
    /**
    * service 方法是专门用来处理请求和响应的
    * @param servletRequest
    * @param servletResponse
    * @throws ServletException
    * @throws IOException
    */
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws
        ServletException, IOException {
        	System.out.println("Hello Servlet 被访问了");
        }
}

基于 XML ⽂件的配置⽅式:

Web.xml

 <servlet>
    <servlet-name>hello</servlet-name>
    <!--全类名-->
    <servlet-class>com.southwind.servlet.HelloServlet</servlet-class>
  </servlet>
<!--  mapping标签给servlet程序配置访问地址-->
  <servlet-mapping>
<!--    告诉服务器,我当前配置的地址给那个servlet程序使用-->
    <servlet-name>hello</servlet-name>
<!--    访问地址-->
    <url-pattern>/demo2</url-pattern>
  </servlet-mapping>

基于注解的⽅式

@WebServlet("/demo2")
public class HelloServlet implements Servlet {
 
	}

url 地址到 Servlet 程序的访问

image-20210423170521391

image-20210423170614051

通过继承 HttpServlet 实现 Servlet 程序

一般在实际项目开发中,都是使用继承 HttpServlet 类的方式去实现 Servlet 程序。

  • 编写一个类去继承 HttpServlet 类
  • 根据业务需要重写 doGet 或 doPost 方法
  • 到 web.xml 中的配置 Servlet 程序的访问地址
public class HelloServlet2 extends HttpServlet {
    /**
    * doGet()在 get 请求的时候调用
    * @param req
    * @param resp
    * @throws ServletException
    * @throws IOException
    */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
    	System.out.println("HelloServlet2 的 doGet 方法");
    }
    /**
    * doPost()在 post 请求的时候调用
    * @param req
    * @param resp
    * @throws ServletException
    * @throws IOException
    */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
    	System.out.println("HelloServlet2 的 doPost 方法");
    }
}
<servlet>
	<servlet-name>HelloServlet2</servlet-name>
	<servlet-class>com.atguigu.servlet.HelloServlet2</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>HelloServlet2</servlet-name>
	<url-pattern>/hello2</url-pattern>
</servlet-mapping>

Servlet 类的继承体系

Servlet ---> GenericServlet ---〉HttpServlet

HTTP 请求有很多种类型,常⽤的有四种:

  • GET 读取

  • POST 保存

  • PUT 修改

  • DELETE 删除

GenericServlet 实现 Servlet 接⼝,同时为它的⼦类屏蔽了不常⽤的⽅法,⼦类只需要重写 service⽅法即可。

HttpServlet 继承 GenericServlet,根据请求类型进⾏分发处理,GET 进⼊ doGET ⽅法,POST 进⼊ doPOST ⽅法。 开发者⾃定义的 Servlet 类只需要继承 HttpServlet 即可,重新 doGET 和 doPOST。

ServletConfig

  • ServletConfig类从类名上来看,就知道是Servlet程序的配置信息类
  • Servlet 程序和 ServletConfig 对象都是由Tomcat负责创建,我们负责使用
  • Servlet 程序默认是第一次访问的时候创建,ServletConfig是每个Servlet 程序创建时,就创建一个对应的ServletConfig对象

ServletConfig 类的三大作用

1、可以获取 Servlet 程序的别名 servlet-name 的值
2、获取Servlet 初始化参数 init-param
3、获取 ServletContext 对象

<!-- servlet 标签给 Tomcat 配置 Servlet 程序 -->
<servlet>
    <!--servlet-name 标签 Servlet 程序起一个别名(一般是类名) -->
    <servlet-name>HelloServlet</servlet-name><!--servlet-class 是 Servlet 程序的全类名-->
    <servlet-class>com.atguigu.servlet.HelloServlet</servlet-class>
    <!--init-param 是初始化参数-->
    <init-param>
        <!--是参数名-->
        <param-name>username</param-name>
        <!--是参数值-->
        <param-value>root</param-value>
    </init-param>
        <!--init-param 是初始化参数-->
        <init-param>
        <!--是参数名-->
        <param-name>url</param-name>
        <!--是参数值-->
        <param-value>jdbc:mysql://localhost:3306/test</param-value>
    </init-param>
    </servlet>
    <!--servlet-mapping 标签给 servlet 程序配置访问地址-->
    <servlet-mapping>
        <!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用-->
        <servlet-name>HelloServlet</servlet-name>
        <!--
        url-pattern 标签配置访问地址 <br/>
        / 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/>
        /hello 表示地址为:http://ip:port/工程路径/hello <br/>
        -->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</servlet>

ServletConfig 各个方法

getServletName() 返回 Servlet的别名
getInitParameter(String key) 获取init参数的值(web.xml)
getInitParameterNames() 返回所有的 initParamter的name值,⼀般⽤作遍历初始化参数
getServletContext() 返回 ServletContext 对象,它是 Servlet 的上下⽂,整个 Servlet 的管理者

@Override
public void init(ServletConfig servletConfig) throws ServletException {
    System.out.println("2 init 初始化方法");
    // 1、可以获取 Servlet 程序的别名 servlet-name 的值
    System.out.println("HelloServlet 程序的别名是:" + servletConfig.getServletName());
    // 2、获取初始化参数 init-param
    System.out.println("初始化参数 username 的值是;" + servletConfig.getInitParameter("username"));
    System.out.println("初始化参数 url 的值是;" + servletConfig.getInitParameter("url"));
    // 3、获取 ServletContext 对象
    System.out.println(servletConfig.getServletContext());
}

ServletContext

什么是 ServletContext?

1、ServletContext 是一个接口,它表示 Servlet 上下文对象
2、一个 web 工程,只有一个 ServletContext 对象实例
3、ServletContext 对象是一个域对象
4、ServletContext 是在 web 工程部署启动的时候创建,在 web 工程停止的时候销毁

什么是域对象?
域对象,是可以像Map一样存取数据的对象,叫域对象

这里的域指的是存取数据的操作范围,整个 web工程

		 存数据  		 取数据   		    删除数据
Map 	 put()   		get()  			remove()
域对象 setAttribute() getAttribute() removeAttribute();

ServletContext对象的作用

1、获取 web.xml 中配置的上下文参数 context-param
2、获取当前的工程路径,格式: /工程路径
3、获取工程部署后在服务器硬盘上的绝对路径
4、像 Map 一样存取数据

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
    // 1、获取 web.xml 中配置的上下文参数 context-param
    ServletContext context = getServletConfig().getServletContext();
    String username = context.getInitParameter("username");
    System.out.println("context-param 参数 username 的值是:" + username);
    System.out.println("context-param 参数 password 的值是:" +
    context.getInitParameter("password"));
    // 2、获取当前的工程路径,格式: /工程路径
  	System.out.println( "当前工程路径:" + context.getContextPath() );
    // 3、获取工程部署后在服务器硬盘上的绝对路径
    /**
    * / 斜杠被服务器解析地址为:http://ip:port/工程名/ 映射到 IDEA 代码的 web 目录<br/>
    */
    System.out.println("工程部署的路径是:" + context.getRealPath("/"));
    System.out.println("工程下 css 目录的绝对路径是:" + context.getRealPath("/css"));
    System.out.println("工程下 imgs 目录 1.jpg 的绝对路径是:" + context.getRealPath("/imgs/1.jpg"));
}
!--context-param 是上下文参数(它属于整个 web 工程)-->
<context-param>
    <param-name>username</param-name>
    <param-value>context</param-value>
</context-param>
<!--context-param 是上下文参数(它属于整个 web 工程)-->
<context-param>
    <param-name>password</param-name>
    <param-value>root</param-value>
</context-param>

ServletContext 像 Map 一样存取数据:

ContextServlet1 代码:

public class ContextServlet1 extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
        ServletException, IOException {
        // 获取 ServletContext 对象
        ServletContext context = getServletContext();
        System.out.println(context);
        System.out.println("保存之前: Context1 获取 key1 的值是:"+ context.getAttribute("key1"));
        context.setAttribute("key1", "value1");
        System.out.println("Context1 中获取域数据 key1 的值是:"+ context.getAttribute("key1"));
    }
}

ContextServlet2 代码:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
    ServletContext context = getServletContext();System.out.println(context);
    System.out.println("Context2 中获取域数据 key1 的值是:"+ context.getAttribute("key1"));
}

ServletConfig 和 ServletContext 的区别:

ServletConfig 作⽤于某个 Servlet 实例,每个 Servlet 都有对应的 ServletConfig,ServletContext 作⽤于整个 Web 应⽤,⼀个 Web 应⽤对应⼀个 ServletContext,多个 Servlet 实例对应⼀个ServletContext

⼀个是局部对象,⼀个是全局对象

HTTP 协议回顾

什么是 HTTP 协议

  • 协议是指双方,或多方,相互约定好,大家都需要遵守的规则,叫协议
  • 所谓 HTTP 协议,就是指,客户端和服务器之间通信时,发送的数据,需要遵守的规则,叫 HTTP 协议
  • HTTP 协议中的数据又叫报文

请求的 HTTP 协议格式

  • 客户端给服务器发送数据叫请求
  • 服务器给客户端回传数据叫响应
  • 请求又分为 GET 请求,和 POST 请求两种

GET 请求

1、请求行
    (1) 请求的方式 GET
    (2) 请求的资源路径[+?+请求参数]
    (3) 请求的协议的版本号 HTTP/1.1
2、请求头
	key : value 组成不同的键值对,表示不同的含义。

image-20210421104718675

POST 请求

1、请求行
    (1) 请求的方式 POST 
    (2) 请求的资源路径[+?+请求参数] 
    (3) 请求的协议的版本号 HTTP/1.1 
2、请求头 
	key : value 不同的请求头,有不同的含义 
空行
3、请求体
	就是发送给服务器的数据

image-20210421104938669

常用请求头的说明

Accept 表示客户端可以接收的数据类型
Accpet-Languege 表示客户端可以接收的语言类型
User-Agent 表示客户端浏览器的信息
Host 表示请求时的服务器 ip 和端口号

区分GET和POST请求

GET

  • from标签 method=get
  • a标签
  • link标签引入css
  • Script标签引入js文件
  • img标签引入图片
  • iframe引入html页面
  • 在浏览器地址栏输入地址后翘回车

POST

  • from标签 method=post

响应的 HTTP 协议格式

1、响应行
    (1) 响应的协议和版本号
    (2) 响应状态码
    (3) 响应状态描述符
2、响应头
	  key : value 不同的响应头,有其不同含义
空行
3、响应体
	就是回传给客户端的数据

image-20210421105124947

常用的响应码说明

200 表示请求成功
302 表示请求重定向
404 表示请求服务器已经收到了,但是你要的数据不存在(请求地址错误)
500 表示服务器已经收到请求,但是服务器内部错误(代码错误)

MIME 类型说明

  • MIME 是 HTTP 协议中数据类型
  • MIME 的英文全称是”Multipurpose Internet Mail Extensions” 多功能 Internet 邮件扩充服务。MIME 类型的格式是“大类型/小类型”,并与某一种文件的扩展名相对应

常见的 MIME 类型

文件 MIME 类型
超文本标记语言文本 text/html
普通文本 text/plain
TAR 文件 application/x-tar
GIF 图形 image/gif

HttpServletRequest

HttpServletRequest 类的作用

  • 每次只要有请求进入 Tomcat 服务器,Tomcat 服务器就会把请求过来的 HTTP 协议信息解析好封装到 Request 对象中
  • 然后传递到 service 方法(doGet 和 doPost)中给我们使用。我们可以通过 HttpServletRequest 对象,获取到所有请求的信息

HttpServletRequest 类的常用方法

getRequestURI() 获取请求的资源路径
getRequestURL() 获取请求的统一资源定位符(绝对路径)
getRemoteHost() 获取客户端的ip地址
getHeader() 获取请求头
getParameter()获取请求的参数
getParameterValues()获取请求的参数(多个值的时候使用)
setAttribute(key, value) 设置域数据
getAttribute(key)获取域数据
getRequestDispatcher() 获取请求转发对
getMethod() 获取请求的方式GET或POST

常用 API 示例代码:

public class RequestAPIServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        // i.getRequestURI() 获取请求的资源路径
        System.out.println("URI => " + req.getRequestURI());
        // ii.getRequestURL() 获取请求的统一资源定位符(绝对路径)
        System.out.println("URL => " + req.getRequestURL());
      	// iii.getRemoteHost() 获取客户端的 ip 地址
        /**
        * 在 IDEA 中,使用 localhost 访问时,得到的客户端 ip 地址是 ===>>> 127.0.0.1<br/>
        * 在 IDEA 中,使用 127.0.0.1 访问时,得到的客户端 ip 地址是 ===>>> 127.0.0.1<br/>
        * 在 IDEA 中,使用 真实 ip 访问时,得到的客户端 ip 地址是 ===>>> 真实的客户端 ip 地址<br/>
        */
        System.out.println("客户端 ip 地址 => " + req.getRemoteHost());
        // iv.getHeader() 获取请求头
        System.out.println("请求头 User-Agent ==>> " + req.getHeader("User-Agent"));
        // vii.getMethod() 获取请求的方式 GET 或 POST
        System.out.println( "请求的方式 ==>> " + req.getMethod() );
    }
}

如何获取请求参数

<body>
    <form action="http://localhost:8080/07_servlet/parameterServlet" method="get">
        用户名:<input type="text" name="username"><br/>
        密码:<input type="password" name="password"><br/>
        兴趣爱好:<input type="checkbox" name="hobby" value="cpp">C++
        <input type="checkbox" name="hobby" value="java">Java
        <input type="checkbox" name="hobby" value="js">JavaScript<br/>
        <input type="submit">
    </form>
</body>

Java 代码:

public class ParameterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
      	//设置编码
     		req.setCharacterEncoding("UTF-8");
        // 获取请求参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String[] hobby = req.getParameterValues("hobby");
        System.out.println("用户名:" + username);
        System.out.println("密码:" + password);
        System.out.println("兴趣爱好:" + Arrays.asList(hobby));
    }
}

请求转发

什么是请求的转发?

请求转发是指,服务器收到请求后,从一次资源跳转到另一个资源的操作叫请求转发。

image-20210421111014356

public class Servlet1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        // 获取请求的参数(办事的材料)查看
        String username = req.getParameter("username");
        System.out.println("在 Servlet1(柜台 1)中查看参数(材料):" + username);
        // 给材料 盖一个章,并传递到 Servlet2(柜台 2)去查看
        req.setAttribute("key1","柜台 1 的章");
        // 问路:Servlet2(柜台 2)怎么走
        /**
        * 请求转发必须要以斜杠打头,/ 斜杠表示地址为:http://ip:port/工程名/ , 映射到 IDEA 代码的 web 目录
        <br/>
        *
        */
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("/servlet2");
        // 走向 Sevlet2(柜台 2)
        requestDispatcher.forward(req,resp);
    }
}
public class Servlet2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
    IOException {
        // 获取请求的参数(办事的材料)查看
        String username = req.getParameter("username");
        System.out.println("在 Servlet2(柜台 2)中查看参数(材料):" + username);
        // 查看柜台1是否有盖章
        Object key1 = req.getAttribute("key1");
        System.out.println("柜台 1 是否有章:" + key1);
        // 处理自己的业务
        System.out.println("Servlet2 处理自己的业务 ");
	}
}

base标签

比如这里设置

<base href="http://localhost:8080/07_servlet/a/b/c.html">
或者
<base href="http://localhost:8080/07_servlet/a/b/">

image-20210424201046209

Web 中的相对路径和绝对路径

在 javaWeb 中, 路径分为相对路径和绝对路径两种:

  • 相对路径是:
.          表示当前目录
..         表示上一级目录
资源名     表示当前目录/资源名

绝对路径: http://ip:port/工程路径/资源路径

在实际开发中, 路径都使用绝对路径, 而不简单的使用相对路径,具体而言, 绝对路径的表示方式有:

  • 绝对路径
  • base+相对
// i.getRequestURI() 获取请求的资源路径
System.out.println("URI => " + req.getRequestURI());
// ii.getRequestURL() 获取请求的统一资源定位符(绝对路径)
System.out.println("URL => " + req.getRequestURL());
//动态获取当前项目的路径(磁盘上的真实路径)
ServletContext context=getServletContext();
response.sendRedirect(context.getContextPath()+"/pages/a.html")

web 中 / 斜杠的不同意义

在 web 中 / 斜杠 是一种绝对路径

/ 斜杠 如果被浏览器解析,得到的地址是:http://ip:port/

<a href="/">斜杠</a>

/ 斜杠 如果被服务器解析,得到的地址是:http://ip:port/工程路径

  • /servlet1
  • servletContext.getRealPath(“/”);
  • request.getRequestDispatcher(“/”);

特殊情况

response.sendRediect(“/”); 把斜杠发送给浏览器解析。得到 http://ip:port/

中文乱码问题

get请求:

使用Servlet处理get请求时,如果get请求的参数中有中文,直接接收会是乱码,这个时候需要使用类似下面的语句来处理乱码:

String name = request.getParameter("name");
System.out.prinlnt(name); // 乱码

// 处理乱码
name = new String(name.getBytes("ISO8859-1"),"UTF-8");
System.out.println(name);// 乱码问题解决

每次中文都要处理,比较麻烦,可以使用过滤器,使用类似下面的代码处理乱码问题:

public String getParameter(String name) {
    String value = super.getParameter(name);
    if (value == null)
        return null;
    String method = request.getMethod();
    if ("get".equalsIgnoreCase(method)) {
        try {
            value = new String(value.getBytes("ISO8859-1"),"UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    return value;
}

为什么我们需要将ISO8859-1转为UTF-8?为什么接收到的参数是ISO8859-1这种编码方式的?
其实很简单,只是个配置问题:
在tomcat安装目录下的conf/server.xml中,有如下的配置:

URIEncoding该配置决定了使用get请求通过浏览器地址栏访问tomcat时的编码方式,默认的编码方式使ISO8859-1:

URIEncoding:This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-``8859``-``1` `will be used.

可以这样配置,则上述代码中,就不需要再从ISO8859-1转为UTF-8了:

URIEncoding="UTF-8"

值得注意的是,从tomcat8.0开始,URIEncoding默认值不再是ISO8859-1,而变成了UTF-8 ,那么也就意味着,从tomcat8.0开始,get请求中的中文参数,不需要特殊处理了

post请求:

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
    // 设置请求体的字符集为 UTF-8,从而解决 post 请求的中文乱码问题
    req.setCharacterEncoding("UTF-8");
    System.out.println("-------------doPost------------");
    // 获取请求参数
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    String[] hobby = req.getParameterValues("hobby");
    System.out.println("用户名:" + username);
    System.out.println("密码:" + password);
    System.out.println("兴趣爱好:" + Arrays.asList(hobby));
}

HttpServletResponse

HttpServletResponse 类的作用

  • HttpServletResponse 类和 HttpServletRequest 类一样。每次请求进来,Tomcat 服务器都会创建一个 Response 对象传递给 Servlet 程序去使用。HttpServletRequest 表示请求过来的信息,HttpServletResponse 表示所有响应的信息
  • 我们如果需要设置返回给客户端的信息,都可以通过 HttpServletResponse 对象来进行设置

两个输出流的说明

  • 字节流 getOutputStream(); 常用于下载(传递二进制数据)

  • 字符流 getWriter(); 常用于回传字符串(常用)

  • 两个流同时只能使用一个,使用了字节流,就不能再使用字符流,反之亦然,否则就会报错

响应的乱码解决

解决响应中文乱码方案一(不推荐使用):

// 设置服务器字符集为 UTF-8
resp.setCharacterEncoding("UTF-8");
// 通过响应头,设置浏览器也使用 UTF-8 字符集
resp.setHeader("Content-Type", "text/html; charset=UTF-8);

解决响应中文乱码方案二(推荐):

// 它会同时设置服务器和客户端都使用 UTF-8 字符集,还设置了响应头
// 此方法一定要在获取流对象之前调用才有效
resp.setContentType("text/html; charset=UTF-8");

请求重定向

请求重定向,是指客户端给服务器发请求,然后服务器告诉客户端说。我给你一些地址。你去新地址访问。叫请求 重定向(因为之前的地址可能已经被废弃)。

image-20210421113301751

请求重定向的第一种方案:

// 设置响应状态码 302 ,表示重定向,(已搬迁)
resp.setStatus(302);
// 设置响应头,说明 新的地址在哪里
resp.setHeader("Location", "http://localhost:8080");

请求重定向的第二种方案(推荐使用):

resp.sendRedirect("http://localhost:8080");

文件上传下载

文件的上传和下载,是非常常见的功能。很多的系统中,或者软件中都经常使用文件的上传和下载。 比如:QQ 头像,就使用了上传。 邮箱中也有附件的上传和下载功能。 OA 系统中审批有附件材料的上传

文件的上传介绍

1、要有一个 form 标签,method=post 请求

2、form 标签的 encType 属性值必须为multipart/form-data

3、在 form 标签中使用input type=file添加上传的文件

4、编写服务器代码(Servlet 程序)接收,处理上传的数据

encType=multipart/form-data表示提交的数据,以多段(每一个表单项一个数据段)的形式进行拼接,然后以二进制流的形式发送给服务器

HTTP 协议的说明

image-20210424203534339

commons-fileupload.jar常用API

commons-fileupload.jar 需要依赖 commons-io.jar 这个包,所以两个包我们都要引入

第一步,就是需要导入两个 jar 包:

  • commons-fileupload-1.2.1.jar

  • commons-io-1.4.jar

commons-fileupload.jarcommons-io.jar 包中,我们常用的类有哪些?

  • ServletFileUpload 类,用于解析上传的数据
  • FileItem类,表示每一个表单项
//判断当前上传的数据格式是否是多段的格式
boolean ServletFileUpload.isMultipartContent(HttpServletRequest request);
public List<FileItem> parseRequest(HttpServletRequest request)//解析上传的数据
//判断当前这个表单项,是否是普通的表单项。还是上传的文件类型
//true 表示普通类型的表单项 false 表示上传的文件类型
boolean FileItem.isFormField()
String FileItem.getFieldName()//获取表单项的 name 属性值
String FileItem.getString() //获取当前表单项的值。
String FileItem.getName(); //获取上传的文件名
void FileItem.write( file ); //将上传的文件写到参数 file 所指向硬盘位置

fileupload 类库的使用

上传文件的表单

<form action="http://192.168.31.74:8080/09_EL_JSTL/uploadServlet" method="post" enctype="multipart/form-data">

用户名:<input type="text" name="username" /> <br>
头像:<input type="file" name="photo" > <br>
<input type="submit" value="上传">

</form>

解析上传的数据的代码

image-20210424204308913

image-20210424204320995

文件下载

image-20210424210255797

下载的常用 API 说明

  • response.getOutputStream();
  • servletContext.getResourceAsStream();
  • servletContext.getMimeType();
  • response.setContentType();

response.setHeader("Content-Disposition", "attachment; fileName=1.jpg");

这个响应头告诉浏览器。这是需要下载的。而attachment表示附件,也就是下载的一个文件。fileName=后面,表示下载的文件名。

完成上面的两个步骤,下载文件是没问题了。但是如果我们要下载的文件是中文名的话。你会发现,下载无法正确显示出正确的中文名。

原因是在响应头中,不能包含有中文字符,只能包含 ASCII 码

中文名乱码问题解决方案

URLEncoder 解决 IE 和谷歌浏览器的附件中文名问题

如果客户端浏览器是 IE 浏览器 或者 是谷歌浏览器。我们需要使用 URLEncoder 类先对中文名进行 UTF-8 的编码 操作。 因为 IE 浏览器和谷歌浏览器收到含有编码后的字符串后会以 UTF-8 字符集进行解码显示。

// 把中文名进行 UTF-8 编码操作。
String str = "attachment; fileName=" + URLEncoder.encode("中文.jpg", "UTF-8");
// 然后把编码后的字符串设置到响应头中 
response.setHeader("Content-Disposition", str);

如果是FireFox,需要Base64编码

image-20210424211335676

七、Cookie&Session

http是无状态的,他不保存状态,意思就是一个浏览器发的请求,随后就断开了,下一次发送请求就和 上一次无关了。

比如一个用户购买一个商品,第一次需要登录,如果再买一个时向服务器发送请求,服务器如果不知道是谁发的,那么他就得再登录一次,这显然是不合理的,于是就提出了cookie和session的概念。

cookie是记录在浏览器端的一个字符串,session是保存在服务器端的一个对象。他们两互相配合让服务器有了能识别客户端一些状态的能力,意思就是服务就就能知道这个客户端有没有登录等。cookie就相当于通行证,session就是门房,进去时需要从门房识别一个身份。

创建时机

  1. 当浏览器向客户端发送请求时,服务器会为他创建一个session,同时相应会加一个头(SetCookie: jsessionid=ewrwerwer123)

  2. 浏览器得到相应就会在在自己这保存下这个字符串,以后访问这个网站的时候就会一直带着。

  3. 当下一个请求发起时,会带着这个cookie的信息,服务器通过查询id找的session,通过session内保存的信息,就能获得这个客户端的状态。

什么是 Cookie?

  • Cookie 翻译过来是饼干的意思
  • Cookie 是服务器通知客户端保存键值对的一种技术
  • 客户端有了 Cookie 后,每次请求都发送给服务器
  • 每个 Cookie 的大小不能超过 4kb

如何创建 Cookie

image-20210419220806656

protected void createCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
//1 创建 Cookie 对象
Cookie cookie = new Cookie("key4", "value4");
//2 通知客户端保存 Cookie
resp.addCookie(cookie);
//1 创建 Cookie 对象
Cookie cookie1 = new Cookie("key5", "value5");
//2 通知客户端保存 Cookie
resp.addCookie(cookie1);
resp.getWriter().write("Cookie 创建成功");
}

服务器如何获取 Cookie

服务器获取客户端的 Cookie 只需要一行代码:req.getCookies():Cookie[]

image-20210419221025141

Cookie 的工具类:

public class CookieUtils {
    /**
     * 查找指定名称的Cookie对象
     * @param name
     * @param cookies
     * @return
     */
    public static Cookie findCookie(String name , Cookie[] cookies){
        if (name == null || cookies == null || cookies.length == 0) {
            return null;
        }

        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())) {
                return cookie;
            }
        }

        return null;
    }

}

Servlet 程序中的代码:

    protected void getCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie[] cookies = req.getCookies();

        for (Cookie cookie : cookies) {
            // getName方法返回Cookie的key(名)
            // getValue方法返回Cookie的value值
            resp.getWriter().write("Cookie[" + cookie.getName() + "=" + cookie.getValue() + "] <br/>");
        }

        Cookie iWantCookie = CookieUtils.findCookie("key1", cookies);

//        for (Cookie cookie : cookies) {
//            if ("key2".equals(cookie.getName())) {
//                iWantCookie = cookie;
//                break;
//            }
//        }
        // 如果不等于null,说明赋过值,也就是找到了需要的Cookie
        if (iWantCookie != null) {
            resp.getWriter().write("找到了需要的Cookie");
        }
    }

Cookie 值的修改

方案一:

1、先创建一个要修改的同名(指的就是 key)的 Cookie 对象 

2、在构造器,同时赋于新的 Cookie 值。 

3、调用 response.addCookie( Cookie )
Cookie cookie = new Cookie("key1","newValue1");
resp.addCookie(cookie);

方案二:

1、先查找到需要修改的 Cookie 对象 

2、调用 setValue()方法赋于新的 Cookie 值。 

3、调用 response.addCookie()通知客户端保存修改
Cookie cookie = CookieUtils.findCookie("key2", req.getCookies());
       if (cookie != null) {
           cookie.setValue("newValue2");
           resp.addCookie(cookie);
       }

Cookie 生命控制

Cookie 的生命控制指的是如何管理 Cookie 什么时候被销毁(删除)

setMaxAge()

  • 正数,表示在指定的秒数后过期
  • 负数,表示浏览器一关,Cookie 就会被删除(默认值是-1)
  • 零,表示马上删除 Cookie
/**
    * 设置存活1个小时的Cooie
    * @param req
    * @param resp
    * @throws ServletException
    * @throws IOException
    */
   protected void life3600(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

       Cookie cookie = new Cookie("life3600", "life3600");
       cookie.setMaxAge(60 * 60); // 设置Cookie一小时之后被删除。无效
       resp.addCookie(cookie);
       resp.getWriter().write("已经创建了一个存活一小时的Cookie");

   }

   /**
    * 马上删除一个Cookie
    * @param req
    * @param resp
    * @throws ServletException
    * @throws IOException
    */
   protected void deleteNow(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       // 先找到你要删除的Cookie对象
       Cookie cookie = CookieUtils.findCookie("key4", req.getCookies());
       if (cookie != null) {
           // 调用setMaxAge(0);
           cookie.setMaxAge(0); // 表示马上删除,都不需要等待浏览器关闭
           // 调用response.addCookie(cookie);
           resp.addCookie(cookie);

           resp.getWriter().write("key4的Cookie已经被删除");
       }

   }

   /**
    * 默认的会话级别的Cookie
    * @param req
    * @param resp
    * @throws ServletException
    * @throws IOException
    */
   protected void defaultLife(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       Cookie cookie = new Cookie("defalutLife","defaultLife");
       cookie.setMaxAge(-1);//设置存活时间
       resp.addCookie(cookie);
   }

Cookie 有效路径 Path 的设置

Cookie 的 path 属性可以有效的过滤哪些 Cookie 可以发送给服务器。哪些不发。

path 属性是通过请求的地址来进行有效的过滤。

CookieA path=/工程路径 

CookieB path=/工程路径/abc

请求地址如下: http://ip:port/工程路径/a.html

CookieA 发送 

CookieB 不发送

http://ip:port/工程路径/abc/a.html

CookieA 发送 

CookieB 发送
Cookie cookie = new Cookie("path1", "path1");
    // getContextPath() ===>>>>  得到工程路径
    cookie.setPath( req.getContextPath() + "/abc" ); // ===>>>>  /工程路径/abc
    resp.addCookie(cookie);
    resp.getWriter().write("创建了一个带有Path路径的Cookie");

Cookie 练习—免输入用户名登录

image-20210419222822461

login.jsp 页面

<form action="http://localhost:8080/13_cookie_session/loginServlet" method="get">
	用户名:<input type="text" name="username" value="${cookie.username.value}"> <br>
	密码:<input type="password" name="password"> <br>
	<input type="submit" value="登录">
</form>

LoginServlet

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
	String username = req.getParameter("username");
	String password = req.getParameter("password");
    if ("wzg168".equals(username) && "123456".equals(password)) {
		//登录 成功
		Cookie cookie = new Cookie("username", username);
		cookie.setMaxAge(60 * 60 * 24 * 7);//当前 Cookie 一周内有效
		resp.addCookie(cookie);
		System.out.println("登录 成功");
	} else {
		// 登录 失败
		System.out.println("登录 失败");
	}
}

Session

什么是 Session 会话?

1、Session 就一个接口(HttpSession)。

2、Session 就是会话。它是用来维护一个客户端和服务器之间关联的一种技术。

3、每个客户端都有自己的一个 Session 会话。

4、Session 会话中,我们经常用来保存用户登录之后的信息。

如何创建 Session 和获取(id 号,是否为新)

如何创建和获取 Session。它们的 API 是一样的。 request.getSession()

  • 第一次调用是:创建 Session 会话

  • 之后调用都是:获取前面创建好的 Session 会话对象

isNew(); 判断到底是不是刚创建出来的(新的)

true 表示刚创建 

false 表示获取之前创建

每个会话都有一个身份证号。也就是 ID 值。而且这个 ID 是唯一的。

getId() 得到 Session 的会话 id 值。

Session 域数据的存取

/**
* 往 Session 中保存数据
* @param req* @param resp
* @throws ServletException
* @throws IOException
*/
protected void setAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
	req.getSession().setAttribute("key1", "value1");
	resp.getWriter().write("已经往 Session 中保存了数据");
}
/**
* 获取 Session 域中的数据
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void getAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
	Object attribute = req.getSession().getAttribute("key1");
	resp.getWriter().write("从 Session 中获取出 key1 的数据是:" + attribute);
}

Session 生命周期控制

public void setMaxInactiveInterval(int interval) 设置 Session 的超时时间(以秒为单位),超过指定的时长,Session 就会被销毁。

值为正数的时候,设定 Session 的超时时长。 

负数表示永不超时(极少使用)

public int getMaxInactiveInterval() 获取 Session 的超时时间

public void invalidate() 让当前 Session 会话马上超时无效

Session 默认的超时时间长为 30 分钟

因为在 Tomcat 服务器的配置文件 web.xml中默认有以下的配置,它就表示配置了当前 Tomcat 服务器下所有的 Session 超时配置默认时长为:30 分钟。

如果说。你希望你的 web 工程,默认的 Session 的超时时长为其他时长。你可以在你自己的 web.xml 配置文件中做 以上相同的配置。就可以修改你的 web 工程所有 Seession 的默认超时时长。

<!--表示当前 web 工程。创建出来 的所有 Session 默认是 20 分钟 超时时长-->
<session-config>
	<session-timeout>20</session-timeout>
</session-config>

Session 超时的概念介绍:

image-20210420170135655

示例代码:

protected void life3(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
	// 先获取 Session 对象
	HttpSession session = req.getSession();
	// 设置当前 Session3 秒后超时
	session.setMaxInactiveInterval(3);
	resp.getWriter().write("当前 Session 已经设置为 3 秒后超时");
}

Session 马上超时示例:

protected void deleteNow(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
	// 先获取 Session 对象
	HttpSession session = req.getSession();
	// 让 Session 会话马上超时
	session.invalidate();
	resp.getWriter().write("Session 已经设置为超时(无效)");
}

浏览器和 Session 之间关联的技术内幕

Session 技术,底层其实是基于 Cookie 技术来实现的。

image-20210420170339207

八、Filter

什么是过滤器

1、Filter 过滤器它是 JavaWeb 的三大组件之一。三大组件分别是:Servlet 程序、Listener 监听器、Filter 过滤器

2、Filter 过滤器它是 JavaEE 的规范。也就是接口

3、Filter 过滤器它的作用是:拦截请求,过滤响应。

拦截请求常见的应用场景有:

1、权限检查

2、日记操作

3、事务管理

Filter 的初体验

要求:在你的 web 工程下,有一个 admin 目录。这个 admin 目录下的所有资源(html 页面、jpg 图片、jsp 文件、等等)都必 须是用户登录之后才允许访问。

思考:根据之前我们学过内容。我们知道,用户登录之后都会把用户登录的信息保存到Session域中。所以要检查用户是否登录,可以判断Session中否包含有用户登录的信息

Object user = session.getAttribute("user");
// 如果等于 null,说明还没有登录
if (user == null) {
	request.getRequestDispatcher("/login.jsp").forward(request,response);
	return;
}

Filter 的工作流程图

image-20210420171201652

Filter 代码:

public class AdminFilter implements Filter {
    /**
    * doFilter 方法,专门用于拦截请求。可以做权限检查
    */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChainfilterChain) throws IOException,ServletException {
   		HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    	HttpSession session = httpServletRequest.getSession();
    	Object user = session.getAttribute("user");
    	// 如果等于 null,说明还没有登录
    	if (user == null) {
    		servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest,servletResponse);
    		return;
    	} else {
    	// 让程序继续往下访问用户的目标资源
    		filterChain.doFilter(servletRequest,servletResponse);
    	}
    }
}

web.xml 中的配置:

<!--filter 标签用于配置一个 Filter 过滤器-->
<filter>
    <!--给 filter 起一个别名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置 filter 的全类名-->
    <filter-class>com.atguigu.filter.AdminFilter</filter-class>
</filter><!--filter-mapping 配置 Filter 过滤器的拦截路径-->
<filter-mapping>
    <!--filter-name 表示当前的拦截路径给哪个 filter 使用-->
    <filter-name>AdminFilter</filter-name>
    <!--url-pattern 配置拦截路径
    / 表示请求地址为:http://ip:port/工程路径/ 映射到 IDEA 的 web 目录
    /admin/* 表示请求地址为:http://ip:port/工程路径/admin/*
    -->
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

Filter 过滤器的使用步骤:

1、编写一个类去实现 Filter 接口

2、实现过滤方法 doFilter()

3、到 web.xml 中去配置 Filter 的拦截路径

Filter 的生命周期

Filter 的生命周期包含几个方法

  • 构造器方法

  • init 初始化方法

    • 第 1,2 步,在 web 工程启动的时候执行(Filter 已经创建)
  • doFilter 过滤方法

    • 第 3 步,每次拦截到请求,就会执行
  • destroy 销毁

    • 第 4 步,停止 web 工程的时候,就会执行(停止 web 工程,也会销毁 Filter 过滤器)

FilterConfig 类

FilterConfig 类见名知义,它是 Filter 过滤器的配置文件类。

Tomcat 每次创建 Filter 的时候,也会同时创建一个 FilterConfig 类,这里包含了 Filter 配置文件的配置信息。

FilterConfig 类的作用是获取 filter 过滤器的配置内容

  • 获取 Filter 的名称 filter-name 的内容
  • 获取在 Filter 中配置的 init-param 初始化参数
  • 获取 ServletContext 对象
@Override
public void init(FilterConfig filterConfig) throws ServletException {
    System.out.println("2.Filter 的 init(FilterConfig filterConfig)初始化");
    // 1、获取 Filter 的名称 filter-name 的内容
    System.out.println("filter-name 的值是:" + filterConfig.getFilterName());
    // 2、获取在 web.xml 中配置的 init-param 初始化参数
    System.out.println("初始化参数 username 的值是:" + filterConfig.getInitParameter("username"));
    System.out.println("初始化参数 url 的值是:" + filterConfig.getInitParameter("url"));
    // 3、获取 ServletContext 对象
    System.out.println(filterConfig.getServletContext());
}

web.xml 配置

<!--filter 标签用于配置一个 Filter 过滤器-->
<filter>
    <!--给 filter 起一个别名-->
    <filter-name>AdminFilter</filter-name>
    <!--配置 filter 的全类名-->
    <filter-class>com.atguigu.filter.AdminFilter</filter-class>
    <init-param>
        <param-name>username</param-name>
        <param-value>root</param-value>
    </init-param>
    <init-param>
        <param-name>url</param-name>
        <param-value>jdbc:mysql://localhost3306/test</param-value>
    </init-param>
</filter>

FilterChain 过滤器链

image-20210420172137716

Filter 的拦截路径

精确匹配

<url-pattern>/target.jsp</url-pattern>
以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/target.jsp

目录匹配

<url-pattern>/admin/*</url-pattern>
以上配置的路径,表示请求地址必须为:http://ip:port/工程路径/admin/*

后缀名匹配

<url-pattern>*.html</url-pattern>
以上配置的路径,表示请求地址必须以.html 结尾才会拦截到
<url-pattern>*.do</url-pattern>
以上配置的路径,表示请求地址必须以.do 结尾才会拦截到
<url-pattern>*.action</url-pattern>
以上配置的路径,表示请求地址必须以.action 结尾才会拦截到

使用 Filter和ThreadLocal组合管理事务

使用 ThreadLocal 来确保所有 dao 操作都在同一个 Connection 连接对象中完成

image-20210425220820180

JdbcUtils 工具类的修改

image-20210425221034230

image-20210425221121726

image-20210425221143361

修改 BaseDao

image-20210425221206385

image-20210425221232144

image-20210425221250908

使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch。来进行实现的管理

image-20210425221339475

Filter 类代码

image-20210425221416401

在 web.xml 中的配置

image-20210425221444049

记得把 BaseServlet 中的异常往外抛给 Filter 过滤器

image-20210425221511723

将所有异常都统一交给 Tomcat,让 Tomcat 展示友好的错误信息页面

image-20210425221540610

九、Listener

简介

监听器用于监听Web应用中某些对象的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当监听范围的对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计网站在线人数、系统加载时进行信息初始化、统计网站的访问量等等。

实现监听:

  1. 创建类实现监听器接口
  2. web.xml文件中配置(注册)监听器<listener> <listener-class>url</listener-class></listener>Servlet3.0后可以通过注解@WebListener 注册监听器

image-20210425221742885

web.xml 的加载顺序是:context- param -> listener -> filter -> servlet

Listener监听三个域对象创建与销毁

监听ServletContext域对象的创建与销毁:实现ServletContextListener接口。

@WebListener
public class MyServletContextListener implements ServletContextListener{

	@Override
	public void contextInitialized(ServletContextEvent arg0) {
		System.out.println("初始化");
	}
	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		System.out.println("销毁了");
	}

}

监听ServletRequest域对象的创建与销毁:实现ServletRequestListener接口。

ServletRequest域对象的生命周期:

创建:访问服务器任何资源都会发送请求(ServletRequest)出现,访问.html.jsp.servlet都会创建请求
销毁:服务器已经对该次请求做出了响应
@WebListener
public class MyServletRequestListener implements ServletRequestListener{

	@Override
	public void requestDestroyed(ServletRequestEvent arg0) {
		System.out.println("ServletRequest销毁了");
	}

	@Override
	public void requestInitialized(ServletRequestEvent arg0) {
		System.out.println("ServletRequest创建了");
	}

}

监听HttpSession域对象的创建与销毁:实现HttpSessionListener接口:
HttpSession域对象的生命周期:

创建:只要调用了getSession()方法就会创建,一次会话只会创建一次
销毁:1.超时(默认为30分钟)2.非正常关闭,销毁3.正常关闭服务器(序列化)
@WebListener
public class MyHttpSessionListener implements HttpSessionListener{

	@Override
	public void sessionCreated(HttpSessionEvent arg0) {
		System.out.println("HttpSession创建了");
	}

	@Override
	public void sessionDestroyed(HttpSessionEvent arg0) {
		System.out.println("HTTPSession销毁了");
	}

}

十、JSON

什么是 JSON?

JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON 采用完全独立于语言的文本格式,而且很多语言都提供了对 json 的支持(包括 C, C++, C#, Java, JavaScript, Perl, Python 等)。 这样就使得 JSON 成为理想的数据交换格式。

json 是一种轻量级的数据交换格式。

  • 轻量级指的是跟 xml 做比较

  • 数据交换指的是客户端和服务器之间业务数据的传递格式

JSON 在 JavaScript 中的使用

json 的定义

json 是由键值对组成,并且由花括号(大括号)包围。每个键由引号引起来,键和值之间使用冒号进行分隔, 多组键值对之间进行逗号进行分隔。

json 定义示例:

var jsonObj = {
    "key1":12,
    "key2":"abc",
    "key3":true,
    "key4":[11,"arr",false],
    "key5":{
        "key5_1" : 551,
        "key5_2" : "key5_2_value"
	},
    "key6":[{
        "key6_1_1":6611,
        "key6_1_2":"key6_1_2_value"},{
        "key6_2_1":6621,
        "key6_2_2":"key6_2_2_value"
    }]
};

json的访问

json 本身是一个对象。

json 中的 key 我们可以理解为是对象中的一个属性。

json 中的 key 访问就跟访问对象的属性一样: json 对象.key

json访问示例:

alert(typeof(jsonObj));// object json 就是一个对象
alert(jsonObj.key1); //12
alert(jsonObj.key2); // abc
alert(jsonObj.key3); // true
alert(jsonObj.key4);// 得到数组[11,"arr",false]
// json 中 数组值的遍历
for(var i = 0; i < jsonObj.key4.length; i++) {
alert(jsonObj.key4[i]);
}
alert(jsonObj.key5.key5_1);//551
alert(jsonObj.key5.key5_2);//key5_2_value
alert( jsonObj.key6 );// 得到 json 数组
// 取出来每一个元素都是 json 对象
var jsonItem = jsonObj.key6[0];
// alert( jsonItem.key6_1_1 ); //6611
alert( jsonItem.key6_1_2 ); //key6_1_2_value

json 的两个常用方法

  • JSON.stringify() 把 json 对象转换成为 json 字符串

  • JSON.parse() 把 json 字符串转换成为 json 对象

json 的存在有两种形式。

  • 一种是:对象的形式存在,我们叫它 json 对象。

  • 一种是:字符串的形式存在,我们叫它 json 字符串。

  • 一般我们要操作 json 中的数据的时候,需要 json 对象的格式。

  • 一般我们要在客户端和服务器之间进行数据交换的时候,使用 json 字符串。

JSON 在 java 中的使用

jackson fastjson Gson

JSON介绍

Java–>json

将一个类以json字符串的形式输出:

//将一个类以json字符串的形式输出
    @Test
    public void test1(){
      	//使用Jackson的ObjectMapper
        ObjectMapper mapper = new ObjectMapper();
        User user = new User();
        user.setMoney(1000);
        user.setUsername("张三");
        user.setPassword("123");
        try {
            System.out.println(mapper.writeValueAsString(user));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

以json字符串输出一个List集合:

@Test
public void test2(){
    ObjectMapper mapper = new ObjectMapper();
    List<User> list = new ArrayList<User>();
    User u = new User("张三", "123", 1000);
    list.add(u);
    u = new User("李四", "456", 2000);
    list.add(u);
    u = new User("王五", "789", 3000);
    list.add(u);
    u = new User("赵六", "555", 4000);
    list.add(u);
    try {
        System.out.println(mapper.writeValueAsString(list));
    } catch (JsonProcessingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

将一个Map以json字符串的形式输出:

@Test
public void test3(){
    ObjectMapper mapper = new ObjectMapper();
    Map<String, String> map = new HashMap<String, String>();
    map.put("username", "张三");
    map.put("password", "123456");
    try {
        System.out.println(mapper.writeValueAsString(map));
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
}

Json–>java

json字符串转为javaBean

public void test5(){
    String str = "{\"id\":1,\"name\":\"三国演义\",\"price\":20,\"author\":\"罗贯中\",\"detail\":{\"pressTime\":\"2001-01-01\",\"storyTime\":\"196-05-06\"},\"attribute\":{\"category\":\"小说\",\"edition\":\"9\"}}";
    ObjectMapper mapper = new ObjectMapper();
    try {
        Book book = mapper.readValue(str, Book.class);
        System.out.println(book.getAuthor()+","+book.getAttribute().getCategory());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

json字符串转为List

//json-->List
    @Test
    public void test6(){
        String str = "[{\"username\":\"张三\",\"password\":\"123\",\"money\":1000},{\"username\":\"李四\",\"password\":\"456\",\"money\":2000},{\"username\":\"王五\",\"password\":\"789\",\"money\":3000},{\"username\":\"赵六\",\"password\":\"555\",\"money\":4000}]";
        ObjectMapper mapper = new ObjectMapper();
        try {
            List<User> us = mapper.readValue(str, new TypeReference<ArrayList<User>>() {});
            for (User user : us) {
                System.out.println(user.getUsername()+","+user.getMoney());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

json字符串转为Map:

//json-->map
@Test
public void test7(){
    String str = "{\"password\":\"888888\",\"username\":\"李四\"}";
    ObjectMapper mapper = new ObjectMapper();
    try {
        Map<String, String> map = mapper.readValue(str, new TypeReference<Map<String, String>>() {});
        for (String key : map.keySet()) {
            System.out.println(key+","+map.get(key));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

十一、Ajax

简介

AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术

  • ajax 是一种浏览器通过 js 异步发起请求,局部更新页面的技术
  • ajax 请求的局部更新,浏览器地址栏不会发生变化 局部更新不会舍弃原来页面的内容

传统请求和异步请求

  • 基于超级链接 地址栏 form表单 地址栏 location.href 发起的请求全部是传统请求
    特点: 请求之后,刷新整张页面
    缺点: 由于刷新了整张页面,用户操作被中断,造成大量网络流量的极大浪费。
  • 基于ajax发起的请求都是异步请求
    特点: 多个请求并行发生,请求之间互不影响,请求之后页面不动,刷新页面的局部

传统网站中存在的问题

  • 网速慢的情况下,页面加载时间长,用户只能等待
  • 表单提交后,如果一项内容不合格,需要重新填写所有表单内容
  • 页面跳转,重新加载页面,造成资源浪费,增加用户等待时间

Ajax 的应用场景

  • 页面上拉加载更多数据
  • 列表数据无刷新分页
  • 表单项离开焦点数据验证
  • 搜索框提示文字下拉列表

Ajax 运行原理

image-20210426102426443

核心对象

XMLHttpRequest 对象是一个javascript对象,存在着浏览器差异。简称xhr对象

Ajax入门

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script type="text/javascript">
		// 1.创建ajax对象
		var xhr = new XMLHttpRequest();
		// 2.告诉Ajax对象要向哪发送请求,以什么方式发送请求
		// 1)请求方式 2)请求地址
		xhr.open('get', 'http://localhost:3000/first');
		// 3.发送请求
		xhr.send();
		// 4.获取服务器端响应到客户端的数据
		xhr.onload = function (){
			console.log(xhr.responseText)
		}
	</script>
</body>
</html>

Ajax 状态码

在创建ajax对象,配置ajax对象,发送请求,以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是ajax状态码。

0:请求未初始化(还没有调用open())
1:请求已经建立,但是还没有发送(还没有调用send())
2:请求已经发送
3:请求正在处理中,通常响应中已经有部分数据可以用了
4:响应已经完成,可以获取并使用服务器的响应了
xhr.readyState // 获取Ajax状态码

onreadystatechange 事件

当 Ajax 状态码发生变化时将自动触发该事件。
在事件处理函数中可以获取 Ajax 状态码并对其进行判断,当状态码为 4 时就可以通过 xhr.responseText 获取服务器端的响应数据了。

不推荐,因为这种方式会由于状态码的变化被触发多次,效率低.推荐使用onload这种方式.唯一不足就是不兼容低版本IE浏览器

image-20210426104807866

原生 ajax 请求的示例

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head>
    <meta http-equiv="pragma" content="no-cache" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    <script type="text/javascript">
	// 在这里使用 javaScript 语言发起 Ajax 请求,访问服务器 AjaxServlet 中 javaScriptAjax
		function ajaxRequest() {
            // 1、我们首先要创建 XMLHttpRequest
            var xmlhttprequest = new XMLHttpRequest();
            // 2、调用 open 方法设置请求参数
      	xmlhttprequest.open("GET","http://localhost:8080/16_json_ajax_i18n/ajaxServlet?action=javaScriptAjax",true)
            // 4、在 send 方法前绑定 onreadystatechange 事件,处理请求完成后的操作。
            xmlhttprequest.onreadystatechange = function(){
                if (xmlhttprequest.readyState == 4 && xmlhttprequest.status == 200) {
                    var jsonObj = JSON.parse(xmlhttprequest.responseText);
                    // 把响应的数据显示在页面上
                    document.getElementById("div01").innerHTML = "编号:" + jsonObj.id + " , 姓名:" +jsonObj.name;
                }
            }
            // 3、调用 send 方法发送请求
            xmlhttprequest.send();
		}
    </script>
   </head>
    <body>
    	<button onclick="ajaxRequest()">ajax request</button>
    	<div id="div01">
    	</div>
    </body>
   </html>

发送GET方式请求

//1. 创建xhr对象
var xhr ; 
if(window.XMLHttpRequest){
	xhr = new XMLHttpRequest(); 
}else{
	xhr = new ActiveXObject("Microsoft.XMLHTTP"); 
}
//2.发送请求,并传递参数
xhr.open("GET", "/ajax_day2/test?name=zhangsan");
xhr.send();
//3.处理响应
xhr.onreadystatechange = function(){ 
  if(xhr.readyState==4 && xhr.status==200){
     console.log(xhr.responseText);
 	}
}

发送POST方式请求

//1. 创建xhr对象
var xhr; if(window.XMLHttpRequest){
xhr = new XMLHttpRequest(); }else{
xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
//2.发送请求,并传递参数
xhr.open("POST","/ajax_day2/test");
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded"); xhr.send("name=zhangsan");
//3.处理响应
xhr.onreadystatechange = function(){
if(xhr.readyState==4 && xhr.status==200){ console.log(xhr.reponseText);
} 
}

Ajax的数据交换机制

image-20210426110333170

image-20210426110704108

Ajax函数封装

问题:发送一次请求代码过多,发送多次请求代码冗余且重复
解决方案:将请求代码封装到函数中,发请求时调用函数即可

利用回调函数

image-20210426112409636

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<script type="text/javascript">
		function ajax (options) {
			// 存储的是默认值
			var defaults = {
				type: 'get',
				url: '',
				data: {},
				header: {
					'Content-Type': 'application/x-www-form-urlencoded'
				},
				success: function () {},
				error: function () {}
			};

			// 使用options对象中的属性覆盖defaults对象中的属性
			Object.assign(defaults, options); //options会覆盖defaults原有的属性

			// 创建ajax对象
			var xhr = new XMLHttpRequest();
			// 拼接请求参数的变量
			var params = '';
			// 循环用户传递进来的对象格式参数
			for (var attr in defaults.data) {
				// 将参数转换为字符串格式
				params += attr + '=' + defaults.data[attr] + '&';
			}
			// 将参数最后面的&截取掉 
			// 将截取的结果重新赋值给params变量
			params = params.substr(0, params.length - 1);

			// 判断请求方式
			if (defaults.type == 'get') {
				defaults.url = defaults.url + '?' + params;
			}

			/*
				{
					name: 'zhangsan',
					age: 20
				}

				name=zhangsan&age=20

			 */

			// 配置ajax对象
			xhr.open(defaults.type, defaults.url);
			// 如果请求方式为post
			if (defaults.type == 'post') {
				// 用户希望的向服务器端`		`传递的请求参数的类型
				var contentType = defaults.header['Content-Type']
				// 设置请求参数格式的类型
				xhr.setRequestHeader('Content-Type', contentType);
				// 判断用户希望的请求参数格式的类型
				// 如果类型为json
				if (contentType == 'application/json') {
					// 向服务器端传递json数据格式的参数
					xhr.send(JSON.stringify(defaults.data))
				}else {
					// 向服务器端传递普通类型的请求参数
					xhr.send(params);
				}

			}else {
				// 发送请求
				xhr.send();
			}
			// 监听xhr对象下面的onload事件
			// 当xhr对象接收完响应数据后触发
			xhr.onload = function () {

				// xhr.getResponseHeader()
				// 获取响应头中的数据
				var contentType = xhr.getResponseHeader('Content-Type');
				// 服务器端返回的数据
				var responseText = xhr.responseText;

				// 如果响应类型中包含applicaition/json
				if (contentType.includes('application/json')) {
					// 将json字符串转换为json对象
					responseText = JSON.parse(responseText)
				}

				// 当http状态码等于200的时候
				if (xhr.status == 200) {
					// 请求成功 调用处理成功情况的函数
					defaults.success(responseText, xhr);
				}else {
					// 请求失败 调用处理失败情况的函数
					defaults.error(responseText, xhr);
				}
			}
		}
    
    ajax({
      type: 'post',
      url: 'http://localhost:3000/responseData',
      sucess:function(data){
        console.log('this is success');
        console.log(data);
      }
    })
    
		/*
			请求参数要考虑的问题

				1.请求参数位置的问题

					将请求参数传递到ajax函数内部, 在函数内部根据请求方式的不同将请求参数放置在不同的位置

					get 放在请求地址的后面

					post 放在send方法中

				2.请求参数格式的问题

					application/x-www-form-urlencoded

						参数名称=参数值&参数名称=参数值

						name=zhangsan&age=20

					application/json

						{name: 'zhangsan', age: 20}

					1.传递对象数据类型对于函数的调用者更加友好
					2.在函数内部对象数据类型转换为字符串数据类型更加方便

		*/
	</script>
</body>
</html>

jQuery 中的 AJAX 请求

ajax 方法

  • url 表示请求的地址
  • type 表示请求的类型 GET 或 POST 请求
  • data 表示发送给服务器的数据
    • 格式有两种:
              一:name=value&name=value
                二:{key:value}
      

success 请求成功,响应的回调函数

dataType 响应的数据类型

  • text 表示纯文本
  • xml 表示 xml 数据
  • json 表示 json 对象
$("#ajaxBtn").click(function(){
	$.ajax({
		url:"http://localhost:8080/16_json_ajax_i18n/ajaxServlet",
		// data:"action=jQueryAjax",
		data:{action:"jQueryAjax"},
		type:"GET",
		success:function (data) {
			// alert("服务器返回的数据是:" + data);
			// var jsonObj = JSON.parse(data);
			$("#msg").html("编号:" + data.id + " , 姓名:" + data.name);
		},
		dataType : "json"
	});
});

$.get 方法$.post 方法

  • url 请求的 url 地址
  • data 发送的数据
  • callback 成功的回调函数
  • type 返回的数据类型
// ajax--get 请求
$("#getBtn").click(function(){
	$.get("http://localhost:8080/16_json_ajax_i18n/ajaxServlet","action=jQueryGet",function (data) {
		$("#msg").html(" get 编号:" + data.id + " , 姓名:" + data.name);
	},"json");
});
// ajax--post 请求
$("#postBtn").click(function(){
	$.post("http://localhost:8080/16_json_ajax_i18n/ajaxServlet","action=jQueryPost",function (data)
	{
		$("#msg").html(" post 编号:" + data.id + " , 姓名:" + data.name);},"json");
});

$.getJSON 方法

  • url 请求的 url 地址
  • data 发送给服务器的数据
  • callback 成功的回调函数
// ajax--getJson 请求
$("#getJSONBtn").click(function(){
	$.getJSON("http://localhost:8080/16_json_ajax_i18n/ajaxServlet","action=jQueryGetJSON",function
	(data) {
		$("#msg").html(" getJSON 编号:" + data.id + " , 姓名:" + data.name);
	});
});

表单序列化 serialize() serialize()可以把表单中所有表单项的内容都获取到,并以 name=value&name=value 的形式进行拼接。

// ajax 请求
$("#submit").click(function(){
	// 把参数序列化
	$.getJSON("http://localhost:8080/16_json_ajax_i18n/ajaxServlet","action=jQuerySerialize&" +
$("#form01").serialize(),function (data) {
	$("#msg").html(" Serialize 编号:" + data.id + " , 姓名:" + data.name);
	});
});

十二、Axios

# axios的引言
	Axios 是一个  异步请求 技术

# 异步请求
	基于XMLHttpRequest对象发起的请求都是异步请求

# 异步请求特点
	请求之后页面不动,响应回来更新的是页面的局部,多个请求之间互不影响,并行执行
	ajax确实用来发送异步请求,ajax过气  
	系统架构 前后端分离架构系统 ---- 异步请求技术-----> Vue 全家桶系列 前端技术端  Vue  淘汰了jQuery 

引入依赖

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

GET方式请求

<!--引入axios的相关依赖-->
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
      //get方式请求
      axios.get('http://localhost:8888/axios/findAll?username=zhangsan&password=123')//发送请求的url
          .then(function(response){
              console.log(response.data);
          })//响应回来触发的回调函数
          .catch(function(err){ //当请求出现错误时回调函数
              console.log(err);
          });
  </script>

POST方式的请求

<!--引入axios的相关依赖-->
   <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
   <script>
       //post请求
       axios.post('http://localhost:8888/axios/save',{name:"zhangsan",age:23}).then(function (response) {
           console.log(response.data);
       }).catch(function (err) {
           console.log(err);
       });
   </script>

总结

  1. axios在发送post方式的请求时传递的参数如果为对象类型,axios会自动将对象转为json格式的字符串使用 application/json的请求头向后端服务接口传递参数
  2. axios的post请求传递参数的两种方式:
  3. 第一种使用字符串进行参数传递: “name=zhangsan&age=23” 这种形式
  4. 第二种方式后端接口直接使用@RequestBody注解形式接收参数:
  5. 发送get跟post时,发送参数名称要跟接受参数名称保持一致(如果是对象的话,则跟对象属性的名称保持一致)

Axios的并发请求

在同一时间发送多个不同的请求到后端服务,最后同一处理不同服务的响应结果

<!--引入axios的相关依赖-->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    //并发请求: 在同一时间发送多个不同的请求到后端服务,最后同一处理不同服务的响应结果
    function findAll(){
        return axios.get("http://localhost:8888/axios/findAll?username=xiaochen&password=123");
    }
    function save(){
        return axios.post('h tp://localhost:8888/axios/save',{name:"xiaosun",age:23})
    }
    //并行发送
    axios.all([findAll(),save()]).then(
        axios.spread(function(result1,result2){
            console.log(result1.data);
            console.log(result2.data);
        })//用来统一处理多个并发请求的执行结果
    );//all用来处理并发请求
</script>

总结

  1. 针对于并发请求需要用到axios.all()函数来完成并发请求的处理
  2. 针对于并发请求的结果汇总需要使用axios.spread()函数来统一汇总请求结果

Axios的高级使用配置对象

配置对象

{
  // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // 默认是 get

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'htt ps://some-domain.com/api/',

  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data) {
    // 对 data 进行任意转换处理

    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理

    return data;
  }],

  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是即将与请求一起发送的 URL 参数
  // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
  params: {
    ID: 12345
  },

  // `paramsSerializer` 是一个负责 `params` 序列化的函数
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // `data` 是作为请求主体被发送的数据
  // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  // 在没有设置 `transformRequest` 时,必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  // - Node 专属: Stream
  data: {
    firstName: 'Fred'
  },

  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  // 如果请求话费了超过 `timeout` 的时间,请求将被中断
  timeout: 1000,

  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // 默认的

  // `adapter` 允许自定义处理请求,以使测试更轻松
  // 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
  adapter: function (config) {
    /* ... */
  },

  // `auth` 表示应该使用 HTTP 基础验证,并提供凭据
  // 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // 默认的

  // `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` 是承载 xsrf token 的值的 HTTP 头的名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // 默认的

  // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `maxContentLength` 定义允许的响应内容的最大尺寸
  maxContentLength: 2000,

  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认的
  },

  // `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
  // 如果设置为0,将不会 follow 任何重定向
  maxRedirects: 5, // 默认的

  // `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
  // `keepAlive` 默认没有启用
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // 'proxy' 定义代理服务器的主机名称和端口
  // `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
  // 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: : {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // `cancelToken` 指定用于取消请求的 cancel token
  // (查看后面的 Cancellation 这节了解更多)
  cancelToken: new CancelToken(function (cancel) {
  })
}

使用配置对象形式发送请求

 var instance = axios.create({
   method:"GET",
   baseURL:"http://localhost:8888",
   data:{  //作为请求体发送的数据,只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
   }
 });

instance.get("/axios/findAll?username=zhangsan");

Axios的Restful风格的API

# Axios的API总结
  axios.request(config)
  axios.get(url[, config])
  axios.delete(url[, config])
  axios.head(url[, config])
  axios.post(url[, data[, config]])
  axios.put(url[, data[, config]])
  axios.patch(url[, data[, config]])
# NOTE:
		在使用别名方法时, url、method、data 这些属性都不必在配置中指定。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!