返回

Swift NSURLComponents %2C重复编码问题及解决方案

IOS

Swift 中 NSURLComponents 对 %2C 的重复编码问题及解决方案

在使用 NSURLComponents 构建 URL 时,开发者有时会遇到 , 被编码成 %252C 而不是期望的 %2C 的情况。这种问题会导致 API 请求失败,因为服务器无法正确解析参数。本文将深入探讨此问题的原因,并提供多种解决方案。

问题分析

NSURLComponents 在构建 URL 时会自动对 query 参数进行百分号编码。当开发者手动对字符串进行编码时,再将其传入 NSURLComponents,就可能导致重复编码。%2C 本身是 , 的编码形式。当 NSURLComponents 再次编码时,会将 % 编码为 %25,导致最终结果变为 %252C

具体到示例代码,generateBboxValue 函数已经对 , 进行了编码,生成了包含 %2C 的字符串。当该字符串被用作 NSURLQueryItem 的值时,NSURLComponents 会进一步编码,从而导致问题。

解决方案

以下是几种解决此问题的方案:

1. 避免手动编码,直接使用原始值

最直接的解决方案是避免手动对字符串进行编码,直接将原始值传给 NSURLComponents,让它自动处理编码。

代码示例:

func generateBboxValue(lat: Int, lon: Int) -> String {
    let lat1 = lat - 1
    let lat2 = lat + 1
    let lon1 = lon - 1
    let lon2 = lon + 1

    return "\(lat1),\(lat2),\(lon1),\(lon2)"
}

func generateSearchByLocaleURL(lat: Int, lon: Int) -> String? {
    var components = NSURLComponents()

    let bbox = generateBboxValue(lat: lat, lon: lon)

    components.scheme = "https"
    components.host = "api.flickr.com"
    components.path = "/services/rest/"
    components.queryItems = [
        URLQueryItem(name: "method", value: "flickr.photos.search"),
        URLQueryItem(name: "api_key", value: "KEY_VALUE"),
        URLQueryItem(name: "bbox", value: bbox),
        URLQueryItem(name: "extras", value: "url_m"),
        URLQueryItem(name: "format", value: "json"),
        URLQueryItem(name: "nojsoncallback", value: "1")
    ]

    return components.string
}

操作步骤:

  1. 修改 generateBboxValue 函数,直接返回用逗号分隔的字符串。
  2. 使用 generateSearchByLocaleURL 生成 URL。

通过这种方式,NSURLComponents 会正确地将 , 编码为 %2C

原理: 此方法将编码的任务完全交给 NSURLComponents 处理,避免了手动编码可能导致的错误。这是一种推荐的做法,因为它更简洁,更不容易出错。

2. 使用 String.addingPercentEncoding(withAllowedCharacters:) 方法进行部分编码

如果出于某些原因必须手动处理部分编码,可以使用 String.addingPercentEncoding(withAllowedCharacters:) 方法进行更精细的控制,只编码必要的字符。

代码示例:

func generateBboxValue(lat: Int, lon: Int) -> String {
    let lat1 = lat - 1
    let lat2 = lat + 1
    let lon1 = lon - 1
    let lon2 = lon + 1

    let bbox = "\(lat1),\(lat2),\(lon1),\(lon2)"
    let allowedCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-._~"))
    return bbox.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? bbox
}

// generateSearchByLocaleURL 函数保持不变

操作步骤:

  1. generateBboxValue 函数中,使用 addingPercentEncoding(withAllowedCharacters:) 方法,并指定允许的字符集。此处允许的字符集包含字母数字字符,以及 -._~ 这些常用字符。
  2. generateBboxValue 的返回值传入 generateSearchByLocaleURL 函数生成URL。

原理: 该方法允许开发者指定哪些字符不需要编码,只编码其他字符。在本例中,逗号 , 不在允许的字符集中,因此会被编码为 %2C

额外安全建议: 在构建 URL 时,始终要对用户输入的数据进行编码,以防止安全漏洞,如跨站脚本攻击 (XSS)。 即使使用 addingPercentEncoding(withAllowedCharacters:) 进行部分编码,也要仔细考虑允许的字符集,避免遗漏可能导致安全问题的字符。 除了使用 CharacterSet.alphanumerics 外,还需要根据实际情况添加其他允许的字符。 例如,如果URL中允许空格,则需要将空格添加到允许的字符集中。 同时要对允许的字符集进行严格的审查和测试,以确保其安全性。

3. 创建自定义 URLQueryItem

如果需要更高的灵活性,可以创建自定义的 URLQueryItem,并在其中处理编码。

代码示例:

extension URLQueryItem {
    init(name: String, unencodedValue: String) {
        self.init(name: name, value: unencodedValue)
    }
}

func generateSearchByLocaleURL(lat: Int, lon: Int) -> String? {
    var components = NSURLComponents()
    let bbox = generateBboxValue(lat: lat, lon: lon)
    components.scheme = "https"
    components.host = "api.flickr.com"
    components.path = "/services/rest/"
    components.queryItems = [
        URLQueryItem(name: "method", value: "flickr.photos.search"),
        URLQueryItem(name: "api_key", value: "KEY_VALUE"),
        URLQueryItem(name: "bbox", unencodedValue: bbox),
        URLQueryItem(name: "extras", value: "url_m"),
        URLQueryItem(name: "format", value: "json"),
        URLQueryItem(name: "nojsoncallback", value: "1")
    ]
    return components.string
}

// generateBboxValue 函数使用第一种方案的代码,返回用逗号分隔的原始字符串即可

操作步骤:

  1. 定义 URLQueryItem 的扩展,创建一个接受未编码值的初始化方法。
  2. 使用 URLQueryItem(name:unencodedValue:) 创建 bboxqueryItem
  3. 其余部分代码保持不变。

原理: 通过自定义 URLQueryItem 的初始化方法,可以绕过 NSURLComponents 的默认编码行为,直接使用未编码的数值。 这种方式为开发者提供了更高的自由度,可以精确控制每个参数的编码方式。

总结

处理URL编码问题时,最重要的是理解 NSURLComponents 的工作原理,避免重复编码。直接使用原始值并让 NSURLComponents 自动处理编码通常是最简单、最安全的方法。如果需要手动处理编码,则应使用 String.addingPercentEncoding(withAllowedCharacters:) 进行部分编码,或者创建自定义 URLQueryItem 进行精确控制。

选择哪种方法取决于具体的需求和场景,但总体原则是保持代码简洁、易读,并注意安全性。