整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

如何快速学习 Java 系列之跨域(CORS)请求(

如何快速学习 Java 系列之跨域(CORS)请求(day 5)

天(第 4 天),我们实现了第一个 API —— echo,并通过 httpie 成功调用。今天我们来尝试一下用浏览器来调用是否还能成功调通?答案是否定的。原因就是我们今天要学习的 浏览器同源策略 导致的,同时引出了 CORS 实现跨域访问。本文主要内容包括:

  • CORS
  • 同源策略
  • 支持 CORS 跨域访问
  • 预检请求 Preflight Request



浏览器调用 API 的尝试

先来回顾一下,在 day 4 文章的实例中,我们已经通过 httpie 成功的调用了 echo 接口,如下图:

非浏览器成功调用

下面我们来写个 JavaScript 脚本,通过浏览器来调用 echo 接口。


  • 直接运行 HTML 文件调用接口

新建一个 html 文件,代码如下:

<!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>
  <button onclick="callEcho()">call /api/util/echo</button>
  <script>
    function callEcho() {
      fetch('http://localhost:8991/api/util/echo', {
        method: 'post',
        headers: {
          'Content-Type': 'application/json'
        }, 
        body: JSON.stringify({ name: 'zhangsan', num: 100 })
      })
      .then(res=> res.json())
      .then(res=> {
        console.log(res)
      })
      .catch(err=> {
        console.log(err)
      })
    }
  </script>
</body>
</html>

保存后,双击用浏览器打开该 HTML,点击按钮即可触发调用 echo 接口,调用结果如下:

执行失败,报被 CORS 策略阻塞


  • 通过 HTTP 服务器调用接口

下面是 VUE 代码,添加到 VUE 项目中,执行 yarn -dev 命令运行,点击按钮,触发调用 echo 接口。

<script setup>
  function callApiEcho() {
    fetch('http://localhost:8991/api/util/echo', {
      method: 'post',
      headers: {
        'Content-Type': 'application/json'
      }, 
      body: JSON.stringify({ name: 'zhangsan', num: 100 })
    })
    .then(res=> res.json())
    .then(res=> {
      console.log(res)
    })
    .catch(err=> {
      console.log(err)
    })
  }
</script>

<template>
  <el-button type="primary" @click="callApiEcho">调用 /api/util/echo</el-button>
</template>

这种方式运行是有 HTTP 服务器的,调用的结果如下图,它更清晰的指出了源域 IP

执行失败,报被 CORS 策略拒绝


跨域(CORS)和同源策略(SOP)

CORSCross Origin Resource Sharing, 俗称“跨域”,全称“跨域资源共享”,是每个 WEB 项目开发人员,不管是前端还是后端,都会遇到的问题。

跨域问题是浏览器为了安全才有的,使用其它客户端工具,比如 httpiecurl 等都没有该问题。

Web 浏览器实现了一种被称为同源策略的安全机制,防止网页在不同域中访问资源,包括 API;而 CORS 提供了一种安全的方式,允许一个域(源域,用 origin 表示)调用另一个域中的资源,即允许在一个域下运行的 web 应用程序访问另一个域。

SOPSame Origin Policy同源策略同源是指协议、域名和端口都相同,任何一个不相同都不算同源


跨域请求和响应

CORS Request 有两类:"simple" requests"preflight" requests,浏览器自己会决定使用哪种请求,无需我们人为的干预。我们需要了解该机制即可。

  • 简单请求 Simple requests (GET, POST, HEAD)

当请求满足下面条件时,浏览器将该请求视为“simple”请求:

(1)使用 GETPOST HEAD 请求

(2)使用 CORS safe-listed header

(3)使用 Content-Type header 值为 application/x-wwww-form-urlencodedmultipart/form-datatext/plain

(4)没有在任何 XMLHttpRequestUpload 对象上注册事件侦听器

(5)请求中未使用 ReadableStream 对象

满足这些条件的请求,则被允许继续正常执行,不会被阻止,并且在返回响应时检查 Access-Control-Allow-Origin header

  • 预检请求 Preflight requests (OPTIONS)

如果不是“simple” request,浏览器将使用 HTTP OPTIONS 方法自动发出预检请求预检请求用于确定服务端确切的 CORS 能力,判断服务端是否理解预期的 CORS 协议。 如果 OPTIONS 调用的结果指示无法请求,则不会再发起对服务端的实际请求

预检请求将请求模式设置为 OPTIONS,并设置一组 header 来描述接下来的请求:

(1)Access-Control-Request-Method:请求的预期方法(如 GET、POST

(2)Access-Control-Request-Headers:将随请求一起发送的自定义 header 的名称

(3)Origincurrent origin

预检请求举例:

curl -i -X OPTIONS localhost:3031/api/echo \
-H 'Access-Control-Request-Method: GET' \
-H 'Access-Control-Request-Headers: Content-Type, Accept' \
-H 'Origin: http://localhost:3030'

这个例子表示,客户端向服务端询问:我想向从 http://localhost:3030 向 http://localhost:3031/api/echo 发起一个 Get 请求,该请求包含 “Content-Type, Acceptheader,是否可以?

服务器判断后,在响应中包含一些类似 Access-Control-*header,以指示是否允许随后的请求。 这些 header 有如下几种:

(1)Access-Control-Allow-Origin: 表示允许发请求的源, “*” 表示允许所有源访问

(2)Access-Control-Allow-Methods: 允许的 HTTP methods,以逗号分隔

(3)Access-Control-Allow-Headers: 允许发送的 custom headers,以逗号分隔

(4)Access-Control-Max-Age: preflight request 预请求响应结果的缓存时长,在这段时长内,再次调用该接口不用进行预请求调用。

一个 preflight 请求的 Response 可能是像下面这样的:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Vary: Access-Control-Request-Headers
Access-Control-Allow-Headers: Content-Type, Accept
Content-Length: 0
Date: Fri, 05 Apr 2023 11:41:08 GMT
Connection: keep-alive

看到这里,是不是已经明白为什么在浏览器调试工具“网络”窗口中经常看到发出一次请求,有两条 log 的原因了吧?

一次调用显示两条 log

对的,没错就是因为其中一条是预检请求,另外一条才是真实请求


后端跨域的实现

  • 添加跨域配置

后端跨域配置

添加 CorsConfig.java 文件,代码如下。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                // When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header.
                // To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
                .allowedOrigins("*")
                //.allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "DELETE")
                .allowedHeaders("*")
                // restful api 是无状态的,无需缓存 cookie 等信息
                //.allowCredentials(true)
                // Access-Control-Max-Age header 表明预检请求响应的有效时间。在有效时间内,浏览器无须为同一请求再次发起预检请求。
                // 请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。
                // 在预检中,浏览器发送的头中包含有 HTTP 方法和真实请求中会用到的头。
                // 也就是说对于同样的请求,在 max-age 规定的时间内就不用再次通过预检了,就可以直接请求了,单位s
                .maxAge(1800);
    }
}
  • 跨域调用

