'How can I pass parameters in an HTTP Post request in Swift?

Am working on a simple Swift test app which just calls Perl script on my server. Right now I just want to send over a username and id, and get them back in a JSON response. Nothing more, am still in the learning stage.

But no matter which way I try, I cannot successfully send the two parameters in my URLRequest.

In the sample below, you'll see I try to send them in the main url, I've tried to add them as forHTTPHeaderFields, but the response I get back in my URLSessionDataDelegate is always:

data is {"userid":"","username":""} JSON Optional({ userid = ""; username = "";

let file = File(link: "http://example.com/cgi-bin/swift.pl?username=John&userid=01", data: "hello")
uploadService.start(file: file)

And within my instance of URLSession I have tried:

// From one of my view controllers I create a File struct 
// from a YouTube lesson. Eventually I want to send a file. 
// So for now am using just *Hello*:

let uploadTask = UploadTask(file: file)
let url = URL(string: file.link)!
let uploadData = Data(file.data.utf8)
    
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField:"Content-Type")
    
request.addValue("application/json", forHTTPHeaderField: "Accept")
    
request.addValue("John", forHTTPHeaderField: "username")
request.addValue("01", forHTTPHeaderField: "userid")

uploadTask.task = uploadSession.uploadTask(with: request, from: uploadData)
uploadTask.task?.resume()

Every other part of the Swift test works, I get a response and data in my URSessionDelegate, and no errors. Obviously I just can't figure out how to properly send over the two parameters. For the record:

  • the Perl script below does work from a linux command line, or when called from a web browser.

  • If I hardcode the return repsonse in the perl script below, I do recieve it in the my URLSessionDelegate, so I know that I am parsing it correctly

  • As well, my server's error log shows that $header1 and $header2 never get initialized.

#!/usr/bin/perl -w
use strict;
use CGI;
use JSON;
my $q = new CGI;

my $header1 = $q->param("username");
my $header2 = $q->param("userid");

print $q->header('application/json');

my %out = (username=>"$header1", userid=>"$header2");

my $json = encode_json \%out;

print $json;
exit(0);



Solution 1:[1]

You are sending the parameters username and userid as http header values. Your perl scrip is expecting them a query parameters. So first create a URLComponents object, than add query items and finally create your url.

Try this:

let uploadTask = UploadTask(file: file)
var urlComponents = URLComponents(string: file.link)!

let queryItems = [URLQueryItem(name: "username", value: "John"),
                  URLQueryItem(name: "userid", value: "01")]

urlComponents.queryItems = queryItems

let url = urlComponents.url!

var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField:"Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

uploadTask.task = uploadSession.uploadTask(with: request, from: 
uploadData)
uploadTask.task?.resume()

Have a look at this Post that shows how to add query parameters using an extension to URL

Solution 2:[2]

In these two lines:

request.addValue("John", forHTTPHeaderField: "username")
request.addValue("01", forHTTPHeaderField: "userid")

You are adding those as http headers and not url query parameters.

To add query parameters, you need to convert to URLComponents first and then convert back: https://developer.apple.com/documentation/foundation/urlcomponents

var urlComponents = URLComponents(string: file.link)!
urlComponents.queryItems = [
    URLQueryItem(name: "username", value: "name"),
    URLQueryItem(name: "userid", value: "id")
]

let newURL = urlComponents.url!

//use the newURL

Solution 3:[3]

Just create a dictionary with data

let parameterDictionary = ["username" : "John", "userid": "01"]

Then create httpBody object using

guard let httpBody = try? JSONSerialization.data(withJSONObject: parameterDictionary, options: []) else { return }

Then simply add that body in your request parameter

request.httpBody = httpBody

Solution 4:[4]

I finally found the answer here on StackOverflow.

Having no experience in http methods, the short answer to my question is that if I am using "GET", I would use urlComponents.queryItems, but if I am using "POST" then my parameters would have to be in the http body itself.

But more importantly, the answer found in the link explains when and why you should use "GET" as opposed to "POST", and vice-versa.

So to anyone coming across this, definitely read the answer provided in the link.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mcscxv
Solution 2
Solution 3 Hashim Khan
Solution 4