AFNetworking v3.1.0 multipartFormRequestWithMethod Uploads JSON Numeric Values With Quotes

不打扰是莪最后的温柔 提交于 2019-12-12 03:29:35

问题


When I upload a file using AFNetworking v3.1.0 multipartFormRequestWithMethod and include a parameter dictionary, which is converted to JSON, and the dictionary has an entry such as:

["Test": 1]

that is, a key with a numeric value, the JSON structure when it arrives at my server is a quoted string. For example:

{"Test":"1"}

This is not what I want. What I want received on the server is:

{"Test": 1}

Note that I do not get this problem when using the POST method of AFHTTPSessionManager (which I believe doesn't allow for file upload).

Is this a bug in AFNetworking? Is it something I'm doing? Is there a workaround or other solution? I've seen the discussion at AFNetworking JSON serialization problems but my application isn't really suited to parsing on the server to get this back to the way I'd like it (numbers without the quotes). Thoughts?

Here's my client code which uses 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()
    }
}

I've used a nil parameter value for constructingBodyWithBlock because the issue arises when I upload a file and when I don't (this example doesn't actually upload a file).

Here's the code that calls those methods:

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)")
        }*/
    }
}

For completeness, I'll include the Node.js server I'm using for testing:

'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'));
});

And here's the package.json for the server:

{
  "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"
  }
}

回答1:


It turns out this issue is probably best not considered an AFNetworking error.

Here's the change I made on the client:

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)")
    }*/
}

Here's the change I made to the server:

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);
}

See also https://github.com/AFNetworking/AFNetworking/issues/3544



来源:https://stackoverflow.com/questions/37449472/afnetworking-v3-1-0-multipartformrequestwithmethod-uploads-json-numeric-values-w

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!