支持跨域之后,我们再分别用 httpie 和 浏览器来调用 echo 接口看看有什变化。

跨域前后 httpie 调用结果对比

跨域前后 chrome 调用结果对比

从上面实践可以看到,支持跨域后,浏览器能成功调用 echo 接口了。


预检请求(Preflight Request)实战

最后我们再来增加一个 delete 接口,来亲自见识一下“预检请求”。

从上面的解释,我们知道预检请求不能是 GETPOST 这种请求,而我们已有的 echo 接口是一个 POST 请求,所以需要新增一个符合条件的接口,这里我们增加一个 HTTP DELETE 接口来演示。

增加 delete 接口

  • 添加接口

增加一个新的 Controller,并添加 delete 接口,采用 @DeleteMapping 表示使用 HTTP DELETE 方法来请求。

import com.example.springdemo.dto.ProductQueryDto;
import com.example.springdemo.model.Result;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("api/product")
public class ProductController {
    @DeleteMapping("delete")
    public Result<String> delete(@RequestBody ProductQueryDto param) {
        System.out.printf("[product][del] %s\n", param.getId());
        Result<String> res=new Result<>();
        return res.setData(param.getId());
    }
}

增加 ProductQueryDto,用于接口传参。这里大家先不用去管什么是 DTO,什么是 Model,后面的分享会逐一说明的。

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ProductQueryDto {
    private String id;
}
  • 添加调用 delete 接口的 JavaScript 代码
