【自用】前端通信(渲染页面方式SSR、url参数、window.location、跨域、透传、http请求异步、缓存cookie、web安全)
目录请求头响应头HTTP版本、状态码web安全浏览器缓存、本地存储渲染页面方式SSR、SEO搜索引擎优化浏览器http请求同步 js标签跨域、url异步web-worker创建分线程适用于计算密集型任务index.js为加载到html页面中的主线程(js文件)work.js为在index中创建的分线程异步ajax、websock协议ajax是异步的技术术语最早的api是xhrXMLHttpRequestaxios同构同样的代码在nodejs端浏览器端都可用在浏览器用xhrNode.js中使用Node的内置http模块fetch es6 api基于promise宏任务微任务事件循环event loopurl?分割url和参数 参数分隔符 #锚点id参数顺序/无效参数不影响urlObj.searchParams.set多值参数编/解码JS编码/解码decodeURIxxxbase64如jZXNzL2luZGV4I编码Buffer.from(str).toString(base64)解码Buffer.from(str, base64).toString()不要混用编码方法现代浏览器自动处理部分编码应用浏览器的自动URL解析保护特殊字符illegal base64 data at input byte404参数丢失window.location属性值打开url跨域LocalStorage、SessionStorage、Cookie不通过服务器CORS 禁用chrome.exe --disable-web-security --user-data-dirC:\chrome_temp跨窗口通信 APIWindow.postMessage目标窗口监听originwindow.addEventListener(message, receiveMessage, false);receiveMessage(event){event.dataevent.orginevent.source.postMessage}jsonpscriptjs、cssimg静态资源仅get方式现在浏览器兼容性高了逐步淘汰了代理服务器服务器间不用同源Nginx反向代理类似cors字段设置proxy_passproxy应用vue.config.js应用本地页面跨域请求cors浏览器IE10以下不支持服务端Access-Control-Allow-Originpreflight预检查跨域复杂请求前OPTIONS 请求简单请求GET/HEAD/POSTContent-Typetext/plain,multipart/form-data,application/x-www-form-urlencoded报错localhost/127.0.0.1与本机IP应用当服务端没有设置跨域时可以将json伪装成text/plainwebsocketHTML5新特性TCP实时通信兼容性不好只适用于主流浏览器和IE10应用webpack热更新透传【开发业务】url请求头响应头HTTP版本、状态码web安全浏览器缓存、本地存储渲染页面方式SSR、SEO搜索引擎优化浏览器http请求服务 / Server 一直运行的程序等待被访问同步 js标签跨域、urlimg srclink href异步web-worker创建分线程适用于计算密集型任务当在 HTML 页面中执行脚本时页面的状态是不可响应的直到脚本已完成。web worker 是运行在后台的 JavaScript独立于其他脚本不会影响页面的性能。Web Worker 线程无法直接访问 DOM文档对象模型元素、操作页面内容也不能访问浏览器的Window和 Document 对象。这是为了防止 Web Worker影响主页面的交互index.js为加载到html页面中的主线程(js文件)work.js为在index中创建的分线程index.js 创建分线程 var w new webwork(work.js)//创建 Web Worker 对象 向子线程发送数据 w.postmesage(数据) work.js onmessage function(ev) { console.log(ev);//接受主线程发送过来的ev.data数据 this.postMessage(数据)//通过 postmesage(数据) 向主线程发送数据 } 通过w.onmessagefunction(ev){ev.data} ev.data 接受 a 的值. w.terminate();//终止 Web Worker异步ajax、websock协议ajax是异步的技术术语最早的api是xhrXMLHttpRequestaxios同构同样的代码在nodejs端浏览器端都可用在浏览器用xhrNode.js中使用Node的内置http模块// 在浏览器中根据其Content-Type头信息自动转换数据 axios.get(/api/data).then(response { // response.data 将是一个JavaScript对象 }); // 在Node.js中手动设置响应数据类型 axios.get(/api/data, { responseType: json }).then(response { // response.data 将是一个JavaScript对象 });axios 新版本也支持了fetch第三方库都是基于原生API的所以axios也还是基于xhr的fetch es6 api基于promise【JavaScript】爆肝 2 万字一次性搞懂 Ajax、Fetch 和 Axios 的区别~宏任务微任务事件循环event loopurl?分割url和参数 参数分隔符 #锚点idhttp://example.com/page?param1value1param2value2#section1分隔实际的URL和参数URL中指定的参数间的分隔符左边为参数名、右边参数值#锚点Anchor用于标识文档中的特定位置或元素,仅在客户端前端使用并且由浏览器处理,不发送到服务器后端指示浏览器滚动到具有idsection1的元素处。参数顺序/无效参数不影响urlObj.searchParams.setURL 构造函数在解析无效 URL 时会抛出异常duotry { let urlObj new URL(url); urlObj.searchParams.set(meishi_appid, id); // 更新或创建参数 global.viewList[id].view.webContents.loadURL(urlObj.href); } catch (error) { global.viewList[id].view.webContents.loadURL(url); } 等同于encodeURIComponent ? const title encodeURIComponent(res.fileName); let newUrl ${url}?title${title};多值参数写法示例逗号分隔单参数userNamesxxx,yyy重复同名参数userNamesxxxuserNamesyyy其他分隔符具体看接口文档ids1|2|3或ids1;2;3JSON 字符串post请求更常见userNames[xxx,yyy]编/解码URL 规范RFC 3986规定URL 只能包含以下字符保留字符A-Z、a-z、0-9、-、_、.、~特殊字符!、*、、(、)、;、:、、、、、$、,、/、?、#、[、]中文等非 ASCII 字符必须经过编码Percent-Encoding才能使用%xx形式JS编码/解码decodeURIxxx参数值用encodeURIComponent/URL编码/百分号编码编码更彻底。如https%3A%2F%2F https://完整 URL用encodeURI保留/、?等结构字符。base64如jZXNzL2luZGV4I编码Buffer.from(str).toString(base64)解码Buffer.from(str, base64).toString()如aHR0cHM6...bpm...不要混用编码方法错误的解码如用decodeURI解码encodeURIComponent的结果会导致乱码。现代浏览器自动处理部分编码在浏览器地址栏输入中文时Chrome/Firefox 会自动编码但代码中仍需手动处理。如英文逗号可以不编码。userNamesxxx,yyy浏览器地址栏直接输入、回车通常就能用不用手动写成%2C。但以下情况建议编码或必须用编码字符/内容原因?#有特殊含义会破坏 URL 结构空格可能被转成或%20中文、特殊符号必须编码用户名里本身带逗号会和分隔符冲突需确认接口规则应用浏览器的自动URL解析// 浏览器看到这个URL会自动解析token参数可能会重新编码 const normalUrl res.fileUrl ?token res.token; // 成功方案整个URL被编码浏览器不会解析内部结构把这个当作一个普通的编码字符串处理 const encodedUrl encodeURIComponent(res.fileUrl ?token res.token);保护特殊字符illegal base64 data at input byte// 内层编码保护token中的特殊字符 const safeToken encodeURIComponent(res.token); // 外层编码保护整个URL结构 const fullEncodedUrl encodeURIComponent(res.fileUrl ?token safeToken); // 普通URL - 浏览器会解析参数 window.open(https://example.com/file.xlsx?tokenabcdef123); // 浏览器可能把 解析为空格把 当作参数分隔符404参数丢失SSO 跳转或后端重定向时hash 后的参数丢失业务页面拿不到实际参数直接 404。首次打开时未登录会走 SSO 跳转链路hash 丢失概率大。登录后页面可能直接渲染不再走 SSO 跳转所以正常。URL 超长被截断或参数顺序导致 hash 被提前截断。window.location属性值window的全局对象表示当前页面http://www.example.com/path/index.htmlwindow.location.href获取/设置 urlwindow.location.orgin协议、主机名和端口号部分//https://www.example.com:8080/page.html // :// : //https%3A%2F%2Fwww.example.com%3A8080。 encodeURIComponent(window.location.origin) //encodeURIComponent用于将字符串中的特殊字符(空格、、、 、?)转换为编码形式确保URL中不包含任何无效字符 //查询参数时 或者 动态参数时 需要encodeURIComponent const url https://example.com/api?param encodeURIComponent(queryParam); window.location.href https://www.example.com/path/to/resource.html/domain${location.host}req${encodeURIComponent(location.pathname)}protocolhttps${location.hash}window.location.protocol: 协议httpwindow.location.host主机端口host:8080/IP地址127.123.32.1唯一/域名www.example.com助记window.location.hostname主机hostwindow.location.port端口8080window.location.pathname: 资源路径path/index.html资源index.htmlwindow.location.hash:window.location.search: 搜索var searchParams new URLSearchParams(window.location.search); console.log(searchParams.get(name)); // 输出 John打开url同源页面自动携带所有相关Cookie到新页面都能复用登录态。同基域页面SSO系统携带父域Cookie也能复用登录态。非同源打开方式技术原理是否继承登录态location.href 同源导航✅window.open(url)同源弹出窗口✅ahref target_blank同源新标签页✅地址栏直接输入全新的浏览器实例❌复制链接粘贴全新的浏览会话❌跨域跨域是浏览器内核特有的安全保护机制命令行数据传输工具 curl不受 CORS限制。同源策略URL 的协议、域名和端口号完全一致查询参数不影响同源共享Cookie、LocalStorageLocalStorage、SessionStorage、Cookie特性LocalStorageSessionStorageCookie数据范围同源域名同源 路径 标签页【同路径】可通过 Domain【域名】/Path【路径】 灵活配置持久性持久需手动或代码清除标签页关闭即清除可设置过期时间 (Expires/Max-Age)跟随请求否否是自动在 HTTP 头中发送可访问性仅客户端 JavaScript仅客户端 JavaScript客户端和服务器均可访问同域通常指主域名相同 不关心协议和端口不是标准术语不通过服务器CORS 禁用chrome.exe --disable-web-security --user-data-dirC:\chrome_temp跨窗口通信 APIWindow.postMessage目标窗口监听originwindow.addEventListener(message, receiveMessage, false);receiveMessage(event){event.dataevent.orginevent.source.postMessage}http://localhost:3000!DOCTYPE html html head script window.addEventListener(DOMContentLoaded, (event) { // 获取目标窗口的引用 const targetWindow document.getElementById(targetFrame).contentWindow; // 给按钮添加点击事件处理函数 document.getElementById(sendButton).addEventListener(click, () { // 向目标窗口发送消息 targetWindow.postMessage(你好来自页面1, http://localhost:4000); }); }); // 接收消息的处理函数 function receiveMessage(event) { // 确保消息来自预期的源 if (event.origin ! http://localhost:4000) { return; } console.log(接收到的消息, event.data); } // 监听消息事件 window.addEventListener(message, receiveMessage, false); /script /head body button idsendButton发送消息/button iframe idtargetFrame srchttp://localhost:4000/iframe /body /htmlhttp://localhost:4000!DOCTYPE html html head script // 监听消息事件 window.addEventListener(message, receiveMessage, false); // 接收消息的处理函数 function receiveMessage(event) { // 确保消息来自预期的源 if (event.origin ! http://localhost:3000) { return; } console.log(接收到的消息, event.data); // 向页面1发送回应消息 event.source.postMessage(你好来自页面2, event.origin); } /script /head body h1页面2/h1 /body /htmljsonpscriptjs、cssimg静态资源仅get方式现在浏览器兼容性高了逐步淘汰了JSONPJSON WithPadding是利用script srcXXX跨域因为是动态创建script标签所以它只支持get请求不支持post请求代理服务器服务器间不用同源正向代理主要是用来解决访问限制问题;反向代理则是提供负载均衡、安全防护等作用。Nginx反向代理类似cors字段设置proxy_pass前端应用运行在http://localhost:3000跨域访问后端 API 它运行在http://localhost:8000server { #HTTP 协议来监听请求应该使用 listen 80; listen 80; server_name localhost; location /api { proxy_pass http://localhost:8000; # 允许来自前端应用的跨域请求 add_header Access-Control-Allow-Origin http://localhost:3000; add_header Access-Control-Allow-Methods GET, POST, OPTIONS; add_header Access-Control-Allow-Headers DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range; # 支持 OPTIONS 请求用于预检请求 if ($request_method OPTIONS) { add_header Access-Control-Allow-Origin http://localhost:3000; add_header Access-Control-Allow-Methods GET, POST, OPTIONS; add_header Access-Control-Allow-Headers DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range; # 响应预检请求直接返回 204 No Content add_header Content-Length 0; add_header Content-Type text/plain charsetUTF-8; return 204; } } }proxy应用vue.config.jsconst configStatic { proxy: { /test: { target: http://www.xx.cn,// 设置目标服务器的地址 changeOrigin: true, pathRewrite: { ^/test: / } } } module.exports configStatic应用本地页面跨域请求浏览器有同源策略 / 跨域CORS页面在http://10.253.70.42:3000接口在https://xiaomi.77.com域名、端口、协议不同 → 浏览器可能禁止页面里的 JS 直接fetch外站接口常见解决办法本机起一个代理proxy浏览器 → 同源http://本机:3000/proxy?url真实接口本机服务 → 服务端去请求 →https://xiaomi.77.com...本机服务 → 把结果还给浏览器浏览器只和「本机 3000」说话跨域由服务器完成服务器不受浏览器 CORS 限制。//index.html const targetUrl https://xiaomi.77.com/updateGrayUser?${params}; const url /proxy?url${encodeURIComponent(targetUrl)}; fetch(url)托管页面让index.html能通过http://打开不是file://代理转发页面不能直接跨域访问外站时由本机代为请求// server.js 靠本机的 Node进程服务端去请求外站绕过浏览器的跨域限制 // require 加载 Node.js 自带的能力不用额外安装。 const http require(http); const https require(https); const fs require(fs); const path require(path); const { URL } require(url); const PORT 3000; function proxy(targetUrl, res) { //由 Node 去请求外站 https.get(targetUrl, (proxyRes) { let data ; proxyRes.on(data, (chunk) { data chunk; }); proxyRes.on(end, () { res.writeHead(proxyRes.statusCode || 200, { Content-Type: proxyRes.headers[content-type] || application/json; charsetutf-8, Access-Control-Allow-Origin: *, }); res.end(data); }); }).on(error, (err) { res.writeHead(500, { Content-Type: application/json }); res.end(JSON.stringify({ error: err.message })); }); } http.createServer((req, res) { const url new URL(req.url, http://${req.headers.host}); if (url.pathname /proxy) { const targetUrl url.searchParams.get(url); if (!targetUrl) { res.writeHead(400, { Content-Type: application/json }); return res.end(JSON.stringify({ error: missing url parameter })); } return proxy(targetUrl, res); } const file path.join(__dirname, url.pathname / ? index.html : url.pathname.slice(1)); if (!file.startsWith(__dirname)) { res.writeHead(403).end(Forbidden); return; } fs.readFile(file, (err, data) { if (err) { res.writeHead(404).end(Not Found); return; } const type path.extname(file) .html ? text/html; charsetutf-8 : text/plain; res.writeHead(200, { Content-Type: type }).end(data); }); }).listen(PORT, 0.0.0.0, () { console.log(http://localhost:${PORT}); });cors浏览器IE10以下不支持服务端Access-Control-Allow-Originpreflight预检查跨域复杂请求前OPTIONS 请求预检请求是指浏览器在发送跨域请求之前会先发送一个OPTIONS 请求用于询问服务器是否接受实际的请求。预检请求主要用于对复杂请求例如包含自定义请求头或使用非简单标头字段的请求进行安全验证。简单请求GET/HEAD/POSTContent-Typetext/plain,multipart/form-data,application/x-www-form-urlencoded条件2除了被允许的自定义请求头例如X-PINGOTHER请求头仅包含一些简单标头字段如Accept、Accept-Language、Content-Language、Content-TypeRange。其中Content-Type 的值仅限于下列三者之一text/plain无格式正文(可以有效避免XSS漏洞)multipart/form-data键值对型数据application/x-www-form-urlencodedURLencoded默认报错localhost/127.0.0.1与本机IP将 localhost 改为本机 IP 后出现 CORS 跨域无法登录原因如下1. **同源策略限制**浏览器的同源策略要求协议、域名、端口完全一致。localhost 和 127.0.0.1 或本机 IP如 192.168.x.x在浏览器看来是不同的源。2. **Cookie 不共享**如果你的登录态如 session、token、cookie是在 localhost 下设置的切换到本机 IP 时cookie 不会自动带上导致认证失效接口返回未登录。3. **CORS 配置问题**后端如果只允许 localhost 跨域换成 IP 后请求来源变了CORS 校验失败浏览器拦截请求。应用当服务端没有设置跨域时可以将json伪装成text/plain原理包装成简单请求以躲过预检查。但是有些客户端或者代理还是会预检查安全浏览器对于 JSON 响应的默认处理机制是自动将其解析为 JavaScript 对象。当浏览器接收到响应时如果响应的 Content-Type 是application/json或没有指定 Content-Type浏览器会自动将响应体解析为JSONJavaScript 对象如果是恶意代码可能会被执行导致安全漏洞或攻击。通过将 JSON 响应伪装成 text/plain 类型可以避免浏览器将响应自动解析为 JavaScript 对象的默认行为。这样一来JavaScript 代码需要手动对响应进行解析确保数据的安全性和正确性。这种伪装可以提供额外的安全层防止对于响应的自动解析可能带来的安全风险。//请求头 GET /example HTTP/1.1 Host: example.com Accept: text/plain //响应头 HTTP/1.1 200 OK Content-Type: text/plain //响应体 {foo: bar}跨源资源共享CORS - HTTP | MDNCross-Origin Resource Sharing (CORS) - HTTP | MDNwebsocketHTML5新特性TCP实时通信兼容性不好只适用于主流浏览器和IE10应用webpack热更新HTML5 的一个持久化的协议它实现了浏览器与服务器的全双工通信WebSocket和HTTP都是应用层协议都基于TCP 协议。WebSocket 在建立连接时需要借助 HTTP 协议连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了原生WebSocket API使用起来不太方便Socket.io它很好地封装了webSocket接口提供了更简单、灵活的接口也对不支持webSocket的浏览器提供了向下兼容。本地文件socket.html向localhost:3000发生数据和接受数据// socket.html script let socket new WebSocket(ws://localhost:3000); socket.onopen function () { socket.send(我爱你);//向服务器发送数据 } socket.onmessage function (e) { console.log(e.data);//接收服务器返回的数据 } /script// server.js let express require(express); let app express(); let WebSocket require(ws);//记得安装ws let wss new WebSocket.Server({port:3000}); wss.on(connection,function(ws) { ws.on(message, function (data) { console.log(data); ws.send(我不爱你) }); })透传【开发业务】透传 Transparent Transmission数据从一个系统/模块传递到另一个系统/模块中间不做任何处理或修改参数是否敏感↓是 → 使用POST请求体/加密传输否 → 参数是否简单且量小↓是 → 使用URL参数跨域跨端否 → 数据是否复杂或量大↓是 → 使用状态管理/存储Vuex、LocalStorage否 → 是否需要持久化↓是 → LocalStorage/SessionStorage否 → 是否临时性通信↓是 → 事件总线/全局状态url// 假设当前URLhttps://example.com/path?sourceehrextraxxx const urlParams new URLSearchParams(window.location.search); // window.location.search ?sourceehrextraxxx // urlParams.get(source) → ehr ✅ //URLSearchParams 构造函数不会解析完整 URL但是如果字符串起始位置有 ? 的话会被去除。 const paramsString1 http://example.com/search?query%40; const searchParams1 new URLSearchParams(paramsString1); console.log(searchParams1.has(query)); // false console.log(searchParams1.has(http://example.com/search?query)); // true