同源政策与CORS
CORS意为跨源资源共享(Cross origin resource sharing),它是一个W3C标准,由一系列HTTP Header组成,这些 HTTP Header决定了浏览器是否允许JavaScript 代码成功获得跨源请求的服务器响应。
在说CORS之前,先说一下关于浏览器的同源政策(same-origin policy)。
同源政策是浏览器安全的基石,由Netscape公司引入浏览器,目前所有浏览器都实行这个政策。最初它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同":协议相同,域名相同,端口相同。
举例来说,http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。它的同源情况如下:
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
同源政策的是为了保证用户信息的安全,防止恶意的网站窃取数据。设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。所以有时需要一些额外的操作来打破同源政策的束缚。
Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同时,浏览器允许通过设置document.domain共享 Cookie。
举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。
document.domain = 'example.com';
现在,A网页通过脚本设置一个 Cookie。
document.cookie = "test1=hello";
B网页就可以读到这个 Cookie。
var allCookie = document.cookie;
注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策。
另外,服务器也可以在设置Cookie的时候,可以指定Cookie的所属域名为一级域名,比如:
Set-Cookie: key=value; domain=.example.com; path=/
这样的话,二级域名不用做任何设置,都可以读取这个Cookie。
同源政策规定,AJAX请求只能发给同源的网址,否则就报错。有三种方法规避这个限制:JSONP、WebSocke和CORS。
JSONP是早期服务器与客户端跨源通信的常用方法。它的基本思想是,通过在网页上添加一个<script>元素向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
function addScriptTag(src) {var script = document.createElement('script');script.setAttribute("type","text/javascript");script.src = src;document.body.appendChild(script);
}window.onload = function () {addScriptTag('http://example.com/ip?callback=foo');
}function foo(data) {console.log('Your public IP address is: ' + data.ip);
};
上面代码通过动态添加<script>元素,向服务器example.com发出请求。该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
foo({"ip": "8.8.8.8"
});
由于<script>元素请求的脚本,直接作为代码运行。这时只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
下面是浏览器发出的WebSocket请求的头信息
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
上面请求头中有一个字段是Origin,声明了该请求的源。正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。服务器可以根据这个字段,判断是否许可本次通信。如果允许这个源,服务器就会做出如下回应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
接下来就是CORS了。CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准, 堪称跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。
同源安全策略默认阻止“跨源”获取资源,XMLHttpRequest 和 Fetch API 都遵循同源策略,这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源。
现代浏览器支持在API容器中(例如 XMLHttpRequest 或 Fetch)使用 CORS,CORS 机制给了WEB服务器一种权限,允许服务器进行跨源访问控制。服务器可以选择在响应报文中包含正确的CORS响应头,这样浏览器就能允许JavaScript跨源访问WEB服务器提供的资源。
CORS功能概述
CORS标准新增了一组 HTTP Header字段,允许服务器声明哪些源(Origin)通过浏览器有权限访问。另外,标准要求对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预请求(preflight request),获知服务器端是否允许该跨源请求。服务器确认允许后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端是否需要携带身份凭证(Credential,例如 Cookie 和 HTTP 认证相关数据)。CORS请求失败时会产生错误,但是为了安全,在JavaScript代码层面无法获知到底具体是哪里出了问题。只能查看浏览器的控制台以得知具体是哪里出现了错误。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。
凡是不同时满足上面两个条件,就属于非简单请求。
浏览器对这两种请求的处理,是不一样的。
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中增加一个Origin字段。
下面是一个浏览器发送跨源AJAX请求是简单请求例子:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。而服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。如果Origin指定的域名在许可范围内,服务器返回的响应会包含Access-Control-Allow-Origin头信息字段:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml[…XML Data…]
本例中,服务端返回的标头Access-Control-Allow-Origin: * 表明,该资源可以被任意外源访问。
使用 Origin 和 Access-Control-Allow-Origin 就能完成最简单的访问控制。如果 https://bar.other 的资源持有者想限制他的资源只能通过https://foo.example来访问(也就是说,非 https://foo.example 域无法通过跨源访问访问到该资源),可以这样做:
Access-Control-Allow-Origin: https://foo.example
注意: 当响应的是附带身份凭证的请求时(credentialed requests request),服务端必须明确 Access-Control-Allow-Origin 的值,而不能使用通配符“*”(后面还会再说明)。
上面的头信息之中除了Access-Control-Allow-Origin是必须的字段,还有两个可选的字段Access-Control-Allow-Credentials和Access-Control-Expose-Headers。
Access-Control-Allow-Credentials字段的值是一个布尔值,表示是否允许发送Cookie。默认情况下Cookie不包括在CORS请求之中。Access-Control-Allow-Credentials设为true,表示服务器明确许可Cookie可以包含在请求中发给服务器。这个值也只能设为true,如果服务器不允许浏览器发送Cookie,删除该字段即可。
CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到响应头中的6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定,例如:
Access-Control-Expose-Headers: FooBar
这样XMLHttpRequest对象的getResponseHeader('FooBar')可以返回FooBar的值。
withCredentials 属性
上面说到CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器指定Access-Control-Allow-Credentials: true,另一方面开发者必须在AJAX请求中打开withCredentials属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否则,即使服务器同意发送Cookie,浏览器也不会发送。
但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。
xhr.withCredentials = false;
非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前增加一次使用 OPTIONS 方法发起的预检请求(preflight request)到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用可以避免跨域请求对服务器的用户数据产生未预期的影响。
预检请求会询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和请求头字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
如下是一个需要执行预检请求的 HTTP 请求:
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://bar.other/resources/post-here/");
xhr.setRequestHeader("X-PINGOTHER", "pingpong");
xhr.setRequestHeader("Content-Type", "application/xml");
xhr.onreadystatechange = handler;
xhr.send("<person><name>Arun</name></person>");
上面的代码使用 POST 请求发送一个 XML 请求体,该请求的 Content-Type 为 application/xml,且使用了一个自定义的X-PINGOTHER 请求头。浏览器发现这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。
下面是这个"预检"请求的HTTP头信息。
OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。OPTIONS 是 HTTP/1.1 协议中定义的方法,用于从服务器获取更多信息,是安全的方法。该方法不会对服务器资源产生影响。OPTIONS预检请求中除了origin之外同时携带了下面两个标头字段:
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法。Access-Control-Request-Headers告知服务器,实际请求将携带两个自定义请求标头字段:X-PINGOTHER 与 Content-Type。服务器据此决定,该实际请求是否被允许。
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,若确认允许跨源请求,就可以做出如下回应。
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
服务器的响应头携带了 Access-Control-Allow-Origin: https://foo.example,限制请求的源域。Access-Control-Allow-Methods 表明服务器允许客户端使用 POST 和 GET 方法发起请求。Access-Control-Allow-Headers 表明服务器允许请求中携带字段 X-PINGOTHER 与 Content-Type。与 Access-Control-Allow-Methods 一样,Access-Control-Allow-Headers 的值为逗号分割的列表。最后,字段 Access-Control-Max-Age 给定了该预检请求可供缓存的时间长短,单位为秒,默认值是 5 秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。以上例子中,该响应的有效时间为 86400 秒,也就是 24 小时。请注意,浏览器自身维护了一个最大有效时间,如果该标头字段的值超过了最大有效时间,将不会生效。当然此时服务器也可以有可选的Access-Control-Allow-Credentials: true响应头,含义与简单请求时是一样的。
如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。
预检请求完成之后,发送实际请求就跟简单请求一样,会有一个Origin
头信息字段(当然也会包含预检请求中已经说明过的自定义请求头等内容)。服务器的回应也都会有一个Access-Control-Allow-Origin
头信息字段。
POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache<person><name>Arun</name></person>
服务器的响应:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain[Some XML payload]
这里有一个Vary响应头。Vary响应头就是让同一个URL根据某个请求头的不同而使用不同的缓存。这里的设定是让浏览器在即使同一个URL的情况下,也能根据不同的Accept-Encoding或Orign进行不同的缓存。为什么要有这个设定?因为默认浏览器里的缓存是以URL为key,一个 URL 对应一个缓存。但如果浏览器访问了两 URL相同但CORS响应头不应该相同的资源会如何呢?
比如在同一个浏览器下,先打开了foo.taobao.com上的一个页面,访问了服务器的资源,这个资源和URL被浏览器缓存了下来,与资源内容一起缓存的还有Access-Control-Allow-Origin: https://foo.taobao.com响应头。这时又打开 bar.taobao.com上的一个页面,这个页面也访问服务器上之前的那个资源,因为URL相同,这时它会读取本地缓存。但是读到的 Access-Control-Allow-Origin头信息是缓存下的 https://foo.taobao.com 而不是自己想要的 https://bar.taobao.com,这时浏览器就报跨域错误了,虽然它应该是能访问到这份资源的。再比如用户先访问了foo.taobao.com
的一个页面 A,页面 A 里用<img>
标签加载了一张图片,注意这时候这张图片已经被浏览器缓存了,并且缓存里没有 Access-Control-Allow-Origin
响应头,因为<img>
发起的请求不带Origin
请求头,然后用户又访问了foo.taobao.com
的另一个页面 B,页面 B 里用 XHR 请求同一张图片,结果读了缓存,没有发现 CORS 响应头,浏览器报跨域错误。
在CORS的场景下,使用Vary: Origin来保证从不同网站发起的请求可以使用各自的缓存。比如从foo.taobao.com发起的请求缓存中的响应头是:
Access-Control-Allow-Origin: https://foo.taobao.com
Vary: Origin
的话,bar.taobao.com在发起同URL的请求就不会使用这份缓存了,因为Origin请求头变了。
还有<img>标签发起的非 CORS 请求缓存中的响应头是:
Vary: Origin
的话, 在使用 XHR 发起的 CORS 请求也不会使用那份缓存,因为Origin请求头从无到有,也算是变了。
最佳的实践原则是:
If CORS protocol requirements are more complicated than setting `Access-Control-Allow-Origin` to * or a static origin, `Vary` is to be used.
如果你的 Access-Control-Allow-Origin
响应头不是简单的写死成了*
或者某一个固定的源,那么你就应该加上Vary: Origin
响应头。
最后再重复说明一下前面已经提及过的附带身份凭证(credential)的请求。
对于跨源的XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest对象的 withCredentials标志为true,或在构造 Request 对象时设置。
本例中https://foo.example 的某脚本向 https://bar.other 发起一个 GET 请求并设置 Cookies。在 foo.example 中可能包含这样的 JavaScript 代码:
const invocation = new XMLHttpRequest();
const url = "https://bar.other/resources/credentialed-content/";function callOtherDomain() {if (invocation) {invocation.open("GET", url, true);invocation.withCredentials = true;invocation.onreadystatechange = handler;invocation.send();}
}
将 XMLHttpRequest 的 withCredentials 标志设置为 true才能向服务器发送 Cookies。这是一个简单 GET 请求,所以浏览器不会对其发起“预检请求”。但是如果服务器端的响应中没有Access-Control-Allow-Credentials: true,浏览器不会把响应内容返回给请求的发送者。
客户端发起的请求头:
GET /resources/credentialed-content/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: https://foo.example/examples/credential.html
Origin: https://foo.example
Cookie: pageAccess=2
请求中指定了 Cookie 是属于 https://bar.other 的内容。如果来自服务器的响应中缺失 Access-Control-Allow-Credentials: true,则服务器响应内容会被浏览器忽略,不会提供给请求的发送者。
服务器的响应:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain[text/plain payload]
CORS 预检请求不能包含凭据,但对预检请求的响应头中必须指定 Access-Control-Allow-Credentials: true 来表明可以携带凭据进行实际的请求。
在服务器响应附带身份凭证的请求时:
- 服务器不能将 Access-Control-Allow-Origin 的值设为通配符“*”,而应将其设置为特定的域,如:Access-Control-Allow-Origin: https://example.com。
- 服务器不能将 Access-Control-Allow-Headers 的值设为通配符“*”,而应将其设置为请求头名称的列表,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
- 服务器不能将 Access-Control-Allow-Methods 的值设为通配符“*”,而应将其设置为特定请求方法名称的列表,如:Access-Control-Allow-Methods: POST, GET
另外,上面的响应标头中也携带了 Set-Cookie
字段,尝试对 Cookie 进行修改(修改pageAccess的内容)。如果操作失败,将会抛出异常。同样的,如果用户设置其浏览器拒绝所有第三方 cookie,那么将不会被保存。
相关文章:

