面向JAVA全栈的HTML进阶

HTML进阶

咱们书接上回,继续我们的HTML学习,上文我们了解了表单的具体格式。

 <form action="服务器地址" method="请求方式" enctype="数据格式">
     <!--表单项-->
     
     <input type="submit" value="提交按钮">
 </form>

好像比较陌生的还是methodenctype这两个属性,我们想要去深入了解methodenctype这两个元素,我们就要去知道http请求.

HTTP请求

http请求可以说是整个web开发的基础,而method正是http请求的一个组成。

对于表单来讲,我们的method请求方式有两种

  • get(默认)提交时,数据跟在URL地址之后
  • post提交时,数据在请求体内

请求方式的不同导致了数据在请求中的位置不一样,这里捏我们要去了解http请求的结构

http请求的组成

分为三部分

  • 请求行包含了请求方式getorpost,请求的URL地址(对应了服务器中方法的URL路径),协议的版本,ps:这里并没有主机ip和端口号,因为捏发送请求的前提是建立了链接,你已经建立了链接主机ip和端口号也就确定下来了,即使不写也没关系了
  • 请求头请求头可以有多行,请求行只有一行格式:头名: 头值
  • 请求体不是所有请求都要有的

由于网页无法设计http请求的原始格式,这里可以用telnet程序测试,下面的HTTP指令都需要用到telnet程序哦,不会的兄弟们可以自行学习配置哦。

我们通过getpost请求来看一下http请求的组成

such as 如果服务器需要一个name数据,我们怎么把name传过去捏

get
 GET /test?name=li HTTP/1.1
 <!-- 上面是请求行 -->
 Host: localhost
 <!-- 上面是请求头 -->
 <!-- Host指明访问服务器中哪一个虚拟主机 -->

get请求中跟在URL后有一个?,?后面有个name=li,这是把数据跟在了URL后面,跟在URL后面的叫查询参数,这样服务器就可以获得这个查询参数的值,所以get请求也就是把数据直接携带在了URL里面

post
 Post /test HTTP/1.1
 <!-- 上面是请求行 -->
 Host: localhost
 Content-type:application/x-wwwwform-urlencoded
 <!-- 决定请求体的类型 -->
 Content-Length:10
 <!-- 决定请求体的长度 -->
 <!-- 上面三行代码是请求头 -->
 ​
 name=zhang
 <!-- 这是请求体 -->

post要完成相同的工作,则是把数据放在了请求体当中。

enctype

在post请求时,指定请求体的数据格式

  • application/x-www-form-urlencoded(默认)即 名字=值
  • multipart/form-data

我们来细嗦一下咱们的默认格式

如果又多个参数我们的格式时什么样捏

 name=zhang&age=18

多个参数之间用&链接捏,这里的请求体增加了, 我们的请求体长度自然也变化了,我们总不能一个一个算帕,小编也懒得算捏,这里,让我们随便打开一个网页,按F12进入控制台,用一点JavaScript的小手段

输入下面这串代码

 "name=zhang&age=18".length
image-20250123193957249

一秒出结果捏,我们再请求头把数据一改即可哦。

那么我们用get请求请求时该怎么办捏,实际上?后面的格式就是默认格式捏,只要再后面将不同数据用&连接起来就好。

有一个小细节捏,这里规避了汉字作为数据捏,在这里汉字是作为特殊字符出现的捏,要传输特殊字符就需要先用URI对特殊字符进行编码,再发送才不会报错捏,但是怎么编码呢,我们又需要一点JavaScript的小手段了

依旧那个控制台,输入

 encodeURIComponent("张")
image-20250123200103810

编码get,我们把引号里面的编码替代汉字就好了捏

 name=%E5%BC%A0

再求一下请求体的长度,改一下长度,这样就,不会报错啦。

对编码感兴趣的小伙伴,我把编码方式放到文末了捏。

另外两种常见请求

json

格式

 POST /test3 HTTP/1.1
 Host:localhost
 Content-Type:application/json
 Content-length:25
 ​
 {"name":"zhang","age":18}

注意这里用小手段求长度时候我们需要用''来包含整个请求体的内容

image-20250123214620775

json就是用字符串来表示对象和数组

 {"属性名":属性值}
 [数组元素1,元素2······]

属性值和元素值可以有的类型:字符串,数字布尔值,null,也可以嵌套其他对象或数组

