0

当我使用 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"
  }
}
4

1 回答 1

0

事实证明,最好不要将这个问题视为 AFNetworking 错误。

这是我对客户端所做的更改:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    // Params you actually want received at the server.
    let params = ["Test" : 1, "Test2": 2]

    var jsonData:NSData?

    do {
        try jsonData = NSJSONSerialization.dataWithJSONObject(params, options: NSJSONWritingOptions(rawValue: 0))
    } catch (let error) {
        Assert.badMojo(alwaysPrintThisString: "Yikes: Error serializing to JSON data: \(error)")
    }

    // The server needs to pull "serverParams" out of the request body, then convert the value to JSON
    let serverParams = ["serverParams" : jsonData!]

    Server.session.uploadFileTo(serverURL, withParameters: serverParams){ (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)")
    }*/
}

这是我对服务器所做的更改:

function handleBody(request, response) {
    response.setHeader('Content-Type', 'application/json');

    console.log("Got request!");
    const jsonObject = JSON.parse(request.body["serverParams"]);
    const jsonString = JSON.stringify(jsonObject);
    console.log("json: " + jsonString);

    response.end(jsonString);
}

另请参阅https://github.com/AFNetworking/AFNetworking/issues/3544

于 2016-06-07T16:40:32.507 回答