跨域问题与DNS解析踩坑记
问题
业务过程中, 有个服务使用无头浏览器专门用来为网页进行截图.
但是当时出现了一个问题在于本地开发环境进行截图是没有任何问题的, 但是在服务器部署后同一个网页部分的截图不一致问题.
主要体现在有个iframe标签嵌入的内容无法被正常截图.
截图流程
- 无头浏览器访问某个前端页面
- console控制台执行某个渲染方法
- 服务监听特定元素是否加载完毕, 包括img标签, iframe标签
- 加载完毕后进行截图
问题分析过程
1. 本地调试打开带页面的浏览器分析
经过对网络请求的分析发现, 页面会有一个发送给测试环境官网的请求来获取iframe标签.
形式如: https://xxx.test.com/api/iframe
这时出现了第一个问题, 因为测试环境是连接公司VPN来访问, 那么也就意味着dns解析是通过公司本地dns服务器来解析处具体IP来访问
那么部署到公有云的k8s的pod中, 使用的公有云的dns, 那么肯定无法正确发出这个请求.
所以修改为k8s中pod可以解析的dns, 请求最后发出的是 http://server/api/iframe
知识点: dns如何解析的

image.png
解析过程如下:
用户发起DNS查询
当用户在浏览器中输入域名时,浏览器会首先检查本地缓存中是否有该域名对应的IP地址。如果有,则直接使用该IP地址;如果没有,则发起DNS查询。操作系统缓存
操作系统会检查自身的DNS缓存中是否有该域名对应的IP地址。如果有,则返回IP地址;如果没有,则查询配置的DNS服务器。查询本地域名服务器(ISP的DNS服务器)
本地域名服务器是由用户的互联网服务提供商(ISP)提供的DNS服务器。当操作系统没有找到缓存记录时,会向本地域名服务器发起查询请求。递归查询
如果本地域名服务器没有该域名的缓存记录,它会代替用户进行递归查询,逐级查找IP地址。这包括以下几个步骤:
a. 根域名服务器
本地域名服务器首先会查询根域名服务器(Root Name Server),根服务器掌握所有顶级域(如.com、.org)的信息。
b. 顶级域名服务器(TLD Name Server)
根服务器会返回相应顶级域名服务器的地址(例如,.com域名的服务器)。然后本地域名服务器会向该顶级域名服务器发起查询请求。
c. 权威域名服务器(Authoritative Name Server)
顶级域名服务器会返回管理该具体域名(例如,example.com)的权威域名服务器地址。最终,本地域名服务器会向权威域名服务器发起查询请求。
返回结果
权威域名服务器返回该域名对应的IP地址。本地域名服务器将该IP地址缓存一段时间,以便下次查询更快,然后将IP地址返回给操作系统。
2. 请求跟踪问题
在解决上述问题后, 发现服务器部署后截图功能仍然存在问题, 仍然无法解决iframe标签未正确加载的问题.
因为服务器上的网络请求无法查看, 也没有具体日志查看, 通过很多手段最终可以确认基本还是那个请求没有发送的问题.
问题在于: 因为存在跨域的问题, 请求会先发送预检请求(OPTIONS 方法), 查看是否允许后续请求发送, 再发送我们的Post请求 http://server/api/iframe
发送预检请求是直接发送到了我们服务上, 而处理预检请求这些的都是gateway的事情, 所以这个请求仍然是没有正确得发出来.
所以我们在公有云的k8s的dns上配置好测试环境的dns解析, 然后让其请求发送到gateway, 然后正常处理就可以了.
其实这个问题在现网环境不会发生, 因为现网不存在dns无法解析这样的情况.
知识点: 跨域问题
跨域是什么?
跨域问题是指浏览器出于安全考虑,限制从一个源(域名、协议或端口)加载的文档或脚本与来自另一个源的资源进行交互。这是浏览器的同源策略(Same-Origin Policy)导致的。
让我简要解释跨域问题的几个要点:
- 同源定义:协议、域名和端口都相同。
- 限制范围:主要包括 XMLHttpRequest 或 Fetch API 发起的 HTTP 请求,以及 DOM 操作等。
- 常见场景:前端 JavaScript 代码试图请求不同源的后端 API。
- 安全考虑:防止恶意网站通过脚本访问其他网站的敏感数据。
- 解决方法:
- CORS(跨源资源共享):服务器设置特定的 HTTP 头
- JSONP:利用<script>标签不受同源策略限制的特点
- 代理服务器:在同源的服务器上创建一个代理
- WebSocket:天然支持跨域通信
- 影响:跨域限制可能会阻碍某些 Web 应用的功能实现,特别是在前后端分离的架构中。
当发现需要发送跨域Http请求后, 浏览器会做什么动作?
当浏览器发现需要发送跨域 HTTP 请求时,它会采取一系列特定的动作。这个过程通常涉及到预检请求(Preflight request)和实际请求。详细解释一下这个过程:
简单请求:
对于简单请求(如 GET、HEAD、POST 请求,且没有自定义头部,Content-Type 限于 application/x-www-form-urlencoded、multipart/form-data 或 text/plain),浏览器会:- 直接发送请求到服务器
- 在请求中添加 Origin 头,表明请求的源
- 等待服务器响应,检查 Access-Control-Allow-Origin 等头部
非简单请求(需要预检):
对于非简单请求(如使用 PUT、DELETE 方法,或带有自定义头部),浏览器会:
a. 发送预检请求(OPTIONS 方法):
- 包含 Origin 头
- 包含 Access-Control-Request-Method 头,说明实际请求将使用的 HTTP 方法
如果有自定义头部,还会包含 Access-Control-Request-Headers 头
b. 等待服务器对预检请求的响应:
- 检查 Access-Control-Allow-Origin
- 检查 Access-Control-Allow-Methods
- 检查 Access-Control-Allow-Headers(如果请求中有自定义头部)
检查 Access-Control-Max-Age(可选,指定预检请求的缓存时间)
c. 如果预检请求通过:
- 发送实际的请求
在实际请求中包含 Origin 头
d. 等待服务器对实际请求的响应:
再次检查 Access-Control-Allow-Origin
处理响应:
- 如果服务器的响应没有包含正确的 CORS 头(主要是 Access-Control-Allow-Origin),浏览器会阻止 JavaScript 代码访问响应内容
- 如果 CORS 头正确,JavaScript 可以正常处理响应
错误处理:
- 如果在任何阶段 CORS 检查失败,浏览器会在控制台输出错误信息
- JavaScript 的 Promise 或 XMLHttpRequest 的 onerror 回调会被触发
缓存预检请求:
- 如果服务器在预检响应中包含 Access-Control-Max-Age 头,浏览器会缓存这个结果,在指定时间内不再发送相同的预检请求