服务端给的接口,需要将参数以application/json的格式,作为请求体,以post请求的方式传给服务端。
项目的网络请求框架是封装的Alamofire,业务层只需要构建业务对应的Request和Response即可。业务代码大致如下:
class Request: WowRequest<Respone> {
var countInfo: Bool = false
var page: Int = 0
var pageSize: Int = 10
override var requestPath: String {
"/community/my" //url的path
}
override var requestType: HttpRequestMethod {
.get //http method
}
override var params: [String : Any] {
//携带的参数
var p = super.params
p["countInfo"] = countInfo
p["page"] = page
p["pageSize"] = pageSize
return p
}
}
框架代码最终调用Alamofire的网络请求API,我们比较关心的一点是,如何处理params,查看Alamofire的源码
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
guard let url = urlRequest.url else {
throw AFError.parameterEncodingFailed(reason: .missingURL)
}
if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
urlComponents.percentEncodedQuery = percentEncodedQuery
urlRequest.url = urlComponents.url
}
} else {
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
}
return urlRequest
}
代码中将Get请求的参数直接拼接到url上;Post请求参数转为data,并作为httpbody设置给了request,也就是说所有的附加参数,最终都是通过httpbody传给了服务端,格式为"application/x-www-form-urlencoded; charset=utf-8".那我们的需求是既需要带上参数,同时还要支持携带application/json格式的请求体。
解决思路可能就是2条:
private func buildRequestWithHttpBody(url: String, method: HTTPMethod, parameters: Parameters, header: HTTPHeaders, httpBody: Data) -> URLRequest? {
//params格式为[String:Any],需要转为[String:String]
var stringParameters = [String:String]()
parameters.forEach { (key: String, value: Any) in
let strKV = URLEncoding.default.queryComponents(fromKey: key, value: value)
stringParameters.merge(strKV) { old, new in
return new
}
}
//使用URLComponents作为url转换中间对象,避免自己写url添加参数,无法考虑周全
guard var urlComponent = URLComponents(string: url) else { return nil }
urlComponent.queryItems = stringParameters.map { URLQueryItem(name: $0.key, value: $0.value) }
guard let url = urlComponent.url, var myURLRequest = try? URLRequest(url: url.absoluteString, method: method, headers: header) else {
Log.debug("错误的URLRequest")
return nil
}
myURLRequest.httpBody = httpBody
return myURLRequest
}
经过测试方案二是行得通的。这里面比较让我诧异的是针对post请求,请求参数直接加到url上和以urlencode表单提交,两者对于服务端来说都是可以的。
查阅了java web相关资料,服务端应该是@RequestParam注解来接收携带参数的,对于拼接到url上的参数和通过表单携带的参数都能解析。代码形式如下:
@PostMapping(value = "/test")
public void test(@RequestParam("id") Integer id,
@RequestParam("name") String name,
@RequestParam("age") Integer age,
@RequestBody String content) {
log.info("id = {}, name = {}, age = {}", id, name, age);
}
如果需要额外携带Application/json的请求体,那么需要将其他参数拼接到request的url上才行;如果不携带application/json请求体,其他参数可以作为表单添加到request的httpbody上。区别就是要把httpbody的坑位留给更需要的人。