同源政策与CORS
CORS意为跨源资源共享(Cross origin resource sharing),它是一个W3C标准,由一系列HTTP Header组成,这些 HTTP Header决定了浏览器是否允许JavaScript 代码成功获得跨源请求的服务器响应。 在说CORS之前,先…...

科技资讯|三星再申请智能戒指商标,智能穿戴进入更小型化发展
三星正在积极扩展可穿戴设备生态,近日向英国知识产权局提交了名为“Samsung Curio”的新商标,其分类为“Class 9”,可能会用于未来的智能戒指。 智能戒指: 可穿戴计算机本质上的智能手环、智能项链、智能眼镜和智能戒指࿱…...

HarmonyOS开发第一步,熟知开发工具DevEco Studio
俗话说的好,工欲善其事,必先利其器,走进HarmonyOS第一步,开发工具必须先行,当然了,关于开发工具的使用,官网和其他的博客也有很多的讲解,但是并没有按照常用的功能进行概述ÿ…...
【应急响应】Linux常用基础命令
文章目录 文件和目录操作文件内容查看和编辑系统信息查询权限管理进程管理网络管理 文件和目录操作 ls:列出目录内容(例如 ls -l 显示详细信息) cd:切换工作目录 pwd:显示当前工作目录 touch:创建空文件&a…...

