Hello,
I am trying to apply ProxyConfiguration on the WebKit webview. I referred to the following sources:
- https://forums.developer.apple.com/forums/thread/110312 and
- https://developer.apple.com/videos/play/wwdc2023/10002/
import WebKit
class WebKitViewModel: ObservableObject {
let webView: WKWebView
@Published var urlString: String = "https://example.com"
init() {
webView = WKWebView(frame: .zero)
}
func loadUrl() {
guard let url = URL(string: urlString) else {
return
}
var request = URLRequest(url: url)
let endpoint = NWEndpoint.hostPort(host: "127.0.0.1", port: 9077)
let proxyConfig = ProxyConfiguration.init(httpCONNECTProxy: endpoint)
let websiteDataStore = WKWebsiteDataStore.default()
websiteDataStore.proxyConfigurations = [proxyConfig]
webView.configuration.websiteDataStore = websiteDataStore
webView.load(request)
}
}
However, this configuration only works for HTTP proxies. When I try to use an HTTPS proxy, it does not work.
When I use NWConnection to connect to the proxy, it works successfully:
import Foundation
import Network
public class HTTPProxy {
private let proxyHost: NWEndpoint.Host
private let proxyPort: NWEndpoint.Port
private var connection: NWConnection?
public init(proxyHost: String, proxyPort: UInt16) {
self.proxyHost = NWEndpoint.Host(proxyHost)
self.proxyPort = NWEndpoint.Port(rawValue: proxyPort)!
}
public func sendHTTPRequest(completion: @escaping (Result<String, Error>) -> Void) {
let tlsOptions = NWProtocolTLS.Options()
let parameters = NWParameters(tls: tlsOptions)
connection = NWConnection(host: proxyHost, port: proxyPort, using: parameters)
connection?.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
self?.sendConnectRequest(completion: completion)
case .failed(let error):
completion(.failure(error))
default:
break
}
}
connection?.start(queue: .global())
}
private func sendConnectRequest(completion: @escaping (Result<String, Error>) -> Void) {
guard let connection = connection else {
completion(.failure(NSError(domain: "Connection not available", code: -1, userInfo: nil)))
return
}
let username = "xxxx"
let password = "xxxx"
let credentials = "\(username):\(password)"
guard let credentialsData = credentials.data(using: .utf8) else {
print("Error encoding credentials")
fatalError()
}
let base64Credentials = credentialsData.base64EncodedString()
let proxyAuthorization = "Basic \(base64Credentials)"
let connectString = "CONNECT api.ipify.org:80 HTTP/1.1\r\n" +
"Host: api.ipify.org:80\r\n" +
"Proxy-Authorization: \(proxyAuthorization)\r\n" +
"Connection: keep-alive\r\n" +
"\r\n"
if let connectData = connectString.data(using: .utf8) {
connection.send(content: connectData, completion: .contentProcessed { error in
if let error = error {
completion(.failure(error))
} else {
self.receiveConnectResponse(completion: completion)
}
})
}
}
private func receiveConnectResponse(completion: @escaping (Result<String, Error>) -> Void) {
connection?.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, context, isComplete, error in
if let data = data, let responseString = String(data: data, encoding: .utf8) {
if responseString.contains("200 OK") {
self.sendRequest(completion: completion)
} else {
completion(.failure(NSError(domain: "Failed to establish connection", code: -1, userInfo: nil)))
}
} else if let error = error {
completion(.failure(error))
}
}
}
private func sendRequest(completion: @escaping (Result<String, Error>) -> Void) {
guard let connection = connection else {
completion(.failure(NSError(domain: "Connection not available", code: -1, userInfo: nil)))
return
}
let requestString = "GET /?format=json HTTP/1.1\r\n" +
"Host: api.ipify.org\r\n" +
// "Proxy-Authorization: Basic xxxxxxxx\r\n" +
"Connection: keep-alive\r\n" +
"\r\n"
print("Sending HTTP request:\n\(requestString)")
if let requestData = requestString.data(using: .utf8) {
connection.send(content: requestData, completion: .contentProcessed { error in
if let error = error {
completion(.failure(error))
} else {
self.receiveResponse(completion: completion)
}
})
}
}
private func receiveResponse(completion: @escaping (Result<String, Error>) -> Void) {
connection?.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, context, isComplete, error in
if let data = data, !data.isEmpty {
print ("Data: \(data)")
if let responseString = String(data: data, encoding: .utf8) {
print("Received response:\n\(responseString)")
completion(.success(responseString))
} else {
completion(.failure(NSError(domain: "Invalid response data", code: -1, userInfo: nil)))
}
} else if let error = error {
completion(.failure(error))
}
if isComplete {
self.connection?.cancel()
self.connection = nil
} else {
self.receiveResponse(completion: completion)
}
}
}
}
This approach works for connecting to the proxy, but it does not help with configuring the proxy for WebKit.
Could someone please assist me in configuring a proxy for WebKit WebView to work with HTTPS proxies?
Thank you!