当我使用 AFNetworking v3.1.0 上传文件multipartFormRequestWithMethod
并包含一个参数字典时,该字典将转换为 JSON,并且字典中有一个条目,例如:
["Test": 1]
即带数值的键,到达我的服务器时的JSON结构是带引号的字符串。例如:
{"Test":"1"}
这不是我想要的。我希望在服务器上收到的是:
{"Test": 1}
请注意,使用 AFHTTPSessionManager 的 POST 方法(我相信它不允许文件上传)时,我没有遇到这个问题。
这是 AFNetworking 中的错误吗?这是我正在做的事情吗?有解决方法或其他解决方案吗?我在AFNetworking JSON 序列化问题上看到了讨论,但我的应用程序并不真正适合在服务器上解析以使其恢复到我想要的方式(不带引号的数字)。想法?
这是我使用 AFNetworking 的客户端代码:
import Foundation
import SMCoreLib // some common library functions I use
class Server {
private let manager: AFHTTPSessionManager!
internal static let session = Server()
var uploadTask:NSURLSessionUploadTask?
private init() {
self.manager = AFHTTPSessionManager()
// https://stackoverflow.com/questions/26604911/afnetworking-2-0-parameter-encoding
self.manager.responseSerializer = AFJSONResponseSerializer()
// This does appear necessary for requests going out to server to receive properly encoded JSON parameters on the server.
self.manager.requestSerializer = AFJSONRequestSerializer()
self.manager.requestSerializer.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
// In the completion hanlder, if error != nil, there will be a non-nil serverResponse.
internal func sendServerRequestTo(toURL serverURL: NSURL, withParameters parameters:[String:AnyObject],
completion:((serverResponse:[String:AnyObject]?, error:NSError?)->())?) {
Log.special("serverURL: \(serverURL)")
if !Network.connected() {
Log.msg("Network not connected!")
completion?(serverResponse: nil, error: Error.Create("Network not connected!"))
return
}
self.manager.POST(serverURL.absoluteString, parameters: parameters, progress: nil,
success: { (request:NSURLSessionDataTask, response:AnyObject?) in
if let responseDict = response as? [String:AnyObject] {
Log.msg("AFNetworking Success: \(response)")
completion?(serverResponse: responseDict, error: nil)
}
else {
completion?(serverResponse: nil, error: Error.Create("No dictionary given in response"))
}
},
failure: { (request:NSURLSessionDataTask?, error:NSError) in
print("**** AFNetworking FAILURE: \(error)")
completion?(serverResponse: nil, error: error)
})
}
// withParameters must have a non-nil key SMServerConstants.fileMIMEtypeKey
internal func uploadFileTo(serverURL: NSURL, withParameters parameters:[String:AnyObject]?, completion:((serverResponse:[String:AnyObject]?, error:NSError?)->())?) {
Log.special("serverURL: \(serverURL)")
if !Network.connected() {
completion?(serverResponse: nil, error: Error.Create("Network not connected."))
return
}
var error:NSError? = nil
let request = AFJSONRequestSerializer().multipartFormRequestWithMethod("POST", URLString: serverURL.absoluteString, parameters: parameters, constructingBodyWithBlock: nil, error: &error)
if nil != error {
completion?(serverResponse: nil, error: error)
return
}
self.uploadTask = self.manager.uploadTaskWithStreamedRequest(request, progress: { (progress:NSProgress) in
},
completionHandler: { (request: NSURLResponse, responseObject: AnyObject?, error: NSError?) in
if (error == nil) {
if let responseDict = responseObject as? [String:AnyObject] {
Log.msg("AFNetworking Success: \(responseObject)")
completion?(serverResponse: responseDict, error: nil)
}
else {
let error = Error.Create("No dictionary given in response")
Log.error("**** AFNetworking FAILURE: \(error)")
completion?(serverResponse: nil, error: error)
}
}
else {
Log.error("**** AFNetworking FAILURE: \(error)")
completion?(serverResponse: nil, error: error)
}
})
if nil == self.uploadTask {
completion?(serverResponse: nil, error: Error.Create("Could not start upload task"))
return
}
self.uploadTask?.resume()
}
}
我使用了 nil 参数值,constructingBodyWithBlock
因为当我上传文件和不上传文件时会出现问题(此示例实际上并未上传文件)。
下面是调用这些方法的代码:
import UIKit
import SMCoreLib
class ViewController: UIViewController {
//let serverURL = NSURL(string: "http://192.168.3.228:8082/json/")!
let serverURL = NSURL(string: "http://192.168.3.228:8082/upload/")!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let params = ["Test" : 1]
Server.session.uploadFileTo(serverURL, withParameters:params){ (serverResponse, error) in
Log.msg("serverResponse: \(serverResponse); error: \(error)")
}
/*
Server.session.sendServerRequestTo(toURL: self.serverURL, withParameters: params) { (serverResponse, error) in
Log.msg("serverResponse: \(serverResponse); error: \(error)")
}*/
}
}
为了完整起见,我将包含我用于测试的 Node.js 服务器:
'use strict';
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var multer = require('multer');
// https://stackoverflow.com/questions/4295782/how-do-you-extract-post-data-in-node-js
app.use(bodyParser.json({extended : true}));
const fileUploadFieldName = "uploadFile";
const initialUploadDirectory = "./uploadedFiles";
// See https://stackoverflow.com/questions/31496100/cannot-app-usemulter-requires-middleware-function-error
// See also https://codeforgeek.com/2014/11/file-uploads-using-node-js/
// TODO: Limit the size of the uploaded file.
// TODO: Is there a way with multer to add a callback that gets called periodically as an upload is occurring? We could use this to "refresh" an activity state for a lock to make sure that, even with a long-running upload (or download) if it is still making progress, that we wouldn't lose a lock.
var upload = multer({ dest: initialUploadDirectory}).single(fileUploadFieldName);
function handleBody(request, response) {
response.setHeader('Content-Type', 'application/json');
console.log("Got request!");
const json = request.body["Test"];
console.log("json: " + json);
const fullBody = JSON.stringify(request.body);
console.log("fullBody: " + fullBody);
response.end(fullBody);
}
app.post("/json" , function(request, response) {
handleBody(request, response);
});
app.post("/upload", upload, function (request, response) {
handleBody(request, response);
});
app.set('port', 8082);
app.listen(app.get('port'), function() {
console.log('Node app is running on port', app.get('port'));
});
这是服务器的 package.json:
{
"name": "jsonserver",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.15.1",
"express": "^4.13.4",
"multer": "^1.1.0"
}
}