什么是Pytorch?
当谈及深度学习框架时,PyTorch 是当今备受欢迎的选择之一。作为一个开源的机器学习库,PyTorch 为研究人员和开发者们提供了一个强大的工具来构建、训练以及部署各种深度学习模型。你可能会问,PyTorch 是什么,它有什么特点…...
Baidu World 2023,定了!
1. 定了,Baidu World 2023 终于定了,今年的 Baidu World 将会于 2023-10-17 日在北京首钢园正式召开,主题为『生成未来 / PROMPT THE WORLD』,这也是近4年来 Baidu World 再次恢复线下举行。 有些小伙伴们如果还不知道什么是 Baid…...
ProxySQL+MGR高可用搭建
服务器点位 NODEIPmgr_node0192.165.26.200mgr_node1192.165.25.201mgr_node2192.165.26.202proxysql192.165.26.199 修改主机名 # 登录192.165.26.200 hostnamectl set-hostname mgr_node0 # 登录192.165.26.201 hostnamectl set-hostname mgr_node1 # 登录192.165.26.202 …...

【Unity小技巧】在Unity中实现类似书的功能(附git源码)
文章目录 前言本文实现的最终效果素材1. 页面素材2. 卡片内容素材地址 翻页实现1. 配置我们的canvas参数2. 添加封面和页码3. 翻页效果4. 添加按钮5. 脚本控制6. 运行效果 页面内容1. 添加卡片内容2. shader控制卡片背面3. 页面背面显示不同卡片 源码参考完结 前言 欢迎来到游…...

