序言一:
计算机网络,核心概念,网络协议,其中网络协议的种类非常繁多,其中一些耳熟能详的,IP,UDP,TCP,ICMP…………其中还有一个应用非常广泛的协议——> HTTP,可能今后在公司中UDP,TCP协议很少用到, IP协议可能使用的更少,但 HTTP 大概率是你工作中使用的最为广泛的,同时也是面试中的一个经典考点
目 录:
第 1 部 分 Http协议 …………………………………………………………………………… 点击这里跳转
1.1 HTTP所处TCP/IP五层协议的栈的位置 …………………………………………………………………点击这里跳转
1.2 应用层协议HTTP的设计与应用 ………………………………………………………………………… 点击这里跳转
1.3 HTTP 协议的协议格式、抓包工具 ………………………………………………………………………点击这里跳转
1.4 抓包工具:fiddler的安装和使用 ……………………………………………………………………… 点击这里跳转
1.5 HTTP 请求的格式 …………………………………………………………………………………………点击这里跳转
1.6 HTTP 响应的格式 …………………………………………………………………………………………点击这里跳转
1.7 HTTP 请求详解 ………………………………………………………………………………………… 点击这里跳转
1.8 HTTP 响应详解……………………………………………………………………………………………点击这里跳转
第 2 部 分 利用代码构造HTTP请求 ………………………………………………………… 点击这里跳转
2.1 基于form表单构造HTTP请求……………………………………………………………………………点击这里跳转
2.2 基于ajax 构造HTTP请求………………………………………………………………………………… 点击这里跳转
第 3 部 分 HTTPS …………………………………………………………………………… 点击这里跳转
3.1 运营商劫持……………………………………………………………………………………………… 点击这里跳转
3.2 对称密钥 ………………………………………………………………………………………………… 点击这里跳转
3.3 非对称密钥……………………………………………………………………………………………… 点击这里跳转
3.4 中间人攻击问题 ………………………………………………………………………………………… 点击这里跳转
第 4 部 分 Tomcat……………………………………………………………………………… 点击这里跳转
4.1 Tomcat的安装与使用…………………………………………………………………………………… 点击这里跳转
4.2 maven …………………………………………………………………………………………………… 点击这里跳转
第 5 部 分 servlet……………………………………………………………………………… 点击这里跳转
5.1 七个步骤实现一个servlet版本的 hello world ……………………………………………………… 点击这里跳转
5.2 smart tomcat 安装、使用、常见状态码……………………………………………………………… 点击这里跳转
5.3 servlet 运行原理 + servlet的生命周期(Tomcat 伪代码) …………… ………………………… 点击这里跳转
5.4 servlet 中的关键 api …………………………………………………………………………………… 点击这里跳转
5.5 servlet 案例……………………………………………………………………………………………… 点击这里跳转
5.6 Cookie 和 session …………………………………………………………………………………… 点击这里跳转
5.7 有关 session 的一个案例(网页登录+302重定向)………………………………………………… 点击这里跳转
5.8 文件提交案例…………………………………………………………………………………………… 点击这里跳转
第一部分 : http 协议
1.1、HTTP所处TCP/IP五层协议的栈的位置
在学习网络原理初识的时候,我们又接触到TCP/IP协议五层协议栈,那么如下图,本文中提到的 HTTP 协议就是位于——TCP/IP五层协议的栈——的应用层,并且HTTP在传输层中是基于 TCP 进行传输的(这里更为严谨的说法是:HTTP 1 和 HTTP 2 版本是基于TCP HTTP 3版本是基于UDP的,但是当下互联网大多数使用的是HTTP 1.1 版本),我们知道五层协议栈中上层和下层是有一定关联关系的,上层协议要调用下层协议,下层协议要给上层协议提供支撑,TCP是保证传输的可靠进行,HTTP是基于业务的。传输层协议,主要关注的是端到端之间的传输,TCP终点关注的是可靠传输,在这样的基础上,应用层协议则是站在程序应用的角度,要对传输的数据进行具体的使用,这就和我们的使用是密切相关的。
1.2、应用层HTTP协议的设计与应用
应用层协议很多时候是程序员自己设计的,根据需求场景,设计协议,但是程序员圈子里面,水平是参差不齐的,所以有大佬提前设计好一些协议,直接让大家照搬,HTTP就是其中的一个典型代表,HTTP可以广泛应用最主要的特点,它虽然是已经设计好的,但是其可扩展的能力是非常强的,可以根据实际需要,让程序员可以传输各种自定义类型数据,所以HTTP是一个非常通用的协议~~,那么它的应用场景是大家平时都经常使用的,只要你打开浏览器看见的网页,随便打开一个网站就是使用过到了HTTP协议
1.3、HTTP协议的协议格式、抓包工具
协议格式:数据具体是怎么组织的
UDP/TCP/IP协议的协议格式是 “二进制” 的协议,经常要理解到二进制的比特位, 相比之下,HTTP是“文本格式”的协议,不需要具体理解二进制位,只需理解文本格式,正因为是文本格式,更方便人肉眼观察,如何才能看到 HTTP 的报文格式?可以借助一些抓包工具,获取 HTTP 交互过程中的请求和响应
什么是抓包工具:抓包工具其实就是第三方的程序,在这个网络的通信中就好比 “代理” ,这个过程使用一个例子来进行讲解:有一天我舍友要吃方便面,他说我想吃方便面,帮我买一个去,于是我就跑到超时,买了个方便面交给舍友,这个场景中的我就叫 “代理” ,我是处于老板和舍友间的 “代理”,可以获取到中间的一些数据。这个过程如果舍友自己去,那么我就不知道他去干啥了,但是他让我去买,我就可以知道这中间的一些细节,所以“代理”是可以的得到传话的细节的
图解:
请求和响应,都是要路过代理的,这个时候在代理上,就很容易获取到请求和响应的详细内容,因此,抓包工具就是一个代理.抓包工具就很容易的能够获取到,传输过程中的网络上的详细数据~
1.4、抓包工具:fiddler的安装和使用
fiddler官方下载地址: 点击跳转
安装步骤 ①、:点击 Tree for free 选取Classic 经典版本👇
安装步骤 ②、:填邮箱获取安装包,然后一路 next 即可安装成功,其中无任何难度,这里不展示👇
安装成功后点击进入到 fiddler :
首先,fiddler 左侧列表显示了当前抓到的所有 HTTP/ HTTPS 数据报:如下图👇
这里我们看到HTTPS的数据报也可以抓到,其实HTTPS是HTTP的孪生兄弟,HTTPS是在HTTP的基础上,引入了加密,这个我们到后面在进行讨论
fiddler 的右侧列表:当点击了左侧列表任意一个数据报后,右侧列表就会出现该数据报的具体内容
Ⅰ、红色边框代表HTTP请求、绿色边框代表HTTP请求: 👇
Ⅱ、标签页:fiddler具体以怎样的格式显示HTTP请求和响应,用的最多的就是 raw (原始的),即看到的是 HTTP 请求数据的本体,选取其他选项 fiddler 对数据进行加工,就不是 “原汁原味” 的数据了
Ⅲ、点击 raw 后即可展示,但感觉字实在是太小了,fiddler是不支持字体放大的,我们可以点击 使用记事本打开进行查看:👇
以上就是HTTP请求的原始某样,如果往HTTP socket中按照上述格式去构造数据,并写入 socket 其实实质上就是构造了一个 HTTP 请求
针对响应来说,也有很多选项,我们还是选取 raw,我们看见响应这里是乱码,乱码其实压缩之后的结果,其实一个服务器最贵的硬件是网络带宽,像这些 HTTP 响应一般都会很大,比较占用带宽,为了提高效率,服务器经常返回一些压缩之后的数据,由浏览器收到之后再进行解压缩:👇
点击这里黄色框的内容,就可以对压缩数据进行解压缩,就可以得到 HTTP 响应👇
常见问题:刚下载好的 fiddler 是默认只能抓取 HTTP 数据报,我们需要通过设置使其可以抓到 HTTPS的数据报👇
点击这里的 Tools 中的 Options:👇
再点击 HTTPS 选项进行如下图的配置:注意这里配置好后点击OK 后 fiddler 会提醒你安装 xxx 证书(一串英文),这里一定要要点 “是”!!!!,不点是你就需要进行卸载重装啦~~👇
1.5、 HTTP 请求的格式
这是我们刚才通过 fiddler 抓到的 HTTP 请求:👇
HTTP 请求格式的总结:👇
针对上述的两张图片,我们进行 HTTP 请求格式的学习:👇
Ⅰ、请求分为4个部分:
Ⅱ、请求行包含3个部分:
①、HTTP 方法:方法大概描述了方法大概想干啥~~ GET(获取)即从服务器获取到某些数据
②、URL:描述了要访问的网络上的资源具体是在哪~~
③、版本号:HTTP/1.1 代表当前使用的 HTTP 版本是 HTTP 1.1 版本,1.1 是当下最主流的版本
Ⅲ、请求头(header):包含了很多行,每一行都是一个键值对,使用冒号和空格进行分割👇
以上这些键值对的个数不是固定的,不同的键值对表示的含义也是不相同的,(后面会介绍一些常见键值对的含义)👆
Ⅳ、空行:请求头的结束标志,相当于链表的 null
Ⅳ、请求正文:可选的,不一定有,我们当前的这个请求是不包含请求正文的。我们再看一个包含请求正文的👇
可以看到这样的请求其请求头和请求正文中间相隔一个空行,这里的请求正文就是存在的👆
1.6、 HTTP 响应的格式
响应的基本格式:👇
通过 fiddler 抓取到的 HTTP 响应:👇
Ⅰ、首行包含四个部分:
①、版本号:
②、状态码:描述一个响应是成功的,还是失败的,以及不同的状态码描述了失败的原因
③、状态码的描述,通过一个/一组单次描述当前状态码的含义
Ⅱ、响应头(header):也是键值对结构,每个键值对占一行,每个键值对使用冒号和空格进行分割,同样响应头中的键值对个数也是不确定的,每个键值对表示不同的含义
Ⅲ、空行:表示响应头的结束
Ⅳ、响应正文(body):表示服务器返回给客户端的具体数据,最常见的格式就是 HTML !!!!
1.7 HTTP 请求详解
①、URL:含义为网络上唯一资源的地址符,需要再URL中既要明确主机是谁,又要明确主机上的哪个资源
常见URL:https://www.baidu.com/ https://blog.youkuaiyun.com/baiyang2001?type=blog 我们可以发现当浏览器打开某个网站,地址栏中的填写的网址就是 URL
URL模板:URL都要遵守这样的模板
a、协议方案名:描述当前这个 URL 是给哪个协议使用的,http 是给HTTP使用的,https是给HTTPS使用的………………
b、登录信息:现在很少使用到,上古时期这里会体现用户名和密码
c、服务器地址:当前访问的主机是谁,这里可以是一个 IP 地址,也可以是一个域名
d、端口号:访问主机上的哪个应用程序(这里的端口号大部分情况下是省略,不是说没有,是浏览器会设置默认端口,对于 http 开头的URL 浏览器会使用 80 作为默认端口,https头的URL 浏览器会使用 443 作为默认端口)
e、带层次的文件路径:文件路径,表示当前服务器要访问的资源是啥,虽然请求的 URL 中写的是一个具体文件路径,但服务器上不一定存在一个对应文件,这个文件可能是真实在磁盘上存在的文件,可能是虚拟的,由服务器代码,构造出来的动态数据
c d e 三个内容相当于取快递,c是菜鸟驿站的位置,d是寻找取件师傅,e是取件码,其实 cde就可以描述一个具体资源的位置,
f、查询字符串:本质上是浏览器/客户端 给服务器传递的自定义信息,相当于对获取到的资源进行提出进一步的要求,查询字符串内容本质上也是键值对结构,也是程序员自定义的,外人不认识,查询字符串和路径之间使用 ?来进行分割 👇
红框位置就是查询字符串
g、片段标识符:描述了要访问当前html页面中那个部分的子部分,能够控制浏览器滚动到相关位置
URL小结:其结构看起来复杂,但是和开发关系紧密,只有4个:ip地址(域名),端口号,带层次结构的路径,查询字符串(query string)后两个和写代码密切相关
除此之外,URL还有一个重要的东西:URL encode / URL decode,当 query string 包含特殊字符,需要对其进行转义,这样的过程叫 URL encode 反之还原内容被叫做 URL decode,URL 里面是有很多特殊含义的符号 / : ? ………………这些符号在URL里面是有特殊含义,万一 query string 中也包含这样的符号就可能导致 URL 被解析失败👇
②、方法:
HTTP 中的请求非常多,但是常用也是就是 GET 和 POST,HTTP引入这些方法是为了表示不同的语义,语义:是否有特定的含义,本来设计 HTTP 的大佬希望程序员根据HTTP语义使用这里的方法,但是随着时间的推移,使用就走形了,方法之间的界限也就模糊了,基本都是GET / POST一把梭,基本不考虑语义,正因为这样的因为,导致方法之间的界限没这么清晰了,POST也可以从服务器取,GET也可以向服务器发送数据
Ⅰ、GET:从服务器获取数据
Ⅱ、POST:向服务器发送数据
③、经典面试题:POST 和 GET的区别:
第一句话:先盖棺定论,POST和GET没有本质区别,具体来说,相当于是GET能使用的场景 POST也能替换之,反之亦然。~~但是细节上还是有一些区别的,POST语义是传递数据,GET语义是获取数据但现状是二者可以相互代替。通常情况下,GET是没有body,通过 query string 传输数据,POST没有 query string 通过body 传递数据(不是强制性的要求,既可以遵守习惯,也可以打破习惯)。~~GET请求一般是幂等的,POST请求一般是不幂等的(这个也不是强求的,而是建议),幂等:每次相同的输入,得到的结果是确定的,不幂等:每次相同的输入,得到的结果是不确定的。~~GET可以被缓存,POST是不可以被缓存的,如果是幂等的,记住结果是很有用的,节省下次访问的开销,如果不是幂等的不应该去记。
④、详解 HTTP 请求中的 header(常见的几种键值对)
-
a、Host 键值对:描述要访问的服务器在哪(包含了IP和端口)
-
b、Content-length 键值对:描述了body的长度。补充:HTTP是基于TCP的协议,TCP是一个面向字节流的协议,面向字节流的一个问题——>粘包问题——>解决方法:明确包的边界(使用分割符、使用长度这两者在HTTP中都有体现),如果当前有多个GET请求到达TCP接收缓冲区了,应用程序读取请求的时候以空行为分隔符,如果当前是有若干个POST请求到达缓存区,空行后还有body当应用程序读到空行之后按照Content-length的长度再去读该长度的数据~~
-
c、Content-Type 键值对:描述了body的数据格式吗,这个东西看起来很长但实际使用中还是非常常用的,这里的含义就是 body 中的数据格式和 url 中 query string 是一样的。常见的三种 Content-Type:
Ⅰ、application/x-www-form-urlencoded:
Ⅱ、multipart/form-data:
Ⅲ、application/json:
注意:如果是一个 GET请求里面没有 body 也就没有b + c 这两个键值对了,一般POST都是带body的,
一般登录都是POST实现的,如下图的码云登录界面 👇
可以看到登录请求就是POST请求,那为什么登录要使用POST登录,其实GET也可以实现,那为啥使用的POST更多,我们知道登录肯定需要给服务器传递用户名和密码,如果是GET,习惯把数据放置在URL中的query string中,这样会使得URL变得很长,用户体验不太好,尤其是早期很多的网站,就是把密码明文提交,如果密码明文的出现在URL其实是不太好的,然而POST是把数据放置在body中,用户是看不到的,body放啥对用户的影响都是非常小的
- d、User-Agent(UA):表示当前用户拿着啥样的东西来上网 👇
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53
上古时期浏览区处在一个飞速进步的版本,最开始只能显示文本,后来可以显示图片,再后来可以显示各种样式,多媒体………………所以在如今浏览器高度发达的今天,UA已经没有那么常用了,但随着移动互联网的到来,UA又迎来了新的使命,来区分手机和PC(最大的区别就是屏幕的尺寸和比例),因此服务器可以根据UA来区分当前是电脑还手机,如果是手机就返回手机版的网页,如果是电脑就返回电脑版的网页
- e、Referer:表示当前这个界面是由哪个页面跳转过来的,Referer不是一定有,如果你是直接输入地址,这个时候是没有Referer。Referer也是一个非常有用的字段,网络上的广告是按照点击量计费的
这就是熟知的运营商劫持!!!!!!这里只做一个简单的介绍,在后面HTTPS中再进行详细介绍
- f、 Cookie:浏览器为了安全,不能让页面的 js 访问到电脑上的文件系统,假设某个网页上有恶意代码,你一点击就会触发这个恶意代码,把你电脑上的重要文件都给删除了,但这样的一些安全设置,也带来了一些麻烦,有时候确实需要页面去存储一些持久化数据,方便后续访问网站,最典型的就是需要存储当前用户的身份信息,当用户在登录页面完成身份信息的认证后,此时服务器就会给浏览器返回一个用户的身份信息,浏览器就把这个用户信息保存到一个特定的位置上,后续再访问同一个浏览器的其他界面的时候,浏览器再自动带上这个身份信息,于是服务器就能识别了
Cookie就是浏览器提供给界面的一种能够持久化存储数据的机制,持久化指的是数据不会因为程序/主机重启而消失,写到磁盘里
Cookie的组织形式:
1、先按照域名组织,针对每个域名分别分配一个空间,我访问搜狗,浏览器就会给搜狗记录一组cookie………………
2、一个小房间里面又会按照键值对的方式进行组织
cookie的数据哪里来:服务器返给客户端的:
但是在cookie存储信息时,我们可以发现:cookie是可以删除的,所以还是把重要信息存在服务器上比较靠谱👇
这些关键信息存储在服务器上,我们管这样的存储叫做session会话,服务器这里管理着很多session,每个session里面里面存储了很多用户的关键信息,每个session有一个sessionId叫做会话标识,cookie上存储就是sessionId(会话ID)👇
这里为什么说很像,因为这里的cookie存储的键值对也是程序员自定义的,外人看不懂👆
总结:Cookie是浏览器提供持久化存储数据的机制,Cookie最常用的场景就是存储会话ID进一步的让我们在访问后续页面的时候,能够带上这个ID从而让服务器知道当前的用户信息~~(服务器上保存用户信息这样的机制叫做session)。Cookie也可以用来存储别的信息,具体想存啥由程序员实现!!!
- g、关于session会话的理解:QQ这里面的消息列表就相当于一个会话列表,这里的聊天记录就是具体信息了
1.8 HTTP 响应详解
①、状态码:状态码表示这次请求是成功还是失败,以及失败的原因是啥,HTTP提供的状态码,有很多!!!
-
a、200 OK 浏览器很顺利的就获取到想要的内容
-
b、404 Not Found 要访问的资源不存在
-
c、403 Forbiden 资源存在但是没有权限访问
-
d、405 Method Not Allowed 尝试使用GET访问访问人家服务器,但可能人家只支持POST于是就返回405
-
e、500 Internal Server Error 服务器自己出现了问题,意味着出现了BUG,人家别人的服务器很少会出现这样的问题,但是自己写代码,也是很容易出现这样的问题
-
f、302 重定向,这个词在计算机很多场景中都会涉及到,不仅是HTTP,但是在细节场景上存在细微差别,但是表示核心含义都是一个意思,302也是在登录页面出现的👇
Loction代表下一步要跳转到哪里👆
总结状态码:
第二部分:利用代码构造HTTP请求
前提:构造请求的两种基本方法:
- 1、HTML / JS(客户端请求,最常见的HTTP客户端是浏览器,使用这一套来设计HTTP请求是很顺理成章的事情)
- ①、基于 form 表单
- ②、基于 ajax
- 2、基于 java(这种方案完全可行,实际开发中不如上面的方式多,这里不做介绍)
2.1 基于form表单构造HTTP请求
当前是把请求直接提交给百度服务器,人家并没有处理这样的参数(没理咱),在后面自己写服务器,就可以针对前端的数据进行处理
详细代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="https://www.baidu.com" method="get">
<input type="text" name="username">
<input type="text" name="userid">
<input type="submit" value="提交">
</form>
</body>
</html>
2.2 基于ajax 构造HTTP请求
form 表单这种方式是一种更为原始的方式,会涉及到页面跳转,页面加载出全新的页面,这件事就不这么科学了,尤其是界面十分复杂的时候,随着前端界面越来越复杂,我们希望不是整个页面都加载,而只加载其中需要加载的某个一个小部分,这时候就可以使用 ajax 了,通过 js 代码构造 HTTP 请求,再通过 js 代码来处理这里的响应,并且把得到的一些数据更新到页面上
ajax就是通过异步等待的方式来进行的,首先构造一个 HTTP 请求发送给服务器,但是浏览器不确定服务器啥时候才有响应,于是就先不管了, 浏览器里面先执行其他代码(该干啥干啥),等到服务器的响应回来之后,再由浏览器通知对应的js代码,以回调函数的方式处理响应
在正式开始使用 ajax 构造HTTP请求的时候,我们还需要引入一个第三方库————>jquery.js来实现HTTP请求的构造
上述的 js 代码构造完成后,我们打开浏览器查看控制台,发现并没有得到响应:👇
我们使用 fiddler 抓包来查看响应:👇
可以看到这里响应的状态码是:200 这证明其实是有响应的,但为什么还是返回没有获取到响应 👇
主要的原因是:浏览器禁止 ajax 跨域进行访问,跨域(跨越多个服务器或多个域名),当前页面处在的服务器,是本地服务器,页面中 ajax 请求是URL是百度的服务器,域名是www.baidu.com,这样就触发了跨域访问,啥样的不算跨域,当前页面处在的服务器就是 www.baidu.com ,页面中再通过 ajax 请求URL 域名为 www.baidu.com 这种就不算跨域,上述行为是浏览器提出的限制。当然也是有方法越过这个限制的,如果对方服务器响应中带有相关的响应头,允许跨域操作,就可以被浏览器正常显示了。当先我们构造的 ajax 请求还是无法被浏览器正确处理的,啥时候能正确处理:等我们有自己的服务器的时候,让页面和 ajax 的地址都是一个服务器
第三部分:HTTPS
前提:HTTPS 是 HTTP 的孪生兄弟,HTTPS 在 HTTP 的基础上引入加密层,这就不得不提到运营商劫持
3.1 运营商劫持
之前 HTTP 是明文传输的,所以传输的数据很容易被获取到,也很容易被篡改,引入HTTPS对数据加密,也就能更好的保护数据安全。加密是一个非常复杂的事情,加密之后也不是绝对的安全,只是说破解起来计算量很大,成本很高,HTTPS引入的加密层,成为SSL(旧的叫法) / TLS(新的叫法),在SSL里面所涉及到的加密操作,主要是两种方式,一个是对称加密,一个是非对称加密
3.2 对称密钥
使用同一个密钥既可以加密也可以解密,双向都可以进行
客户端和服务器持有相同的密钥,客户端传输的数据(HTTP请求中的 header 和 body )通过这个对称密钥进行加密,实际在网络上传输的是密文,服务器收到密文后,使用相同密钥再进行解密,拿到明文。被黑客入侵的路由器是获取到的是加密过的请求,明文的是拿不到的,更不可能篡改数据
上述这个过程看起来很美好,但是存在一个致命缺陷,如何保证客户端和服务器持有同一个密钥,尤其是一个服务器对应 n 多个客户端
这个时候必须是不同客户端用不同密钥,如果各个客户端都是一个密钥,黑客只要自己启动一个客户端,就可拿到密钥。所以服务器就要记录各个服务器能够记住不同客户端的密钥都是什么,而且得让客户端和服务器能够传递这个密钥,因为不同客户端有不同密钥,要么是客户端主动生成密钥告诉服务器,要么是服务器生成密钥告诉客户端,所以需要通过网络来传输密钥
由于中间设备被黑客入侵,这个密钥如果是明文传输就很容易被黑客拿到,如果黑客知道了密钥,后面再怎么加密都是形同虚设!!!所以经过上述讨论后,使用对称加密,最大的问题在于密钥能传递过去,明文传递肯定不行,所以针对密钥还需要进行加密,所以这里需要引入非对称加密
3.3 非对称密钥
非对称加密,有两个密钥,一个是公钥,一个是私钥,公钥是人人都能获取到,私钥是只有自己才可以获取到的,我们就可以使用公钥加密,使用私钥解密,或者使用私钥加密,公钥解密。那是否可以通过公钥推导出私钥,理论上是存在可能的,但实际上不行,计算量不可想象~~基于非对称加密,让服务器自己生成一对公钥和私钥,公钥发出去人人都能拿到,私钥自己保存,客户端生成一个对称密钥,客户端就可以通过公钥对对称密钥进行加密,然后把数据传给服务器,服务器在通过私钥解密,
服务器持有私钥,客户端持有公钥,黑客可以持有公钥,但他拿不到私钥,客户端生成的对称密钥,可以基于公钥对对称密钥进行加密,如果黑客拿到这个密文,那么黑客没有私钥所以不能进行解密,也就不知道对称密钥是啥,既然非对称加密这么好使,那为啥不都使用非对称加密,因为非对称加密的开销要远远大于对称加密,如果只是少来少去的用用,如果所有数据都走非对称加密,这个事就成本太大了
上述过程看似十分的完美,但是还是存在一个漏洞,服务器需要把自己的公钥传输给客户端,黑客在中间就可以 篡改这个公钥,这就会引起“中间人攻击”的问题
3.4 中间人攻击问题
当黑客没有鼓捣事情的时候,一切正常的就是如下图这样:
但是在黑客把服务器的公钥替换成自己的公钥后,客户端使用黑客提供的公钥进行对称密钥加密,因此黑客就可以通过自己的私钥来进行解密,黑客就可以拿到对称密钥,之后黑客为了隐藏,使用从服务器拿到的公钥把对称密钥在进行加密,又得到另外一个密文,服务器拿到这个密文后就可以通过私钥解密得到对称密钥,届时黑客就神不知鬼不觉的拿到这个对称密钥,这个过程就叫做中间人攻击
既然存在中间人攻击,怎么解决呢?需要让客户端能够确认当前这个公钥确实是来自服务器的,不是黑客伪造的(这是问题的关键)
因此就需要引入一个第三方公信机构,证明客户端拿到的是一个合法的公钥,因为大家是信任公信机构的
每次都访问公信机构是不是太麻烦,其实客户端自身就会携带一些公信机构的信息,(内置在操作系统中),不需要去通过服务器网络请求,直接通过本地就可以进行验证
第四部分 : Tomcat
前提:tomcat是什么——>Tomcat 是 HTTP 服务器,HTTP也可以认为是基于TCP的,所以HTTP服务器可以看作TCP服务器的基础上,加上一些额外的功能,能够解析请求中的HTTP报文,把请求转换为结构化数据(对象),也能很方便构造HTTP响应,这样的HTTP服务器提供了一组API方便程序员直接调用来操作HTTP协议,简化程序员的开发,HTTP服务器,属于很大的类,在这个分类下,其中包含了很多具体的实现,Tomcat只是其中一种(Java圈子内最有名的)
4.1 Tomcat的安装与使用
Tomcat官方网站………………………………………………………………………………………………点击这里跳转
①、版本挑选:
②、选取合适的版本点击 zip 下载安装包:
③、下载好后启动 tomcat,点击 start up
看到这句话,就证明 tomcat 正确启动了,我们可以看到有一个“淇℃”这个是乱码了,为什么会出现乱码的现象呢——> tomcat 内部编码格式是 utf - 8,但是咱们使用的 Windows 是简体中文版,默认的编码格式是 GBK,cmd 也跟随了系统的字符集,数据按照 utf-8 的格式来构造,但在 cmd 中按照GBK显示,所以会出现乱码,解决乱码就是要统一编码方式,但是把 cmd 改成 utf-8 字符集,需要修改注册表
所以我们就不管了~~,因为这里也是给大家看一下 tomcat 的启动,后面还有其他方式
运行时常见闪退问题常见问题:👇
-
a、环境变量问题:👇
-
b、端口被占用:👇
正常启动之后就可以使用浏览器来访问 tomcat,就可以看到 tomcat 的欢迎界面:👇
感觉这里和直接点开 html 文件是一致的,但这里的是通过 tomcat 访问(网络路径),直接点击是通过本地文件访问的,URL 是不同的,本质上是有区别的,直接点击仅限于自己的主机进行访问,其他人是访问不到的,通过 tomcat 别人是可以访问到的👇
常见启动服务器的问题:👇
④、查看配置文件:通过修改 server.xml 就可以修改 tomcat 默认绑定的端口,默认绑定的端口(8080),用记事本打开进行修改就可以
⑤、查看日志:
⑥、查看曾经部署过的程序(webapps):
正因为,一个 tomcat 上可以同时部署多个 Servlet 程序,所以 tomcat 也被称为 servlet 的 “容器”👇
⑦、静态页面/动态页面:
web开发主要的工作,主要还是在动态页面,接下来的学习终点也是动态页面的构造,学习 tomcat 给程序员提供的这组用来操作 HTTP 的 API ,这组 API 被叫做 servlet
4.2 maven
maven是 java 世界中,非常知名的 “工程管理工具”/ “构造工具” ,核心功能:管理依赖(进行操作A之前,先进行操作B,依赖是很复杂的,而且是嵌套的,咱们写代码是有很多依赖的,经常会依赖标准库。经常也会依赖第三方库——>引入的一些其他 jar 包),构建/编译(调用 jdk ),打包(打包成 jar包 / war包,jar 包就是一种特殊的压缩包,类似于 rar 就是把各类的.class文件都放到了一起,java 程序常见的发布方式)。
maven存在的意义是将这些操作串起来,一气呵成
如何下载按照 maven ————>我们啥也不用干,IDEA自带
①、maven 的使用
a、创建项目:
b、选择项目的所在目录:
c、目录结构:
d、maven 小窗口:(package是最重要的,打包,不仅会完成打包,还会上面的所有操作,打包结果是一个 war 或jar)
e、管理依赖:
第5部分 : servlet
5.1、七个步骤实现一个servlet版本的 hello world
①、创建一个 maven 项目
②、引入依赖:需要在代码中引入 servlet api,这个 api 不是 jdk 内置的,而是第三方的(Tomcat 提供的),Tomcat 对于 java 来说还是属于第三方的。选取如下图的 servlet 版本(这里版本不可以乱选,tomcat 和 jdk 和servlet 版本要配套),复制红色框的代码,复制到 pom.xml 中 👇
一般 maven 默认仓库的位置:👇
③、创建结构目录:虽然当下 maven 帮我们创建好一些目录,但是还不够用,还不足以支撑我们写一个 servlet 项目,因此还需要手动创建一些目录和文件夹(此处也有简单的方法,先不介绍)
注意:这里的目录结构、目录名称一定要按照这样的方式来写
在 web.xml 需要写一些代码,复制粘贴即可:👇
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
④、编写 servlet 代码,创建一个 java 类
⑤、打包:当前的方法没有 main 是不能单独运行的,需要把当前的代码打包,然后部署到 Tomcat 上,由 Tomcat 进行调用,如果把一个完整应用程序,理解成一个能跑的汽车,当前写的 servlet 代码相当于车厢,车头需要车头拉着走,tomcat 就是车头
准备工作:修改 pom.xml,指定 war 包的名字,双击 package
这里面提示 BUILD SUCCESS:
⑥、部署:把 war 包拷贝到 tomcat 中的 webapps 中
⑦、通过浏览器验证程序,输入地址:127.0.0.1:8080
完成上面的操作后,成功打印出来了,hello world 那么这样打印的 hello world 和 HTML页面打印出来的有怎样的区别,这样的页面是动态的,而 HTML 页面是静态的,这样的代码看起来没区别,我们稍加改动就能看到差别,我们在打印出加上一个打印时间,重新打包,部署,验证
重新查看效果:(没刷新一次,每次获取到的时间戳都是不一样的,这就是一个动态页面)
针对上面的操作:
5.2、smart tomcat 安装、使用、常见状态码
在 setting 中安装即可:(在首次使用 tomcat 的时候,需要先简单配置一下,后续就不需要了)
配置好后启动:注意这里看见红色是很正常的,这里的正常就是红色(Tomcat 的日志导入到 IDEA 中)
常见问题:
常见出错问题汇总:
①、404 你要访问的资源在服务器上不存在(出现 404 大概率是细节上出现了问题)
a、路径的错误(少写第一级 或者 第二级路径)
b、路径正确但服务器没有正确把资源加载起来:
web.xml 中存在错误,web.xml 保证 tomcat 能否正确的加载到 webapp
②、405 Method Not allow
这里的 Mehod 指的是 HTTP 中的方法,假设我们把代码中的 doGet 修改成 doPost 方法,我们在地址栏中输入 URL 这样的操作,浏览器是构造了一个 get 请求,而服务器这边写的是 doPost 不能处理 get 请求,就会出现 405
那啥时候浏览器发的是 get 请求:
1、在地址栏直接输入 URL
2、通过 a 标签跳转
3、通过 img/link/script
4、通过 form 表单 指定 get 方法
5、ajax type 指定为 get
那啥时候浏览器发送的是 post 请求:
1、form 表单 method 指定为 post
2、ajax type 指定为 post
如果在重写 doGet 方法时,没有删除这个 super 方法,也会在浏览器显示 405:
我们点进去 super.doGet 方法中查看其源代码:可以发现这里源代码实现就是判断 HTTP 的版本号来返回 405 / 400,所以这里浏览器会显示 405
③、500 : 5开头的是服务器出现异常,并且异常没有处理,异常抛到 tomcat 中,如果异常没有被 catch 捕获,异常就会沿着调用栈,向上传递,看如下的代码,构造一个空指针异常,由于代码中没有处理,届时浏览器就会返回 500
这里的报错信息中会明确告知你错误的类型,以及错误的位置,我们对应修改即可(这个情况可以说是最好解决的)
④、无法访问此网页(大概率是 Tomcat 没有正常的启动,端口占用 or 启动失败)
5.3、servlet 运行原理(Tomcat 伪代码)
Tomcat 其实一个应用程序,运行在用户态的普通进程(Tomcat 也是 java 的进程),用户写的代码(根据请求计算响应),通过 servlet 和 tomcat 进行交互,tomcat 进一步和浏览器之间的网络传输,仍然是走的网络原理哪一套(分装分用),浏览器和服务器之间的交互,这个过程中是否会涉及到 TCP 三次握手,确认应答,是否会涉及到 IP 的分包组包,是否会涉及到以太网的 MTU 这些都是会的(网络传输是一个很复杂的事情)
Tomcat 伪代码:
// 用来存储所有的 Servlet 对象
private List<Servlet> instanceList = new ArrayList<>();
public void start() {
// 根据约定,读取 WEB-INF/web.xml 配置文件;
// 并解析被 @WebServlet 注解修饰的类
// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
Class<Servlet>[] allServletClasses = ...;
// 这里要做的的是实例化出所有的 Servlet 对象出来;
for (Class<Servlet> cls : allServletClasses) {
// 这里是利用 java 中的反射特性做的
// 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
// 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
// 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
Servlet ins = cls.newInstance();
instanceList.add(ins);
}
// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.init();
}
// 利用我们之前学过的知识,启动一个 HTTP 服务器
// 并用线程池的方式分别处理每一个 Request
ServerSocket serverSocket = new ServerSocket(8080);
// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
ExecuteService pool = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = ServerSocket.accept();
// 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
pool.execute(new Runnable() {
doHttpRequest(socket);
});
}
// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
for (Servlet ins : instanceList) {
ins.destroy();
}
}
public static void main(String[] args) {
new Tomcat().start();
}
}
①、Tomcat 初始化
- a、让 tomcat 从指定目录中找到所有要加载的 servlet 类,前面部署的时候,把 servlet 代码编译成 .class,然后打了 war 包,拷贝到 webapps 中,tomcat 就会从 webapps 中找到那些 .class 对应的 servlet 类,并且需要进行加载,当然也不是 tomcat 一启动就行加载,也可能是 “懒加载”,但是此处伪代码中就认为立即加载
- b、根据刚才类加载的结果,给这些类创建 servlet 实例
- c、实例创建好之后,可以调用当前 servlet 的 init 方法(servlet 自带的方法,默认情况下 init 方法啥也不干,在继承 HttpServlet 时重写 init 方法,就可以在这个阶段,帮我们完成一些初始化工作)
- d、创建 TCP socket 监听 8080 端口等待有客户端建立连接,每次有连接过来,accept 就会返回,就给当前线程池中加一个任务,这个任务里就要负责处理这个请求,此处这个主循环和之前讲过的 TCP 回显服务器,本质没啥区别
- e、如果循环退出,tomcat 也会结束,就会依次循环调用每个 servlet 的 destroy 方法(这里 tomcat 通过正常流程结束才会调用 destroy(通过 8005 管理端口发号施令关闭 tomcat) ,非正常流程,此时来不及调用 destroy(直接关闭进程–大部分是这种情况),这里的 destroy 和 init 一样默认啥也不干,可以在用户态代码中重写)
②、Tomcat 处理请求:
class Tomcat {
void doHttpRequest(Socket socket) {
// 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
HttpServletRequest req = HttpServletRequest.parse(socket);
HttpServletRequest resp = HttpServletRequest.build(socket);
// 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态
内容
// 直接使用我们学习过的 IO 进行内容输出
if (file.exists()) {
// 返回静态内容
return;
}
// 走到这里的逻辑都是动态内容了
// 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
// 最终找到要处理本次请求的 Servlet 对象
Servlet ins = findInstance(req.getURL());
// 调用 Servlet 对象的 service 方法
// 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
try {
ins.service(req, resp);
} catch (Exception e) {
// 返回 500 页面,表示服务器内部错误
}
}
}
- a、rep 通过读取 socket 中的数据,然后按照 HTTP 请求的格式来解析,构造成一个 HttpServletRequest 对象
- b、resp 一个空对象
- c、判断当前要请求的文件是否为静态文件,读取文件内容构造到 resp 的 body 中并返回这个 resp 对象
- d、根据请求的 URL 来获取到使用哪个类来进行处理
- e、根据找到的 servlet 对象来调用里面的 service 方法,在 service 方法内部调用 doGet 或者 doPost
class Servlet {
public void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();
if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
}
......
}
}
- f、如果没有找打匹配的 servlet 类返回 404 ,在执行 service 方法时发现未处理异常,就会返回 500
③、servlet 的生命周期(这几个方法的调用时期,生命周期即啥时候该做啥事)
- a、init 初始化阶段,对象创建好之后,就会执行到,用户可以重写这个方法,来执行一些初始化逻辑
- b、service 在处理请求阶段调用,每次来一个请求都要调用一次 service
- c、destroy 退出主循环,tomcat 结束之前会进行调用,来释放资源
5.4、servlet 中关键的 api
①、HttpServlet:咱们的代码就是通过继承这个类,重写这里面的方法,来被 Tomcat 执行到的
我们上面讨论过了 doGet 的用法,那么 doPost 又是怎样的工作流程:👇
这里使用 ajax 构造 Post 请求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$.ajax({
type:'post',
url:'method',
contentType:
success:function(body){
console.log(body);
},
error:function(){
console.log("无法获取响应");
}
})
</script>
</body>
</html>
按照如下的 url 去访问,按 F12 查看控制台,可以查看到针对于 ajax 构造的 post 请求的响应👇
②、HttpServletRequest 对应一个 Http 请求,Http 请求里有啥,这里就有啥,HttpServletResponse 对应一个 HTTP响应 Http 响应里有啥,这里就有啥
a、针对 HttpServletRequest 有以下的方法:按照图片中的方式进行调用即可👇
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-05-31
* Time: 15:19
*/
@WebServlet("/detail")
public class ShowDetails extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("<h3>首行部分</h3>");
stringBuffer.append(req.getProtocol());
stringBuffer.append("<br>");
stringBuffer.append(req.getMethod());
stringBuffer.append("<br>");
stringBuffer.append(req.getRequestURI());
stringBuffer.append("<br>");
stringBuffer.append(req.getContextPath());
stringBuffer.append("<br>");
stringBuffer.append(req.getQueryString());
stringBuffer.append("<br>");
stringBuffer.append("<h3>header 部分</h3>");
Enumeration<String> enumeration = req.getHeaderNames();
while(enumeration.hasMoreElements()){
String headerName = enumeration.nextElement();
String headerValue = req.getHeader(headerName);
stringBuffer.append("headerName: "+headerName+" headerValue: "+headerValue+"<br>");
}
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(stringBuffer.toString());
}
}
获取到 query string 中的键值对,key 和 value:👇
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-05-31
* Time: 15:33
*/
@WebServlet("/dt")
public class HeaderDetail extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String headerName = req.getParameter("userId");
String headerValue = req.getParameter("userClass");
resp.getWriter().write("userId = " + headerName + " userClass = " + headerValue);
}
}
上面我们使用得到的是 Get 方法中 query string 中的内容,我们也可以得到 Post 方法中 body 中的内容
首先要做的就是构造一个 doPost 方法(处理 Post 请求):👇
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-05-31
* Time: 16:02
*/
@WebServlet("PostParameter")
public class PostServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String userId = req.getParameter("userId");
String userClass = req.getParameter("userClass");
resp.getWriter().write("userId = "+ userId + "userClass" + userClass);
}
}
构建前端页面 (form 表单构造 Post 请求):👇
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="PostParameter" method="post">
<input type="text" name="userId">
<input type="text" name="userClass" >
<input type="submit" value="提交">
</form>
</body>
</html>
除了 form 表单,还有可以使用 json 格式进行构造,对于这种格式手动解析并不容易,直接借助第三方库,处理 json 格式的请求,这里主要使用的是 Jackson(通过 maven 把 Jackson 引入)
版本随便选一个就行:👇
先构造请求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- <form action="PostParameter" method="post">
<input type="text" name="userId">
<input type="text" name="userClass" >
<input type="submit" value="提交">
</form> -->
<input type="text" id="userId">
<input type="text" id="userClass" >
<input type="button" value="提交" id="submit">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
let userIdInput = document.querySelector('#userId');
let userClassInput = document.querySelector('#userClass');
let button = document.querySelector('#submit');
button.onclick = function(){
$.ajax({
type:'post',
url:'PostParameter2',
contentType:'application/json',
data:JSON.stringify({
userId : userIdInput.value,
classId : userClassInput.value;
}),
success:function(body){
console.log(body);
},
})
}
</script> -->
</body>
</html>
再用 Jackson 处理请求:把 post 请求中的 body 取出来,转换为 java 中的 body
在遍历的时候,如果发现有匹配的属性,则把当前 key 对应的 value 赋值到该 User 属性中(通过反射),(赋值过程中会进行自动类型转换),如果没有匹配的属性,就跳过取下一个,如果所有键值对都遍历过后,此时的 User 对象就被构造的差不多了
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-05-31
* Time: 16:40
*/
class User{
public int userId;
public int classId;
}
@WebServlet("/PostJson")
public class PostJsonServlet extends HttpServlet {
//创建一个 Jackson 核心对象
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//readValue 把json格式字符串转换为 java中的字符串
//第一个参数表示对哪个字符串进行转换,这个参数可以填写成一个 string也可以填成一个 inputStream 对象
//第二个参数表示要把这个json字符串准换成哪个java对象
User user = objectMapper.readValue(req.getInputStream(),User.class);
resp.getWriter().write("userId" + user.userId + ",classId" + user.classId);
}
}
可以发现这里使用 ajax 构造的 Post 请求是不会跳转的(默认不会产生跳转)
HttpResponse:👇
演示:
①、设置状态码:👇
@WebServlet("/status")
public class RespStatus extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(200);
resp.getWriter().write("hello");
}
}
当设置状态码为 404 的时候:(还是可以显示出 hello),因为服务器返回的状态码,只是告诉浏览器,当前响应是个啥状态,并不影响浏览器照常显示 body 中的内容👇
②、实现一个自动刷新页面效果:给 Http 响应中设置一个 header :Refresh 即可 👇
@WebServlet("/auto")
public class AutoRefresh extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Refresh","1");//设置1s刷新间隔
resp.getWriter().write("time:"+System.currentTimeMillis());
}
}
③、构建重定向(当浏览器发现状态码 302 和 location,就会跳转到对应位置)👇
@WebServlet("/location")
public class LocationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(302);//设置状态码
resp.setHeader("Location","https://www.baidu.com");//设置请求头
}
}
届时 servlet 还提供了一个更简单的方法来实现 302 重定向:(更推荐的做法)👇
resp.sendRedirect("https://www.baidu.com");
5.5、servlet 案例
案例一:服务器版本的表白墙:通过服务器保存数据,在实现这个程序时需要先考虑清楚服务器和客户端如何交换(约定前后端交互接口,自定义应用层协议,既然是搞一个服务器,具体是提供一个啥样的服务,以及这样的服务如何触发,都需要考虑清楚),对于表白墙来说需要提供两个接口👇
-
1、告诉服务器留言了一条啥样的数据(用户点击提交按钮,就会给服务器发送一个 HTTP请求,让服务器把这个消息存下来),约定好为了实现这样的效果,客户端发送一个啥样的 HTTP 请求,服务器返回一个啥样的 HTTP 响应
在咱们未来工作中,尤其是需要前后端程序员共同完成功能时,这种约定前后端交互接口的行为是至关重要的,而且一定要在开发动作之前,第一件要完成的事情👇
-
2、从服务获取到都有哪些留言(当页面加载,就需要从服务器中获取到曾经存储过的这些消息)👇
-
)
编写步骤:
1、创建项目 :创建 maven 项目
2、引入依赖(引入 Jackson servlet mysql)👇
<?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>wall</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
</dependencies>
</project>
3、建立目录结构:创建如图的目录结构👇
4、编写代码
首先就是前端界面的代码,直接使用现成的:👇
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表白墙</title>
</head>
<body>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100%;
}
h3 {
text-align: center;
padding: 30px 0;
font-size: 24px;
}
p {
text-align: center;
color: #999;
padding: 10px 0;
}
.row {
width: 400px;
height: 50px;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
.row span {
width: 60px;
font-size: 20px;
}
.row input {
width: 300px;
height: 40px;
line-height: 40px;
font-size: 20px;
text-indent: 0.5em;
/* 去掉输入框的轮廓线 */
outline: none;
}
.row #submit {
width: 300px;
height: 40px;
font-size: 20px;
line-height: 40px;
margin: 0 auto;
color: white;
background-color: orange;
/* 去掉边框 */
border: none;
border-radius: 10px;
}
.row #submit:active {
background-color: gray;
}
</style>
<div class="container">
<h3>表白墙</h3>
<p>输入后点击提交, 会将信息显示在表格中</p>
<div class="row">
<span>谁: </span>
<input type="text">
</div>
<div class="row">
<span>对谁: </span>
<input type="text">
</div>
<div class="row">
<span>说: </span>
<input type="text">
</div>
<div class="row">
<button id="submit">提交</button>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
function getMessage(){
$.ajax({
type:'get',
url:'message',
success:function(body){
let container = document.querySelector('.container');
for(let message of body){
let div = document.createElement('div');
div.innerHTML = message.from + ' 对 ' + message.to + ' 说: ' + message.message;
div.className = 'row';
container.appendChild(div);
}
}
})
}
getMessage();
// 当用户点击 submit, 就会获取到 input 中的内容, 从而把内容构造成一个 div, 插入到页面末尾.
let submitBtn = document.querySelector('#submit');
submitBtn.onclick = function() {
// 1. 获取到 3 个 input 中的内容.
let inputs = document.querySelectorAll('input');
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
// 用户还没填写完, 暂时先不提交数据.
return;
}
// 2. 生成一个新的 div, 内容就是 input 里的内容. 把这个新的 div 加到页面中.
let div = document.createElement('div');
div.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
div.className = 'row';
let container = document.querySelector('.container');
container.appendChild(div);
// 3. 清空之前输入框的内容.
for (let i = 0; i < inputs.length; i++) {
inputs[i].value = '';
}
let body = {
from:from,
to:to,
message:msg
}
$.ajax({
type:"post",
url:"message",
contentType:"application/json;charset=utf8",
data:JSON.stringify(body),
success:function(body){
alert("提交请求成功");
},
error:function(){
alert("提交请求失败")
}
})
}
</script>
</body>
</html>
先编写服务器代码:👇
再实现数据库逻辑:
前端代码中除界面外的一些逻辑结构:
整体代码:👇
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-05-20
* Time: 12:55
*/
class Message{
public String message;
public String from;
public String to;
}
@WebServlet("/message")
public class MessageWall extends HttpServlet {
//解析json请求的类
private ObjectMapper objectMapper = new ObjectMapper();
//保存信息
// List<Message> messages = new ArrayList<>();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
// messages.add(message);
try {
save(message);
} catch (SQLException e) {
e.printStackTrace();
}
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write("{\"ok\":true}");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Message> messages = null;
try {
messages = load();
} catch (SQLException e) {
e.printStackTrace();
}
String jsonString = objectMapper.writeValueAsString(messages);
resp.setContentType("application/json; charset=utf8");
System.out.println("jsonString:"+jsonString);
resp.getWriter().write(jsonString);
}
private void save(Message message) throws SQLException {
Connection connection = null;
PreparedStatement statement = null;
//和数据库建立连接
connection = DBUtil.getConnection();
//构建sql语句
String sql = "insert into message values(?,?,?)";
//包装sql语句
statement = connection.prepareStatement(sql);
statement.setString(1,message.from);
statement.setString(2,message.to);
statement.setString(3,message.message);
//执行sql语句
statement.executeUpdate();
//关闭资源
DBUtil.close(connection,statement,null);
}
private List<Message> load() throws SQLException {
List<Message> messages = new ArrayList<>();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
//和数据库建立连接
connection = DBUtil.getConnection();
//构建sql语句
String sql = "select * from message";
//包装sql语句
statement = connection.prepareStatement(sql);
//执行sql语句
resultSet = statement.executeQuery();
while (resultSet.next()){
Message message = new Message();
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
messages.add(message);
}
DBUtil.close(connection,statement,resultSet);
return messages;
}
}
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-06-01
* Time: 10:47
*/
public class DBUtil {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/messagewall?characterEncoding=utf8&useSSL=false";
private static final String USERNAME = "root";
private static final String PASSWORD = "1234";
//1、建立数据源
private volatile static DataSource dataSource = null;
private static DataSource getDataSource(){
if(dataSource == null){
synchronized (DBUtil.class){
if(dataSource == null){
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL(URL);
((MysqlDataSource)dataSource).setUser(USERNAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
}
}
return dataSource;
}
//2、建立连接
public static Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
//3、关闭资源
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) throws SQLException {
if(connection != null){
connection.close();
}
if(statement != null){
statement.close();
}
if(resultSet != null){
resultSet.close();
}
}
}
5、打包 + 6、部署 交给 smart tomcat 工作即可👇
表白墙项目总结:
5.6、session 和 cookie
5.7、有关 session 的一个案例(网页登录+302重定向)
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-06-01
* Time: 13:58
*/
@WebServlet("/index")
public class indexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession httpSession = req.getSession(false);
String username = (String) httpSession.getAttribute("username");
Integer count = (Integer) httpSession.getAttribute("count");
count += 1;
httpSession.setAttribute("count",count);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("<h3>欢迎你! " + username + " 这是第 " +count+ " 次访问主页 </h3>");
}
}
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-06-01
* Time: 13:51
*/
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
if("zhangsan".equals(username) && "123".equals(password)){
HttpSession httpSession = req.getSession(true);
httpSession.setAttribute("username",username);
httpSession.setAttribute("count",0);
resp.sendRedirect("index");
}else{
resp.getWriter().write("登录失败");
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="login" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>
5.8、文件提交案例
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Lenovo
* Date: 2022-06-01
* Time: 14:09
*/
@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Part part = req.getPart("MyImage");
System.out.println(part.getSubmittedFileName());
System.out.println(part.getContentType());
System.out.println(part.getSize());
part.write("d:/java102/1.jpg");
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("上传成功!");
}
}
打印出来的照片信息:👇