<button onclick="callDeleteProduct()">call /api/product/delete</button>
  <script>
    function callDeleteProduct() {
      fetch('http://localhost:8991/api/product/delete', {
        method: 'delete',
        headers: {
          'Content-Type': 'application/json'
        }, 
        body: JSON.stringify({ id: 'p001' })
      })
      .then(res=> res.json())
      .then(res=> {
        console.log(res)
      })
      .catch(err=> {
        console.log(err)
      })
    }
  </script>
  • 运行并调用 delete 接口

一次调用,有两条log

点击这两条数据,查看两次调用的请求头和请求响应,对比如下图:

预检请求和真正请求的对比

小结,今天掌握了浏览器的同源策略 SOP,实现跨域访问 CORS 的方法,学习了预检请求 Preflight Request 和 Simple Request,自己定义了一个符合需要 Preflight Request 的接口,通过代码亲自做了实践。

这里是云端源想IT,帮你轻松学IT”

嗨~ 今天的你过得还好吗?

我们总是先扬起尘土

然后抱怨自己看不见

- 2024.04.17 -

JavaScript是一种轻量级的编程语言,通常用于网页开发,以增强用户界面的交互性和动态性。然而在HTML中,有多种方法可以嵌入和使用JavaScript代码。

本文就带大家深入了解如何在HTML中使用JavaScript。



一、使用 script 标签

要在HTML中使用JavaScript,我们需要使用<script>标签。这个标签可以放在<head>或<body>部分,但通常我们会将其放在<body>部分的底部,以确保在执行JavaScript代码时,HTML文档已经完全加载。

使用 <script> 标签有两种方式:直接在页面中嵌入 JavaScript 代码包含外部 JavaScript 文件。


包含在 <script> 标签内的 JavaScript 代码在浏览器总按照从上至下的顺序依次解释。


所有 <script> 标签都会按照他们在 HTML 中出现的先后顺序依次被解析。



HTML 为 <script> 定义了几个属性:

1)async:可选。表示应该立即下载脚本,但不妨碍页面中其他操作。该功能只对外部 JavaScript 文件有效。


如果给一个外部引入的js文件设置了这个属性,那页面在解析代码的时候遇到这个<script>的时候,一边下载该脚本文件,一边异步加载页面其他内容。


2)defer:可选。表示脚本可以延迟到整个页面完全被解析和显示之后再执行。该属性只对外部 JavaScript 文件有效。


3)src:可选。表示包含要执行代码的外部文件。


4)type:可选。表示编写代码使用的脚本语言的内容类型,目前在客户端,type 属性值一般使用 text/javascript。不过这个属性并不是必需的,如果没有指定这个属性,则其默认值仍为text/javascript。



1.1 直接在页面中嵌入JavaScript代码

内部JavaScript是将JavaScript代码放在HTML文档的<script>标签中。这样可以将JavaScript代码与HTML代码分离,使结构更清晰,易于维护。


在使用<script>元素嵌入JavaScript代码时,只须为<script>指定type属性。然后,像下面这样把JavaScript代码直接放在元素内部即可:

<script type="text/javascript">
function sayHi(){
alert("Hi!");
}
</script>


如果没有指定script属性,则其默认值为text/javascript。


包含在<script>元素内部的JavaScript代码将被从上至下依次解释。在解释器对<script>元素内部的所有代码求值完毕以前,页面中的其余内容都不会被浏览器加载或显示。


在使用<script>嵌入JavaScript代码的过程中,当代码中出现"</script>"字符串时,由于解析嵌入式代码的规则,浏览器会认为这是结束的</script>标签。可以通过转义字符“\”写成<\/script>来解决这个问题。


1.2 包含外部 JavaScript 文件

外部JavaScript是将JavaScript代码放在单独的.js文件中,然后在HTML文档中通过<script>标签的src属性引用这个文件。这种方法可以使代码更加模块化,便于重用和共享。


如果要通过<script>元素来包含外部JavaScript文件,那么src属性就是必需的。这个属性的值是一个指向外部JavaScript文件的链接。

<script type="text/javascript" src="example.js"></script>


  • 外部文件example.js将被加载到当前页面中。
  • 外部文件只须包含通常要放在开始的<script>和结束的</script>之间的那些JavaScript代码即可。