STM32设置为I2C从机模式(HAL库版本)
STM32设置为I2C从机模式(HAL库版本) 目录 STM32设置为I2C从机模式(HAL库版本)前言1 硬件连接2 软件编程2.1 步骤分解2.2 测试用例 3 运行测试3.1 I2C连续写入3.2 I2C连续读取3.3 I2C单次读写测试 4 总结 前言 我之前出过一篇关于…...

牛客网Verilog刷题 | 入门特别版本
文章目录 1、 VL1 输出12、VL2 wire连线3、 VL3 多wire连接4、VL4 反相器5、VL5 与门6、VL6 NOR 门7、VL7 XOR 门8、VL8 逻辑运算10、VL10 逻辑运算211、VL11 多位信号12、VL12 信号顺序调整13、VL13 位运算与逻辑运算14、VL14 对信号按位操作15、VL15 信号级联合并16、VL16 信…...

ROS通信机制之话题(Topics)的发布与订阅以及自定义消息的实现
我们知道在ROS中,由很多互不相干的节点组成了一个复杂的系统,单个的节点看起来是没起什么作用,但是节点之间进行了通信之后,相互之间能够交互信息和数据的时候,就变得很有意思了。 节点之间进行通信的一个常用方法就是…...

容灾设备系统组成,容灾备份系统组成包括哪些
随着信息技术的快速发展,企业对数据的需求越来越大,数据已经成为企业的核心财产。但是,数据安全性和完整性面临巨大挑战。在这种环境下,容灾备份系统应运而生,成为保证企业数据安全的关键因素。下面我们就详细介绍容灾…...

