使用application/x-www-form-urlencoded进行POST请求

40

后端开发人员在POST请求中给出了以下指示:

  1. 路由:{url}/{app_name/{controller}/{action}
  2. 控制器和操作应该是小写字母。
  3. API测试链接:http:****************
  4. 请求应该使用POST方法。
  5. 参数应该通过请求内容主体(FormUrlEncodedContent)传递。
  6. 参数应该是json格式。
  7. 参数区分大小写。

由于我没有经验处理第5个协议,所以我进行了搜索并最终完成了我的代码。

-(id)initWithURLString:(NSString *)URLString withHTTPMEthod:(NSString *)method withHTTPBody:(NSDictionary *)body {

    _URLString = URLString;
    HTTPMethod = method;
    HTTPBody = body;

    //set error message
    errorMessage = @"Can't connect to server at this moment. Try again later";
    errorTitle = @"Connection Error";

    return  self;
}


-(void)fireConnectionRequest {

    NSOperationQueue *mainQueue = [[NSOperationQueue alloc] init];
    [mainQueue setMaxConcurrentOperationCount:5];

    NSError *error = Nil;

    NSURL *url = [NSURL URLWithString:_URLString];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];

    NSData *sendData = [NSJSONSerialization dataWithJSONObject:HTTPBody options:NSJSONWritingPrettyPrinted error:&error];
    [request setHTTPMethod:@"POST"];

    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    [request setHTTPBody: sendData];
    [NSURLConnection connectionWithRequest:request delegate:self];

    NSString *jsonString = [[NSString alloc]initWithData:sendData encoding:NSUTF8StringEncoding];


    //fire URL connectiion request
    [NSURLConnection sendAsynchronousRequest:request queue:mainQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {

        //get the return message and transform to dictionary
        NSString *data = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
        returnMessage = [NSJSONSerialization JSONObjectWithData: [data dataUsingEncoding:NSUTF8StringEncoding]
                                                        options: NSJSONReadingMutableContainers
                                                          error:&error];


        //check return message
        if (!error) {
            [delegate returnMessageForTag:self.tag];

        }
        else {
            [delegate returnErrorMessageForTag:self.tag];
        }

    }];

}

我传递了一个格式化为JSON的字典。他同意我能够传递正确的数据。而且我能够连接到API,但是每当我尝试发送注册数据时,它总是返回“FAILED”。连接没有问题,但我无法传输数据。

这里使用相同API的Android开发人员没有任何问题,但由于不熟悉iOS,他无法帮助我解决问题。

我错过了什么?


2
在某些情况下,我遇到了与JSON参数顺序有关的问题。由于NSDictionary是无序的,当你将其转换为JSON时,参数的顺序可能与声明时使用的顺序不同。尝试手动设置JSON字符串并检查是否有效。如果问题出在这里,你必须使用有序字典(在GitHub上搜索,有许多实现)。 - LombaX
11个回答

49

试试这段代码

Objective C

NSString *post =[NSString stringWithFormat:@"AgencyId=1&UserId=1&Type=1&Date=%@&Time=%@&Coords=%@&Image=h32979`7~U@)01123737373773&SeverityLevel=2",strDateLocal,strDateTime,dict];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:@"%d",[postData length]];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://google/places"]]];
[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];
NSError *error;
NSURLResponse *response;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSString *str=[[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];

Swift 2.2

->

Swift 2.2

var post = "AgencyId=1&UserId=1&Type=1&Date=\(strDateLocal)&Time=\(strDateTime)&Coords=\(dict)&Image=h32979`7~U@)01123737373773&SeverityLevel=2"
var postData = post.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: true)!
var postLength = "\(postData.length)"
var request = NSMutableURLRequest()
request.URL = NSURL(string: "http://google/places")!
request.HTTPMethod = "POST"
request.setValue(postLength, forHTTPHeaderField: "Content-Length")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.HTTPBody = postData
NSError * error
NSURLResponse * response
var urlData = try! NSURLConnection.sendSynchronousRequest(request, returningResponse: response)!
var str = String(data: urlData, encoding: NSUTF8StringEncoding)