与解析嵌入式JavaScript代码一样,在解析外部JavaScript文件(包括下载该文件)时,页面的处理也会暂时停止。

注意:带有src属性的<script>元素不应该在其<script>和</script>标签之间再包含额外的JavaScript代码。如果包含了嵌入的代码,则只会下载并执行外部脚本文件,嵌入的代码会被忽略。

通过<script>元素的src属性还可以包含来自外部域的JavaScript文件。它的src属性可以是指向当前HTML页面所在域之外的某个域中的完整URL。

<script type="text/javascript" src="http://www.somewhere.com/afile.js"></script>

于是,位于外部域中的代码也会被加载和解析。


1.3 标签的位置

在HTML中,所有的<script>标签会按照它们出现的先后顺序被解析。在不使用defer和async属性的情况下,只有当前面的<script>标签中的代码解析完成后,才会开始解析后面的<script>标签中的代码。


通常,所有的<script>标签应该放在页面的<head>标签中,这样可以将外部文件(包括CSS和JavaScript文件)的引用集中放置。



然而,如果将所有的JavaScript文件都放在<head>标签中,会导致浏览器在呈现页面内容之前必须下载、解析并执行所有JavaScript代码,这可能会造成明显的延迟,导致浏览器窗口在加载过程中出现空白。


为了避免这种延迟问题,现代Web应用程序通常会将所有的JavaScript引用放置在<body>标签中的页面内容的后面。这样做可以确保在解析JavaScript代码之前,页面的内容已经完全呈现在浏览器中,从而加快了打开网页的速度。


二、执行JavaScript 程序

JavaScript 解析过程包括两个阶段:预处理(也称预编译)执行

  • 在编译期,JavaScript 解析器将完成对 JavaScript 代码的预处理操作,把 JavaScript 代码转换成字节码;
  • 在执行期,JavaScript 解析器把字节码生成二进制机械码,并按顺序执行,完成程序设计的任务。


1、执行过程

HTML 文档在浏览器中的解析过程是:按照文档流从上到下逐步解析页面结构和信息。

JavaScript 代码作为嵌入的脚本应该也算做 HTML 文档的组成部分,所以 JavaScript 代码在装载时的执行顺序也是根据 <script> 标签出现的顺序来确定。

你是不是厌倦了一成不变的编程模式?想要突破自我,挑战新技术想要突破自我,挑战新技术?却迟迟找不到可以练手的项目实战?是不是梦想打造一个属于自己的支付系统?那么,恭喜你,云端源想免费实战直播——《微实战-使用支付宝/微信支付服务,网站在线支付功能大揭秘》正在进行,点击前往获取源码!云端源想

2、预编译

当 JavaScript 引擎解析脚本时候,他会在与编译期对所有声明的变量和函数预先进行处理。当 JavaScript 解析器执行下面脚本时不会报错。

alert(a); //返回值 undefined
var a=1;
alert(a); //返回值 1


由于变量声明是在预编译期被处理的,在执行期间对于所有的代码来说,都是可见的,但是执行上面代码,提示的值是 undefined 而不是 1。

因为变量初始化过程发生在执行期,而不是预编译期。在执行期,JavaScript 解析器是按照代码先后顺序进行解析的,如果在前面代码行中没有为变量赋值,则 JavaScript 解析器会使用默认值 undefined 。


由于第二行中为变量 a 赋值了,所以在第三行代码中会提示变量 a 的值为 1,而不是 undefined。

fun(); //调用函数,返回值1
function fun(){
alert(1);
}

函数声明前调用函数也是合法的,并能够正确解析,所以返回值是 1。但如果是下面这种方式则 JavaScript 解释器会报错。

fun(); //调用函数,返回语法错误
var fun=function(){
alert(1);
}

上面的这个例子中定义的函数仅作为值赋值给变量 fun 。在预编译期,JavaScript 解释器只能够为声明变量 fun 进行处理,而对于变量 fun 的值,只能等到执行期时按照顺序进行赋值,自然就会出现语法错误,提示找不到对象 fun。

总结:声明变量和函数可以在文档的任意位置,但是良好的习惯应该是在所有 JavaScript 代码之前声明全局变量和函数,并对变量进行初始化赋值。在函数内部也是先声明变量,后引用。

