添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

一、问题引出

在iOS中,如果 WKWebview 跳转的链接不带参数但是带了 # 网页锚点,而你这边项目因为要兼容所有跳转链接,对链接进行了百分比编码,将 # 编码为了 23% , 那么将出现”无法显示网页“或空白网页的情况。

同时满足下面3个条件会出现这个问题:

  • 配置的广告跳转链接中带了 # 符号,即有网页锚点。
  • 链接中是没有参数部分的,即 ?param1=value1& 之类的。
  • webview 加载这个链接之前,对链接整体进行了百分比编码, “#” 符号被编码为 ”23%“
  • 在实际的场景中,产品或运维配置广告链接时,有时需要打开网页后跳转到某个元素节点的,也就是有链接中带 # 这种需求的。

    为了兼容他们配置带 # 链接这种情况,我们iOS这边需要代码上做兼容。

    二、问题根因

    1. 链接中 # 的作用

    一般用于较长网页中,跳转到网页中的某个节点。

    2. 对配置链接进行调试探索

    拿一个链接进行举例: " juejin.cn/post/717682… " ,

    进行百分比编码后:
  • 编码后变为: https://juejin.cn/post/717682356705977963923%heading-4 ,加载失败。
  • 删掉 # 锚点内容后,请求路径 https://juejin.cn/post/7176823567059779639 ,加载成功。
  • 对于上述链接"#"不进行编码:
  • 直接能加载成功, 并且跳转到锚点‘heading-4’。
  • 如果锚点名称写错了,如‘heading-4’写成了‘heading-400’,那么也能加载成功,只不过不会跳到锚点。
  • 那么为什么#被编码为23%之后,就不能请求成功呢?

    3. 链接中#是否被编码,服务器收到请求时有何异同?

    我们对链接进行百分比编码后,通过 Charles 抓包请求的结果: 可以看到:

  • 如果 # 编码为 23% ,则服务器收到的请求路径也是带 23% .
  • 如果是未编码 # ,则服务器收到的请求路径是不带 # 后面的内容的。
  • 这也就是说,对于iOS端来说, 客户端发送请求时未发送#及后面的内容,但是会发送23%及后面的内容。 具体的响应是服务器决定的。

    其中 # 编码为 23% 的两种情况:

  • 23% 后面还有 / , 比如 https:www.xxx.com/path1/path23%/
  • 23% 后面没有 / ,比如 https:www.xxx.com/path1/path23% https:www.xxx.com/path1/path23%section1
  • 第一种情况下,有的网页能加载出来,有的网页会找不到网页,能否加载成功是根据服务器能否找到网页来定;第二种加载会失败,原因是 23% 也被服务器拿去查找资源路径。

    我相信到这里,应该已经解释清楚了问题发生的原因。

    三、兼容链接#的解决方案

    我们客户端APP上显示的营销广告链接都是来源于后台配置的,有时配置的链接是有需要跳到锚点的需求的,那么我们该怎么兼容呢?

  • 需要对链接进行百分比编码.
  • 百分比编码时需要屏蔽掉 # .
  • let url = "https://juejin.cn/post/7176823567059779639#heading-4"
    var notEncodeSet = CharacterSet.urlQueryAllowed
    // 关键代码:
    // 在对链接进行百分比编码时,不编码字符集中追加#
    notEncodeSet.insert(charactersIn: "#")
    if let urlPath = url.addingPercentEncoding(withAllowedCharacters: notEncodeSet) {
        // 一般会有对path追加自定义公参或者设置自定义请求头之类的事情...
        let URL = URL(string: urlPath)!
        let request = MutableURLRequest(url: URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
        // 具体的加载
        webview.load(request as URLRequest)
    

    使用Alamofire的字符编码不能解决问题

    在找到上述原因后,我们可能会考虑使用Alamofire的字符集CharacterSet.afURLQueryAllowed使用来代替系统的CharacterSet.urlQueryAllowed去编码,但这样有用吗?

    首先来看下CharacterSet.afURLQueryAllowed是怎么生成的:

    public static let afURLQueryAllowed: CharacterSet = {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*+,;="
        let encodableDelimiters = CharacterSet(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
        return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters)
    

    可以看到是由CharacterSet.afURLQueryAllowed中除去通用分隔符和子分隔符后生成,也就是说是系统字符集的一个子集,对于这个问题也是行不通的!!!