我们json类型的请求要在服务器怎么接收捏

 @RequestMApping("/test3")
    @ResponseBody
    public Req test3(@RequestBody Req req){
        System.out,println("req:"+ req);
        return req;
    }
    static class Req {
        private String name;
        private int age;
    }
        public String getRname() {
            return name;
        }
        public void setRname(String rname) {
            this.name = rname;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "Req{" +
                    "rname='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
 ​

一个小技巧

jdk16以上的可以简化一下代码,因为新增了record类捏

@RequestMApping("/test3")
@ResponseBody
public Req test3(@RequestBody Req req){
    System.out,println("req:"+ req);
    return req;
}
record Req(String name, int age){}

注意:这里要接收json类型的数据必须要在方法参数上添加@RequstBody

@RequestBody注解的作用就是req这个对象转换为json对象的格式作为响应。

json的优越性:可以直接发送汉字而不需要编码哦

但是你在计算数据长度时候,每有一个汉字需要+2哦,因为一个汉字是三个字节捏,计算长度时候JavaScript是按照一个字节来算的捏。

multipartq

格式

 POST /test2 HTTP/1.1
 Host: localhost
 Content-Type:multipart/form-data;boundary=123
 Content-Length:125
 ​
 --123
 Content-Disposition: form-data;name="name"
 ​
 lisi
 ---123
 Content-Disposition: form-data; name="age"
 ​
 30
 --123--
  • boundary=123用来定义分隔符
  • 起始分隔符是---分隔符
  • 结束分隔符是--分隔符--
  • 其中lisi和30分别为name和age的内容哦

我们该怎么接受捏

 @RequestMapping("/test2")
 @ResponnseBody
 public String test2(String name, Integer age){
     System.out.println(name + " " + age);
     return "收到:" + name + " " + age;
 }

当然我们了解完之后,当然要知道这种请求优越在哪里啦

URI编码的请求而multipart方式的请求可以上传文件。

一个小坑

multipart请求体的长度是怎么求出来的捏,这里不能用双引号也不能用单引号了,因为multipart的请求体里面有换行而单引号双引号均不支持换行,所以我们需要用反引号(键盘是最上面数字键盘最左面的那个)来包裹请求体

image-20250123214956592

这时候你会发现输入116还是会报错的,而上面代码的长度填写的是125,少的九个字节差在哪里捏,这是因为JavaScript的换行符是/n,而http请求的换行符有两个,一个是/r(回车)另一个是/n,所以我们需要加上九个回车符,所以是116+9=125

数据格式小结

客户端发送

  • 编码
    • application/x-www-form-urlencoded:url编码
    • application/json:utf-8编码
    • multipart/form-data:每部分编码可以不同
  • 表单只支持application/x-www-form-urlencodedmultipart/form-data格式发送数据
  • 文件上传需要用multipart/form-data格式
  • JavaScript代码可以支持任意格式发送数据

服务端接收

  • application/x-www-form-urlencodedmultipart/form-data格式的数据,Spring接收方式是统一的,只需要用java bean的属性名对应请求参数名即可
  • 对于application/json格式的数据,Spring接收需要使用@RequestBody注解+java bean的方式

session原理

http无状态,有会话

  • 无状态是指,请求之间相互独立,第一次请求的数据,第二次请求不能重用第一次请求用到name=“zhang”在第二次请求时是不能再用的
  • 有会话是指,客户端和服务端都有相应的技术,可以暂存数据,让数据在请求间共享
image-20250123221646175

我们用一张图来形象理解一下喵,当我们客户端1的用户想要发送请求去寻找数据的时候,并不知道要去那个session对象里面寻找捏,但实际上捏在第一次提交之后,session不仅仅是存储了数据,更重要的是给客户端反馈了一个响应,返回session对象的编号即jsessionid

image-20250123222259128

这时客户端会记住这个编号,同时在第二次会把jssionid连同它的值再次发送给服务器,这样第二次请求一定会去找session对象1,这就是session的工作原理

来看个例子

 GET /s1?name=zhang HTTP/1.1
 Host: localhost
image-20250123223533287

会获得一段特有的cookie

服务器端

 @RequestMApping("/s1")
 @ResponseBody
 public String s1(Httpsession session,String name){
     session.setAttribute("name",name);
     return "数据已存储";
 }

 GET /s2 HTTP/1.1
 Host: localhost
 Cookie:JSESSIONID=560FAB45D02AE09B7E1BC5D9816A5D

服务器端

 @RequestMApping("/s2")
 @ResponseBody
 public String s2(Httpsession session){
     return "取出数据" + session.getAttribute("name");
 }

应用

实现身份认证

image-20250123223835324

这里介绍另一种身份验证技术——jwt技术

image-20250123223947180

不同点在于这里返回了一个token的令牌,把令牌返回给客户端,发送其他请求的时候校验token即可

这里我们来看一下代码实现

生成token

 GET /ji?name=zhang&pass=123 HTTP/1.1
 Host: localhost
image-20250123225858581

服务器端

 @RequestMApping("/j1")
 @ResponseBody
 public String j1(String name,String pass){
     if("zhang".equals(name)&&"123".equals(pass)){
        String token = Jwts.builder().setSubject("name").signWith(key).compact();
            return "身份验证通过:" + token;
     }else{
        return "身份验证失败";
    }
 }

校验token

 GET /j2 HTTP/1.1
 Host: localhost
 Authorization:
 eyJhbGci0iJIUzI1NiJ9.eyJzdWIi0iJ6aGFUZyJ9._1-P_TLIZQPb1_1CyGWplMZaKQ8MCw_plBbYPZ30X28

服务器端

 @RequestMApping("/j2")
 @ResponseBody
 public String j2(@RequesHeader string authorization){
     try{
         System.out.println(authorization);
         Jws<Claims> jws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authorization);
         return "校验通过,你是" + jws.getBody().getSubject();
    }catch (Exception e){
        return "校验失败";
    }
 }