通过今天的分享,相信大家已经对JavaScript在HTML中的应用有了一定的了解。这只是冰山一角,JavaScript的潜力远不止于此。希望这篇文章能激发大家对编程的热情,让我们一起在编程的世界里探索更多的可能性!



我们下期再见!


END

文案编辑|云端学长

文案配图|云端学长

内容由:云端源想分享

TML: HyperText Markup Language 超文本标记语言

HTML代码不区分大小写, 包括HTML标记、属性、属性值都不区分大小写;

任何空格或回车键在代码中都无效,插入空格或回车有专用的标记,分别是 、<br>

HTML标记中不要有空格,否则浏览器可能无法识别。

如何添加注释(comment:评论;注释)

<!-- -->
<comment></comment>
<!-- --> 不能留有空格


字符集

<meta http-equiv="Content-Type" content="text/html;charset=#"/>


<base target="_blank">

可以将a链接的默认属性设置为_blank属性

单个标签要有最好有结束符(可以没有结束符)

<br/> <img src="" width="" /> 

便于兼容XHTML(XHTML必须要有结束符)

HTML标签的属性值可以有引号,可以没有引号,为了提高代码的可读性,推荐使用引号(单引号和双引号),尽管属性值是整数,也推荐加上引号。

<marquee behavior="slide"></marquee> 

便于兼容XHTML(XHTML必须要有引号)

<marquee behavior=slide></marquee>

经过测试,以上程序都可以正确运行


HTML标签涉及到的颜色值格式:

color_name 规定颜色值为颜色名称的文本颜色(比如 "red")。

hex_number 规定颜色值为十六进制值的文本颜色(比如 "#ff0000")。

rgb_number 规定颜色值为 rgb 代码的文本颜色(比如 "rgb(255,0,0)")。

transparent 透明色 color:transparent

rgba(红0-255,绿0-255,蓝0-255,透明度0-1)

opacity属性: 就是葫芦娃兄弟老六(技能包隐身)

css:

div{opacity:0.1} /*取值为0-1*/

英文(颜色值)不区分大小写

HTML中颜色值:采用十六进制兼容性最好(十六进制显示颜色效果最佳)

CSS中颜色值:不存在兼容性

红色 #FF0000

绿色 #00FF00

蓝色 #0000FF

黑色: #000000

灰色 #CCCCCC

白色 #FFFFFF

青色 #00FFFF

洋红 #FF00FF

黄色 #FFFF00


请问后缀 html 和 htm 有什么区别?

答: 1. 如果一个网站有 index.html和index.htm,默认情况下,优先访问.html

2. htm后缀是为了兼容以前的DOS系统8.3的命名规范

XHTML与HTML之间的关系?

XHTML是EXtensible HyperText Markup Language的英文缩写,即可扩展的超文本标记语言.

XHTML语言是一种标记语言,它不需要编译,可以直接由浏览器执行.

XHTML是用来代替HTML的, 是2000年w3c公布发行的.

XHTML是一种增强了的HTML,它的可扩展性和灵活性将适应未来网络应用更多的需求.

XHTML是基于XML的应用.

XHTML更简洁更严谨.

XHTML也可以说就是HTML一个升级版本.(w3c描述它为'HTML 4.01')

XHTML是大小写敏感的,XHTML与HTML是不一样的;HTML不区分大小写,标准的XHTML标签应该使用小写.

XHTML属性值必须使用引号,而HTML属性值可用引号,可不要引号

XHTML属性不能简写:如checked必须写成checked="checked"

单标记<br>, XHTML必须有结束符<br/>,而HTML可以使用<br>,也可以使用<br/>

除此之外XHTML和HTML基本相同.


网页宽度设置多少为最佳?

960px


target属性值理解

_self 在当前窗口中打开链接文件,是默认值

_blank 开启一个新的窗口打开链接文件

_parent 在父级窗口中打开文件,常用于框架页面

_top 在顶层窗口中打开文件,常用语框架页面


字符集:

charset=utf-8

Gb2312 简单中文字符集, 最常用的中文字符

Gbk 简繁体字符集, 中文字符集

Big5 繁体字符集, 台湾等等

Utf-8 世界性语言的字符集

ANSI编码格式编码格式的扩展字符集有gb2312和gbk

单位问题:

HTML属性值数值型的一般不带单位, CSS必须带单位;


强制刷新

ctrl+F5