做这个需求的原因比较蛋疼,因为新闻http协议经常被第三方注入广告,导致新闻中经常出现抽奖广告什么的,产品将新闻替换为https协议,但是有的图片还是http请求,无法通过证书校验(是说无法校验,我还需要验证下)
一、使用js注入进行图片url替换
基本想法就是页面加载完成后注入一段js,然后找到所有<img>标签,替换src地址。
这种方法有个问题,需要等网页加载完成之后才能去替换,这样有可能之前的http图片已经加载了(但是没有通过校验,加载不了),然后再加载你设置的图片,导致加载多次,浪费流量
override func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
super.webView(webView, didFinish: navigation)
let jsString = """
function replaceHttpImageSrc() {
var imgs = document.getElementsByTagName("img");
var urlArray = [];
for (var i = 0; i < imgs.length; i++) {
var src = imgs[i].src;
if (src.indexOf("http://") != -1 && src.indexOf("https://") == -1) {
var newSrc = src.replace("http://","");
var newImgSrc = "你需要替换的图片地址";
var imgSrc = imgs[i].setAttribute("src", newImgSrc);
}
}
}
"""
// 执行js,如果有错,不进行下一步处理
webView.evaluateJavaScript(jsString) { (info, error) in
LOG.debug("注入js函数成功 jsString: \(jsString)")
if error == nil {
webView.evaluateJavaScript("replaceHttpImageSrc()", completionHandler: { (resutl, error) in
if error == nil {
LOG.debug("执行url中图片链接替换成功")
}
})
}
}
}
二、使用URLProtocol接管webview中所有的图片请求
这种方式需要使用到私有api,可以使用字符拼接或者base64解密的方式隐藏掉字符串,但是可能会碰到私有api弃用或者名称改变的情况,不建议使用
1、自建URLProtocol,处理需要的请求。我的名称为CustomURLProtocol
在app启动或者建立vc的时候注册协议
URLProtocol.registerClass(CustomURLProtocol.self)
2、获取私有类,注册需要监听的请求类型
为啥是这两个私有类,可以看下文章https://www.jianshu.com/p/8f5e1082f5e0,或者直接搜这个类的名字
let cls:AnyClass? = NSClassFromString("WKBrowsingContextController")
let sel = NSSelectorFromString("registerSchemeForCustomProtocol:")
if let mycls = cls, mycls.responds(to: sel) {
// 实例方法
// let obj = (mycls as! NSObject.Type).init()
// obj.perform(sel, with: "http")
// obj.perform(sel, with: "https")
// 静态方法
(mycls as! NSObject.Type).perform(sel, with: "http")
(mycls as! NSObject.Type).perform(sel, with: "https")
}
3、利用注册的CustomURLProtocol处理所有的http和https请求
import Foundation
import UIKit
import WebKit
import Alamofire
open class CustomURLProtocol: URLProtocol {
static let imageTypes = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".psd", ".svg", ".swf", ".tiff", ".tif", "=jpeg"]
/// 处理哪些请求需要接管
///
/// - Parameter request: 当前请求request
/// - Returns: true代表有当前协议接管,false就由系统接管
open override class func canInit(with request: URLRequest) -> Bool {
// 只处理http和https请求 ,我的需求只处理http
if let scheme = request.url?.scheme {
if scheme == "http" || scheme == "https" {
// 已经处理过的不再处理
if let pre = URLProtocol.property(forKey: "kURLProtocolHandledKey", in: request) as? Bool, pre {
return false
} else {
return true
}
}
}
return false
}
/// 这里可以将请求进行封装,比如加上统一的请求头,替换某些请求地址
///
/// - Parameter request: 当前请求
/// - Returns: 新的请求
open override class func canonicalRequest(for request: URLRequest) -> URLRequest {
// print(request.url?.absoluteString ?? "")
guard let url = request.url else {
return request
}
let urlString = url.absoluteString
// 包含图片文件,替换图片为指定的图片,也可以增加额外的参数
for type in imageTypes {
if urlString.lowercased().contains(type) {
if let url = URL(string: "https://ws2.sinaimg.cn/large/006tNc79gy1fo6k80vn82j3087036wem.jpg") {
let mutableRequest = NSMutableURLRequest(url: url, cachePolicy: request.cachePolicy, timeoutInterval: request.timeoutInterval)
return mutableRequest as URLRequest
}
}
}
return request
}
var session: URLSession?
var dataTask: URLSessionDataTask?
/// 开始加载请求
open override func startLoading() {
// print("start loading")
guard let url = self.request.url else {
return
}
// UserDefaults.standard.set(true, forKey: "kURLProtocolHandledKey"+ url.absoluteString)
URLProtocol.setProperty(true, forKey: "kURLProtocolHandledKey", in: NSMutableURLRequest(url: url))
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notification.request.url.loading"), object: url.absoluteString)
let config = URLSessionConfiguration.default
self.session = URLSession(configuration: config, delegate: self , delegateQueue: self.queue)
self.dataTask = self.session?.dataTask(with: self.request) // 需要使用本身的request
self.dataTask?.resume()
}
let queue = OperationQueue()
/// 结束加载请求
open override func stopLoading() {
// print("end loading")
self.session?.invalidateAndCancel()
self.session = nil
// self.dataTask?.cancel()
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "notification.request.url.loading"), object: "")
}
}
extension CustomURLProtocol: URLSessionDataDelegate {
// 完成请求
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let myerror = error {
self.client?.urlProtocol(self, didFailWithError: myerror)
} else {
self.client?.urlProtocolDidFinishLoading(self)
}
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: URLCache.StoragePolicy.notAllowed)
completionHandler(URLSession.ResponseDisposition.allow)
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
self.client?.urlProtocol(self, didLoad: data)
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: @escaping (CachedURLResponse?) -> Void) {
completionHandler(proposedResponse)
}
// 重定向url
public func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) {
guard let url = request.url else {
return
}
let redirectRequest = NSMutableURLRequest(url: url)
URLProtocol.removeProperty(forKey: "kURLProtocolHandledKey", in: redirectRequest)
self.client?.urlProtocol(self, wasRedirectedTo: request, redirectResponse: response)
// 取消当前任务
self.dataTask?.cancel()
// 取消当前的加载
self.client?.urlProtocol(self, didFailWithError: NSError(domain: "error domain", code: NSUserCancelledError, userInfo: nil))
}
}
另一个文件自行修改下
import Foundation
import WebKit
// 用来拦截url中的所有请求
class WKProtocolTestVC: USBaseViewController {
var webview: WKWebView!
override func setup() {
super.setup()
URLProtocol.registerClass(CustomURLProtocol.self)
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "WKWebview 处理图片"
if let url = URL(string: "https://www.baidu.com") {
let request = URLRequest(url: url)
webview.load(request)
}
}
override func buildUI() {
super.buildUI()
let config = WKWebViewConfiguration()
self.webview = WKWebView(frame: CGRect.zero, configuration: config)
self.webview.navigationDelegate = self
self.webview.uiDelegate = self
self.view.addSubview(webview)
webview.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
let cls:AnyClass? = NSClassFromString("WKBrowsingContextController")
let sel = NSSelectorFromString("registerSchemeForCustomProtocol:")
print(cls)
print(sel)
if let mycls = cls, mycls.responds(to: sel) {
// 实例方法
// let obj = (mycls as! NSObject.Type).init()
// obj.perform(sel, with: "http")
// obj.perform(sel, with: "https")
// 静态方法
(mycls as! NSObject.Type).perform(sel, with: "http")
(mycls as! NSObject.Type).perform(sel, with: "https")
}
}
}
extension WKProtocolTestVC: WKNavigationDelegate, WKUIDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let jsString = """
function replaceHttpImageSrc() {
var imgs = document.getElementsByTagName("img");
var urlArray = [];
for (var i = 0; i < imgs.length; i++) {
var src = imgs[i].src;
if (src.indexOf("http://") != -1 && src.indexOf("https://") == -1) {
var newSrc = src.replace("http://","");
var newImgSrc = "你需要替换的图片地址";
var imgSrc = imgs[i].setAttribute("src", newImgSrc);
}
}
}
"""
// 执行js,如果有错,不进行下一步处理
webView.evaluateJavaScript(jsString) { (info, error) in
print("注入js函数成功 jsString: \(jsString)")
if error == nil {
webView.evaluateJavaScript("replaceHttpImageSrc()", completionHandler: { (resutl, error) in
if error == nil {
print("执行url中图片链接替换成功")
}
})
}
}
}
}