有些同学又要对token的组成好奇了,那么我们来看一下token

实际上这一段token分为三部分用.分隔开

  • eyJhbGci0iJIUzI1NiJ9表示的是header(签名算法)
  • eyJzdWIi0iJ6aGFUZyJ9表示的时payload(数据)
  • _1-P_TLIZQPb1_1CyGWplMZaKQ8MCw_plBbYPZ30X28表示的是所携带的签名

注意前两段都是没有加密的,都是一个json对象只不过时用了base64的编码变成了这个样子,当然我们可以尝试还原一下

 @Test
 public void test(){
     String token= "eyJhbGci0iJIUzI1Ni19.eyJzdWIi0iJ6aGFUZyJ9._1-P_TLIZQPb1_1CyGWplMZaKQ8MCw_plBbYPZ30X28";
     System.out.println(new String(Base64.getDecoder().decode("eyJhbGci0iJIUzI1NiJ9")));
     System.out.println(new String(Base64.getDecoder().decode("eyJzdWIiOiJ6aGFuZyJ9")));
 }
image-20250123231937670

可以发现这里用的是HS256的算法,而且可以反映出数据并没有加密,所以不要把敏感数据放进去哦。

但是既然没被加密为什么token还具有安全性捏,这个安全性来自于签名,签名能保证token的数据不被篡改。也就是说如果我们把数据的base64加密字符串改为管理员的base64加密字符串后再向服务器发送数据替换后的数据就会返回校验失败

image-20250123232656569

关键在于签名,签名的生成来自于前面两部分外加密钥,虽然签名算法和数据是客户端已知的,但是并不知道密钥,如果恶意篡改了数据,则会导致签名变化,就会导致校验失败

特殊字符编码

有些友友对怎么编码很感兴趣捏,那我们就来了解一下吧

UTF-8是最常见的一种字符编码,我们就先来了解一下UTF-8

这里我们用Java代码来实现编码过程

 package java2;
 ​
 import org.junit.jupiter.api.Test;
 import java.nio.charset.StandardCharsets;
 public class bianma {
     @Test
     public void Test() throws Exception {
         byte[] bytes = "张".getBytes(StandardCharsets.UTF_8);
         /*这里返回的是一个字节数组,我们用一个字节数组来接收*/
         System.out.println(bytes);
     }
 }

让我们开始debug,并观察数组内容,将其转化为十六进制

image-20250123202431499

欸好像和编码后的数据很像欸,这里URI编码的秘密也揭开了哦,就是保留了特殊字符UTF-8的十六进制编码欸

暂无评论

发送评论 编辑评论


				
上一篇
下一篇