Swift 3.0

let jsonData = try? JSONSerialization.data(withJSONObject: kParameters)
    let url: URL = URL(string: "Add Your API URL HERE")!
    print(url)
    var request: URLRequest = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = jsonData
    request.setValue(Constant.UserDefaults.object(forKey: "Authorization") as! String?, forHTTPHeaderField: "Authorization")
    request.setValue(Constant.kAppContentType, forHTTPHeaderField: "Content-Type")
    request.setValue(Constant.UserAgentFormat(), forHTTPHeaderField: "User-Agent")

    let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in

        if data != nil {

            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! NSDictionary
                print(json)
            } catch let error as NSError {
                print(error)
            }
        } else {
            let emptyDict = NSDictionary()
        }
    })
    task.resume()

Swift 4

let headers = [
            "Content-Type": "application/x-www-form-urlencoded"
        ]

    let postData = NSMutableData(data: "UserID=351".data(using: String.Encoding.utf8)!)
    let request = NSMutableURLRequest(url: NSURL(string: "Add Your URL Here")! as URL,
                                      cachePolicy: .useProtocolCachePolicy,
                                      timeoutInterval: 10.0)
    request.httpMethod = "POST"
    request.allHTTPHeaderFields = headers
    request.httpBody = postData as Data

    let session = URLSession.shared
    let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
        if (error != nil) {
            print(error!)
        } else {
            let httpResponse = response as? HTTPURLResponse
            print(httpResponse!)

            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
                print(json)
            } catch {
                print(error)
            }

        }
    })

    dataTask.resume()

Alamofire

Alamofire.request("Add Your URL Here",method: .post, parameters: ["CategoryId": "15"])
        .validate(contentType: ["application/x-www-form-urlencoded"])
        .responseJSON { (response) in

            print(response.result.value)

    }

我希望这段代码对你有用。


谢谢,它对我有用!不知道为什么答案没有被标记为正确。 - Amrendra Pratap Singh
10
Swift 3 的代码示例有误。它根本没有执行 application/x-www-form-urlencoded,而是发送了 JSON。 - alexkent
请同时添加Alamofire的示例。谢谢。 - Awais Fayyaz
@AwaisFayyaz 当然,我很快就会完成。谢谢。 - Darshan Kunjadiya
1
@AwaisFayyaz 根据您的要求,我已经在答案中添加了 Alamofire 的示例。 - Darshan Kunjadiya
有人可以告诉我如何创建 API 请求参数,例如------CommunityID:24 FormID:21 FormData:{"FormID":"21","userResponse":[{"FormFieldID":"FormFieldID1","RptHeader":"Name","userData":"mahesh"},{"FormFieldID":"FormFieldID2","RptHeader":"Mobile Number","userData":"7788556699"},{"FormFieldID":"FormFieldID3","RptHeader":"Email","userData":"mahesh@gmail.com"},{"FormFieldID":"FormFieldID3","RptHeader":"Position","userData":"tester"},{"FormFieldID":"FormFieldID3","RptHeader":"Employment Status","userData":"employee"}]} - Protocol

13

Swift 4

let params = ["password":873311,"username":"jadon","client_id":"a793fb82-c978-11e9-a32f-2a2ae2dbcce4"]
let jsonString = params.reduce("") { "\($0)\($1.0)=\($1.1)&" }.dropLast()
let jsonData = jsonString.data(using: .utf8, allowLossyConversion: false)!
urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
urlRequest.httpBody  = jsonData 

1
需要通过以下方式删除最后一个'&'字符:let jsonString = json.reduce("") { "\($0)\($1.0)=\($1.1)&" }.dropLast() - Raj Pawan Gumdal

7

@fatihyildizhan

由于声望值不足以直接评论您的回答,因此我在这里回答。

Swift 1.2

let myParams = "username=user1&password=12345"
let postData = myParams.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: true)
let postLength = String(format: "%d", postData!.length)

var myRequest = NSMutableURLRequest(URL: self.url)
myRequest.HTTPMethod = "POST"
myRequest.setValue(postLength, forHTTPHeaderField: "Content-Length")
myRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
myRequest.HTTPBody = postData

