如何配置代理解决GCDWebServer+WKWebView的跨域请求问题?
刚好我之前也踩过GCDWebServer代理+WKWebView跨域+Cookie的坑,给你一套亲测有效的解决方案——核心思路是用GCDWebServer创建反向代理端点,让本地网页的请求先发给代理,由代理转发到后端服务器,同时处理跨域头和Cookie同步,完美绕开浏览器的跨域限制。
实现步骤
1. 初始化GCDWebServer并添加代理路由
首先创建一个代理服务器类,给GCDWebServer添加对应HTTP方法的路由(GET/POST/PUT/DELETE/OPTIONS都要覆盖,尤其是OPTIONS的预检请求,浏览器会自动发起)。
2. 编写请求转发逻辑
代理的核心是把本地请求转发到后端,同时处理Cookie和跨域头:
- 提取目标后端URL:把请求路径中
/proxy/后面的部分拼接成完整的后端接口地址 - 复制原请求的headers、body到新的请求中,特别要带上后端的Cookie
- 转发请求后,给本地网页返回响应,并添加CORS允许头
- 同步后端返回的
Set-Cookie到WKWebView的Cookie存储中
3. 修改本地网页的请求地址
把网页里原本直接请求后端的URL,替换成GCDWebServer的代理地址,比如原请求https://your-backend.com/api/user改成http://localhost:8080/proxy/api/user(假设GCDWebServer跑在8080端口)。
完整代码示例(Swift)
import GCDWebServer import WebKit class LocalProxyServer { let webServer = GCDWebServer() // 替换成你的后端基础地址 let backendBaseURL = "https://your-backend-domain.com" // 本地服务器地址,要和CORS头里的源一致 let localServerURL = "http://localhost:8080" init() { setupProxyHandlers() startServer() } private func setupProxyHandlers() { // 处理GET请求 webServer.addHandlerForMethod("GET", path: "/proxy/*", requestClass: GCDWebServerRequest.self) { [weak self] request in return self?.handleProxyRequest(request) } // 处理POST请求 webServer.addHandlerForMethod("POST", path: "/proxy/*", requestClass: GCDWebServerRequest.self) { [weak self] request in return self?.handleProxyRequest(request) } // 处理PUT/DELETE等其他方法,按需添加 webServer.addHandlerForMethod("PUT", path: "/proxy/*", requestClass: GCDWebServerRequest.self) { [weak self] request in return self?.handleProxyRequest(request) } // 处理OPTIONS预检请求(必须加,否则浏览器会拦截跨域请求) webServer.addHandlerForMethod("OPTIONS", path: "/proxy/*", requestClass: GCDWebServerRequest.self) { [weak self] _ in guard let self = self else { return GCDWebServerResponse(statusCode: 500) } let response = GCDWebServerResponse(statusCode: 200) // 允许本地服务器跨域请求 response?.setValue(self.localServerURL, forAdditionalHeader: "Access-Control-Allow-Origin") // 允许携带Cookie response?.setValue("true", forAdditionalHeader: "Access-Control-Allow-Credentials") // 允许的HTTP方法 response?.setValue("GET, POST, PUT, DELETE, OPTIONS", forAdditionalHeader: "Access-Control-Allow-Methods") // 允许的请求头 response?.setValue("Content-Type, Authorization, Cookie", forAdditionalHeader: "Access-Control-Allow-Headers") // 预检缓存时长,减少OPTIONS请求次数 response?.setValue("86400", forAdditionalHeader: "Access-Control-Max-Age") return response } } private func handleProxyRequest(_ request: GCDWebServerRequest) -> GCDWebServerResponse? { guard let self = self else { return GCDWebServerResponse(statusCode: 500) } // 提取代理路径,去掉开头的"/proxy/" let proxyPath = request.path.replacingOccurrences(of: "/proxy/", with: "") guard let targetURL = URL(string: "\(self.backendBaseURL)/\(proxyPath)") else { print("Invalid target URL") return GCDWebServerResponse(statusCode: 400) } // 构建转发请求 var urlRequest = URLRequest(url: targetURL) urlRequest.httpMethod = request.method // 复制原请求的headers(跳过Host,由目标URL自动处理) for (key, value) in request.headers { if key.lowercased() != "host" { urlRequest.setValue(value, forHTTPHeaderField: key) } } // 添加后端Cookie:从系统Cookie存储和WKWebView的Cookie存储中同步 var cookies = [HTTPCookie]() if let systemCookies = HTTPCookieStorage.shared.cookies(for: targetURL) { cookies.append(contentsOf: systemCookies) } // 同步WKWebView的Cookie(如果有单独存储的话) WKHTTPCookieStore.default.getAllCookies { wkCookies in cookies.append(contentsOf: wkCookies.filter { $0.domain == targetURL.host }) } let cookieHeader = HTTPCookie.requestHeaderFields(with: cookies) urlRequest.allHTTPHeaderFields?.merge(cookieHeader) { (_, new) in new } // 处理请求体(比如POST的JSON或表单数据) if let bodyData = request.body { urlRequest.httpBody = bodyData } // 发送转发请求(用信号量实现同步返回,适配GCDWebServer的同步回调) let semaphore = DispatchSemaphore(value: 0) var responseData: Data? var httpResponse: HTTPURLResponse? var requestError: Error? URLSession.shared.dataTask(with: urlRequest) { data, resp, err in responseData = data httpResponse = resp as? HTTPURLResponse requestError = err semaphore.signal() }.resume() semaphore.wait() if let error = requestError { print("Proxy request failed: \(error.localizedDescription)") return GCDWebServerResponse(statusCode: 500) } guard let resp = httpResponse, let data = responseData else { print("Empty response from backend") return GCDWebServerResponse(statusCode: 500) } // 构建返回给本地网页的响应 let serverResponse = GCDWebServerDataResponse(data: data, contentType: resp.mimeType) serverResponse?.statusCode = resp.statusCode // 添加CORS头,允许本地网页跨域获取响应 serverResponse?.setValue(self.localServerURL, forAdditionalHeader: "Access-Control-Allow-Origin") serverResponse?.setValue("true", forAdditionalHeader: "Access-Control-Allow-Credentials") // 同步后端返回的Set-Cookie到系统和WKWebView的Cookie存储 if let setCookieHeaders = resp.allHeaderFields["Set-Cookie"] as? [String] { for cookieHeader in setCookieHeaders { guard let cookie = HTTPCookie.cookies(withResponseHeaderFields: ["Set-Cookie": cookieHeader], for: targetURL).first else { continue } // 同步到系统Cookie存储 HTTPCookieStorage.shared.setCookie(cookie) // 同步到WKWebView的Cookie存储 WKHTTPCookieStore.default.setCookie(cookie) { // 可选:处理Cookie同步完成的回调 } } } return serverResponse } private func startServer() { do { try webServer.start(withPort: 8080, bonjourName: "LocalProxyServer") print("Proxy server running at \(webServer.serverURL?.absoluteString ?? "")") } catch { print("Failed to start proxy server: \(error.localizedDescription)") } } }
使用说明
- 初始化代理服务器:在App启动时创建
LocalProxyServer实例,比如在AppDelegate的application(_:didFinishLaunchingWithOptions:)方法中:let proxyServer = LocalProxyServer() - 加载本地网页:用GCDWebServer的URL加载你的本地网页,比如:
let webView = WKWebView() if let localPageURL = URL(string: "\(proxyServer.localServerURL)/index.html") { webView.load(URLRequest(url: localPageURL)) } - 修改网页请求地址:把网页中所有请求后端的URL替换成代理地址,比如原请求
https://your-backend.com/api/login改成http://localhost:8080/proxy/api/login。
注意事项
- HTTPS后端处理:如果后端是自签名HTTPS证书,需要给URLSession配置信任(仅测试环境使用,生产环境请使用合法证书):
然后用这个自定义session发送请求,替换代码中的let config = URLSessionConfiguration.default config.allowsInvalidCertificates = true let session = URLSession(configuration: config)URLSession.shared。 - CORS头配置:不要把
Access-Control-Allow-Origin设为*,因为*和Access-Control-Allow-Credentials: true不能同时生效,必须指定具体的本地服务器地址(比如http://localhost:8080)。 - Cookie同步:确保WKWebView的Cookie和系统Cookie存储同步,这样代理转发请求时才能正确带上后端需要的Cookie。
内容的提问来源于stack exchange,提问作者Hudi Ilfeld