腾讯云服务器租用价格表_一年、1个月和1小时报价明细
腾讯云服务器租用费用表:轻量应用服务器2核2G4M带宽112元一年,540元三年、2核4G5M带宽218元一年,2核4G5M带宽756元三年、云服务器CVM S5实例2核2G配置280.8元一年、GPU服务器GN10Xp实例145元7天,腾讯云服务器网长期更新腾讯云轻量…...

【java安全】JNDI注入概述
文章目录 【java安全】JNDI注入概述什么是JNDI?JDNI的结构InitialContext - 上下文Reference - 引用 JNDI注入JNDI & RMI利用版本:JNDI注入使用Reference 【java安全】JNDI注入概述 什么是JNDI? JNDI(Java Naming and Directory Interf…...

零基础如何使用IDEA启动前后端分离中的前端项目(Vue)?
一、在IDEA中配置vue插件 点击File-->Settings-->Plugins-->搜索vue.js插件进行安装,下面的图中我已经安装好了 二、搭建node.js环境 安装node.js 可以去官网下载:安装过程就很简单,直接下一步就行 测试是否安装成功:要…...
laravel实现AMQP(rabbitmq)生产者以及消费者
基于php-amqplib/php-amqplib组件适配laravel框架的amqp封装库 支持便捷可配置的队列工作模式 官网详情 在此基础上可支持延迟消息、死信队列等机制。 环境要求: PHP版本: ^7.3|^8.0 需要开启的扩展: socket 其他: 如果需要实现延迟任务需要安装对应版本的ra…...

LeetCode——二叉树篇(九)
刷题顺序及思路来源于代码随想录,网站地址:https://programmercarl.com 目录 669. 修剪二叉搜索树 108. 将有序数组转换为二叉搜索树 538. 把二叉搜索树转换为累加树 669. 修剪二叉搜索树 给你二叉搜索树的根节点 root ,同时给定最小边界…...

uniapp scroll-view横向滚动无效,scroll-view子元素flex布局不生效
要素排查: 1.scroll-x属性需要开启,官方类型是Boolean,实际字符串也行。 2scroll-view标签需要给予一个固定宽度,可以是百分百也可以是固定宽度或者100vw。 3.子元素需要设置display: inline-block(行内块元素&#x…...

无涯教程-进程 - 简介
进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。另外,系统空间是“公共场所”,各进…...

HTML番外篇(四)-HTML5新增元素-CSS常见函数-理解浏览器前缀-BFC
一、HTML5新增元素 1.HTML5语义化元素 在HMTL5之前,我们的网站分布层级通常包括哪些部分呢? header、nav、main、footer ◼ 但是这样做有一个弊端: 我们往往过多的使用div, 通过id或class来区分元素;对于浏览器来说这些元素不…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...