var response: AutoreleasingUnsafeMutablePointer<NSURLResponse?> = nil

这段代码在我的情况下运行良好。


这个答案没有正确地对参数进行URL编码,因此包含保留字符的数据可能会被服务器拒绝或解释错误。请参见规范:https://url.spec.whatwg.org/#urlencoded-serializing - Josh Heald

7

Swift提供了一种URL编码函数,但正如@nolanw在第1条评论中指出的那样,它不是完全匹配的。对于原始问题中的步骤5,一旦您将键值对放入某个结构中,这里有一个简短而简单的替代方案进行编码(Swift 4.2):

var urlParser = URLComponents()
urlParser.queryItems = [
    URLQueryItem(name: "name", value: "Tim Tebow"),
    URLQueryItem(name: "desc", value: "Gators' QB")
]
let httpBodyString = urlParser.percentEncodedQuery

将此粘贴到Xcode playground中,然后添加print(httpBodyString!)。在输出中,您将看到:
name=Tim%20Tebow&desc=Gators'%20QB

注意:此处适用于百分号编码基本表单值集(即不包括二进制数据和多部分)


4
这是个好主意,但不完全符合“application/x-www-form-urlencoded”序列化的规范。空格应该被编码为“+”,但是使用“URLComponents.percentEncodedQuery”时,空格会被编码为“%20”。这对你的用例可能没有影响,但我认为值得指出,以防对其他人产生影响! - nolanw

5

这个版本处理参数的编码,将空格替换为 '+'。

extension String {
    static let formUrlencodedAllowedCharacters =
        CharacterSet(charactersIn: "0123456789" +
            "abcdefghijklmnopqrstuvwxyz" +
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
            "-._* ")

    public func formUrlencoded() -> String {
        let encoded = addingPercentEncoding(withAllowedCharacters: String.formUrlencodedAllowedCharacters)
        return encoded?.replacingOccurrences(of: " ", with: "+") ?? ""
    }
}

class HTTPUtils {
    public class func formUrlencode(_ values: [String: String]) -> String {
        return values.map { key, value in
            return "\(key.formUrlencoded())=\(value.formUrlencoded())"
        }.joined(separator: "&")
    }
}

let headers = [
    "content-type": "application/x-www-form-urlencoded; charset=utf-8"
]

let body = HTTPUtils.formUrlencode([
    "field": "value"
])

var request = try URLRequest(url: url, method: .post, headers: headers)
request.httpBody = body.data(using: .utf8)

URLSession.shared.dataTask(with: request, completionHandler: { ... }).resume()

这个答案正确地处理了百分号编码,使用了在这里指定的机制:https://url.spec.whatwg.org/#urlencoded-serializing其他可用于此问题的答案中的URL编码(通常缺失,如果存在,则不正确)未涵盖正确的字符。当使用那些选项时,请求体中的更复杂数据将被错误地编码,因此接收服务器将无法理解有效载荷。令人惊讶的是,URLRequest中没有内置的处理方法。 - Josh Heald
据我所知,这里唯一合适的答案。虽然最好作为[String: String]的扩展。您可以使用带有反斜杠的"""来避免允许字符变量中的丑陋+ - mxcl

2

Swift 4.2

    func percentEscapeString(_ string: String) -> String {
            var characterSet = CharacterSet.alphanumerics
            characterSet.insert(charactersIn: "-._* ")
            return string
              .addingPercentEncoding(withAllowedCharacters: characterSet)!
              .replacingOccurrences(of: " ", with: " ")
              .replacingOccurrences(of: " ", with: " ", options: [], range: nil)
              .replacingOccurrences(of: "\"", with: "", options: NSString.CompareOptions.literal, range:nil)
          }
//    Set encoded values to Dict values you can decode keys if required
    dictData.forEach { (key, value) in
              if let val = value as? String {
                dictData[key] = self.percentEscapeString(val)
              } else {
                dictData[key] = value
              }
            }

这对我很有用,这里是源链接:https://gist.github.com/HomerJSimpson/80c95f0424b8e9718a40

这个答案正确地处理了百分号编码,使用了在此处指定的机制:url.spec.whatwg.org/#urlencoded-serializing 其他可用于此问题的答案中的URL编码(通常缺失,如果存在,则不正确)未涵盖正确的字符。当使用这些选项时,请求体中的更复杂数据将被错误地编码,因此接收服务器将无法理解有效载荷。令人惊讶的是,URLRequest 中没有内置的处理方法。 - Josh Heald

2

在Swift 3中,JSONSerialization.data(withJSONObject: kParameters)的语法不能正常工作,因此我不得不复制AlamoFire的解决方案...

let body2 = ["username": "au@gmail.com",
        "password": "111",
        "client_secret":"7E",
        "grant_type":"password"]

let data : Data = query(body2).data(using: .utf8, allowLossyConversion: false)!
var request : URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type");
request.setValue(NSLocalizedString("lang", comment: ""), forHTTPHeaderField:"Accept-Language");
request.httpBody = data 

do {...}

}

public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
        var components: [(String, String)] = []

        if let dictionary = value as? [String: Any] {
            for (nestedKey, value) in dictionary {
                components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
            }
        } else if let array = value as? [Any] {
            for value in array {
                components += queryComponents(fromKey: "\(key)[]", value: value)
            }
        } else if let value = value as? NSNumber {
            if value.isBool {
                components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
            } else {
                components.append((escape(key), escape("\(value)")))
            }
        } else if let bool = value as? Bool {
            components.append((escape(key), escape((bool ? "1" : "0"))))
        } else {
            components.append((escape(key), escape("\(value)")))
        }

        return components
    }


    public func escape(_ string: String) -> String {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*+,;="

        var allowedCharacterSet = CharacterSet.urlQueryAllowed
        allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")

        var escaped = ""

        if #available(iOS 8.3, *) {
            escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
        } else {
            let batchSize = 50
            var index = string.startIndex

            while index != string.endIndex {
                let startIndex = index
                let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
                let range = startIndex..<endIndex

                let substring = string.substring(with: range)

                escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring

                index = endIndex
            }
        }
        return escaped
    }

还有一个扩展:

extension NSNumber {
fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) }}

这只是暂时的解决方案,需要找到更好的解决方法...

希望能对您有所帮助...


1
在这行代码中 let data : Data = query(body2).data(using: .utf8, allowLossyConversion: false)!query() 是什么?我在你的代码中找不到它! - Basel
这个答案没有正确地对参数进行URL编码,因此包含保留字符的数据可能会被服务器拒绝。请参见规范:https://url.spec.whatwg.org/#urlencoded-serializing - Josh Heald

1
截至Swift 5,以下代码已经经过测试,可以在今天使用。
let url = URL(string: "https://something")
var request = URLRequest(url: url!)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let post = "parameter1=abc&parameter2=abc"
let postData = post.data(using: String.Encoding.ascii, allowLossyConversion: true)!
request.httpBody = postData

0

这段代码能否转换成Swift?我已经尝试过了,但无法处理它。也许这个代码块可以帮助你。谢谢。

    let myParams:NSString = "username=user1&password=12345"
    let myParamsNSData:NSData = NSData(base64EncodedString: myParams, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)!
    let myParamsLength:NSString = NSString(UTF8String: myParamsNSData.length)
    let myRequest: NSMutableURLRequest = NSURL(fileURLWithPath: self.url)
    myRequest.HTTPMethod = "POST"
    myRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    myRequest.HTTPBody = myParamsNSData
    var data2: NSData!
    var error2: NSError!

0
将字典参数解析为字符串:
-(NSData *)encodeParameters:(NSDictionary *)parameters {

NSMutableArray *list = [NSMutableArray new];

for (NSString *key in [parameters allKeys]) {
    id obj = [parameters objectForKey:key];
    NSString *path = [NSString stringWithFormat:@"%@=%@", key, obj];
    [list addObject:path];
}

return [[list componentsJoinedByString:@"&"] dataUsingEncoding:NSUTF8StringEncoding];

}

并使用它:

[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[self encodeParameters:parameters]];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接