FIGURED IT OUT, See below:
I'm trying to create a program that can pass the contents of a text file to a http POST request in swift. I am storing filters for API queries that I am running in the text files, and would like to pass them as JSON objects(? I think, anyway) to request.httpBody in the request. I'm having problems converting the txt files to something that the httpBody can accept as data (json object?).
Here an example txt file. Filters in the same array are combined using OR logic. Arrays of filters are combined using AND logic, so I have to account for both cases.:
zero_RC.txt
{
"query": "Zero Response Code",
"filters": [
{
"filters": [
{
"field": "inventoryState",
"value": "CONFIRMED",
"type": "IN"
},
{
"field": "responseCode",
"value": "0",
"type": "EQ"
},
{
"field": "exception",
"value": "DNS lookup failed",
"type": "EQ"
}]
}]
}
This is the block that I'm trying to get to work. I believe that I need a JSON object, and can pass that to httpBody in the request below. But, still a beginner at this stuff.
// get JSON, somehow
let file = Bundle.main.path(forResource: "zero_RC", ofType: "txt")
let jsonData = file!.data(using: .utf8)
let JSON = try! JSONSerialization.data(withJSONObject: jsonData as Any, options: [])
if JSONSerialization.isValidJSONObject(JSON) {
print("Oh Yeah")
} else {
print("Nah bud, that ain't working")
}
// make the request
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Basic \(loginData!)", forHTTPHeaderField: "Authorization")
request.httpBody = JSON
So am I taking a string and converting to data, then into a JSON object? I'm thoroughly confused as to how best to do this. I've searched and searched and all I'm finding is parsing articles, which don't exactly help.
Thanks ahead of time.
ANSWERED:
The problem was in the request.setValue. I needed to use Content-Type instead of Accept.
// get JSON
let path = Bundle.main.path(forResource: "zero_RC", ofType: "txt")
let data = try! Data(contentsOf: URL(fileURLWithPath: path!), options: .mappedIfSafe)
// make the request
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Basic \(loginData!)", forHTTPHeaderField: "Authorization")
request.httpBody = data
Here's the solution firstly we will decode your json file into a data model, then will encode that data model object to the httpBody.
let path = Bundle.main.path(forResource: "zero_RC", ofType: "txt")
let data = try! Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonDecoder = JSONDecoder()
let json = try! jsonDecoder.decode(JsonObjModel.self, from: data)
// now encoding that jsonData to the http body
let encoder = JSONEncoder()
urlRequest.httpBody = try! encoder.encode(json)
The 'JsonObjModel' will be the following
// MARK: - JSONObjModel
struct JSONObjModel: Codable {
let query: String
let filters: [JSONObjModelFilter]
}
// MARK: - JSONObjModelFilter
struct JSONObjModelFilter: Codable {
let filters: [FilterFilter]
}
// MARK: - FilterFilter
struct FilterFilter: Codable {
let field, value, type: String
}
Related
I have a working python script that gets cookies from a site. I want to convert it to Swift script, but I do something wrong and can not understand where.
#!/usr/bin/env python
import warnings
import json
import os
import requests
auth_login='someName'
auth_password='somePassword'
passport_params = {'from': 'passport', 'retpath': 'https://192.168.0.1/passport?mode=passport', 'passwd': auth_password, 'login': auth_login, 'display': 'page'}
passport_session = requests.session()
passport_error_passwd = u"wrong pass"
passport_error_login = u"no such users"
print('Connecting to passport...')
try:
passport_request = passport_session.post('https://192.168.0.1/passport?mode=auth', data=passport_params, verify=False)
except:
print('Error connecting')
if os.name == 'nt':
os.system("pause")
exit(1)
else:
print('Authentication successful')
passport_cookies = requests.utils.dict_from_cookiejar(passport_session.cookies)
print('cookies=')
print(passport_cookies)
I am not sure that my post request works correctly, so my cookies are empty.
let auth_login = "someName"
let auth_password = "somePassword"
let url = URL(string: "https://192.168.0.1/passport?mode=auth")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = "POST"
let parameters = [
"from": "passport",
"retpath": "https://192.168.0.1/passport?mode=passport",
"passwd": auth_password,
"login": auth_login,
"display": "page"
]
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard
// let data = data,
let response = response as? HTTPURLResponse,
let fields = response.allHeaderFields as? [String: String]
else {
print("error", error ?? URLError(.badServerResponse))
return
}
let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields, for: url)
print("cookies:\n\(cookies)")
guard (200 ... 299) ~= response.statusCode else {
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}
}
task.resume()
Your python code is sending these:
passport_params = {'from': 'passport', 'retpath': 'https://192.168.0.1/passport?mode=passport', 'passwd': auth_password, 'login': auth_login, 'display': 'page'}
as POST data payload.
While your Swift code
let parameters = [
"from": "passport",
"retpath": "https://192.168.0.1/passport?mode=passport",
"passwd": auth_password,
"login": auth_login,
"display": "page"
]
request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)
is doing that as JSON http body.
So these requests are very different, hence the problem you encounter.
Have a look at this answer how to configure POST data in a request in Swift .
I am using a asp.net back end with a login end point but no matter what I DO in the swift version of this code I get a 415 when I use it in .net and sharp the api works am not sure what am doing wrong here.
And yes I have enabled transport protocol but its not decoding the jwt token correctly for me in swift
Basically the end point returns the jet token used for accessing the api in an object
let jwtAccessToken: String = ""
let urlString = "http://url.com/login" *** hidden for security
purposes but is correct ****
func CallWebApi()
{
// create the url with URL
let url = URL(string: urlString)! // change server url accordingly
let parameters: [String: Any] = [ "username":
"user1#domain.com", "password": "pass1"]
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded",
forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "Post"
do {
request.httpBody = try
JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
} catch let error {
print(error.localizedDescription)
return
}
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
error == nil
else {
// check for fundamental networking error
print("error", error ?? URLError(.badServerResponse))
return
}
guard (200 ... 299) ~= response.statusCode else {
// check for http errors
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}
// do whatever you want with the `data`, e.g.:
do {
let responseObject = data
print(responseObject)
} catch {
print(error)
// parsing error
if let responseString = String(data: data, encoding: .utf8) {
print("responseString = \(responseString)")
} else {
print("unable to parse response as string")
}
}
}
task.resume()
}
MyModel is basically a string
import Foundation
class AuthenticationResponse: ObservableObject {
#Published var jwtToken: String
init(jwtToken: String) {
self.jwtToken = jwtToken
}
}
I think 20 years of c sharp in not helping and am doing things it way and not the swift way if someone could advice be great.
Also in csharp we were told its not great in keeping alive the http client as can degrade performance is this the same for swift and if any library's you can recommend makes the code a bit neater the api has swagger docs enabled.
Edit 3
Example response expected back
{
"id": "b181104e-ba3e-4dba-b124-4bb4a3873b17",
"firstName": "user1",
"lastName": "lastname",
"username": "user1lastname#domainname.com",
"playerId": 0,
"jwtToken": "token in is here",//hidden for security
"error": {
"eventName": null,
"errorMessage": null,
"errorDate": null,
"statusCode": null,
"json": null
},
"refreshToken": null
}
I typically send this to the end point from C sharp
{
"username": "user1#domain.com",
"password": "pass1"
}
What I found I had to do was this
let decoder = JSONDecoder()
let responseObject = try
decoder.decode(AuthenticationResponse.self, from: data)
print(responseObject)
And change my class to be off this
import Foundation
struct AuthenticationResponse: Codable {
var jwtToken: String
}
After I done that I got the expected string back but my question is how does one get this to run correctly its completing before I think I need await but also where is it best to stored the jwttoken?
I don't feel like sharing the actual link as it may have some private information so don't be surprised if it doesn't work.
I have a link that looks like this: www.somelink.com/stuff/searchmembers?member=John
And some headers that I need to pass, like Login: Admin, Password: Admin
When I use this site everything seems to be working just fine, I put the link, make it GET and put headers in key:value format and as a result I get the list of all members, but how can I do the same with URLSession? Here's what I currently have and I don't get anything at all. What am I doing wrong there?
func getAllMembers(urlString: String) {
guard let url = URL(string: urlString) else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Admin", forHTTPHeaderField: "Login")
request.setValue("Admin", forHTTPHeaderField: "Password")
request.httpBody = "member=John".data(using: .utf8)!
URLSession.shared.dataTask(with: request) { (data, response, error) in
print(response)
print(data)
}.resume()
}
Here is What You're Doing Wrong:
Your member=John is actually a URL query parameter. In general, URL requests have query parameters as a part of the URL string itself and not the httpbody.
Quick and Dirty Solution:
You should be good to go if you remove
request.httpBody = "member=John".data(using: .utf8)!
and instead pass the whole "www.somelink.com/stuff/searchmembers?member=John" into your getAllMembers(urlString:) function.
A Better Solution:
Let's say John's username is j o h n. Your function wouldn't make it past that first guard because spaces aren't valid URL string characters.
I like to use URLComponents because it saves me the trouble of having to handle spaces and such.
Here's how I'd write your function:
func getJohnMember(urlString: String) {
//URLComponents to the rescue!
var urlBuilder = URLComponents(string: urlString)
urlBuilder?.queryItems = [
URLQueryItem(name: "member", value: "j o h n")
]
guard let url = urlBuilder?.url else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Admin", forHTTPHeaderField: "Login")
request.setValue("Admin", forHTTPHeaderField: "Password")
URLSession.shared.dataTask(with: request) { (data, response, error) in
print(response)
print(String(data: data, encoding: .utf8)) //Try this too!
}.resume()
}
Just to be clear, I would pass just "www.somelink.com/stuff/searchmembers" into the first parameter.
Now if I were to print(url) after the guard let, I'd get
www.somelink.com/stuff/searchmembers?member=j%20o%20h%20n
Much easier this way, no?
That member=John is a URL-query parameter, not part of the request body. So you need to add it to the URL itself.
func getAllMembers(urlString: String) {
guard let url = URL(string: "\(urlString)?member=John") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Admin", forHTTPHeaderField: "Login")
request.setValue("Admin", forHTTPHeaderField: "Password")
URLSession.shared.dataTask(with: request) { (data, response, error) in
print(response)
print(data)
}.resume()
}
I have a data structure that looks like this in JSON:
[{
"value": "1",
"optionId": "be69fa23-6eca-4e1b-8c78-c01daaa43c88"
}, {
"value": "0",
"optionId": "f75da6a9-a34c-4ff6-8070-0d27792073df"
}]
Basically it is an array of dictionaries. I would prefer to use the default Alamofire methods and would not like to build the request manually. Is there a way to give Alamofire my parameters and Alamofire does the rest?
If I create everything by hand I get an error from the server that the send data would not be correct.
var parameters = [[String:AnyObject]]()
for votingOption in votingOptions{
let type = votingOption.votingHeaders.first?.type
let parameter = ["optionId":votingOption.optionID,
"value": votingOption.votingBoolValue
]
parameters.append(parameter)
}
let jsonData = try! NSJSONSerialization.dataWithJSONObject(parameters, options: [])
let json = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments)
if let url = NSURL(string:"myprivateurl"){
let request = NSMutableURLRequest(URL: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.HTTPMethod = Method.POST.rawValue
request.HTTPBody = try! NSJSONSerialization.dataWithJSONObject(parameters, options: [])
AlamofireManager.Configured.request(request)
.responseJSON { response in
//Handle result
}
}
I have the same issue and resolved this way:
I created a new struct implementing the Alamofire's ParameterEncoding protocol:
struct JSONArrayEncoding: ParameterEncoding {
private let array: [Parameters]
init(array: [Parameters]) {
self.array = array
}
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
let data = try JSONSerialization.data(withJSONObject: array, options: [])
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
return urlRequest
}
}
Then, I can do this:
let parameters : [Parameters] = bodies.map( { $0.bodyDictionary() })
Alamofire.request(url, method: .post, encoding: JSONArrayEncoding(array: parameters), headers: headers).responseArray { ... }
It worked for me. Hope can help someone else.
You can do something like this:
Alamofire.request(.POST, urlPath, parameters: params).responseJSON{ request, response, data in
//YOUR_CODE
}
Where parameters is [String:AnyObject] and yes Alamofire takes care of the rest.
Since it looks like you are using a manager you can do this
YOUR_ALAMOFIRE_MANAGER.request(.POST, url, parameters: params).responseJSON{ request, response, JSON in
//enter code here
}
Here is the source code:
public func request(
method: Method,
_ URLString: URLStringConvertible,
parameters: [String: AnyObject]? = nil,
encoding: ParameterEncoding = .URL,
headers: [String: String]? = nil)
-> Request
{
let mutableURLRequest = URLRequest(method, URLString, headers: headers)
let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0
return request(encodedURLRequest)
}
EDIT:
Since your data is currently [[String:AnyObject]] you will need to modify it so it is in the form [String:AnyObject]. One way you could do this i by doing this ["data":[[String:AnyObject]]]. You will probably have to change your api end point though.
You can provide parameter encoding for JSON POST request and it will send the data as JSON in request body.
Alamofire.request(.POST, "https://httpbin.org/post", parameters: parameters, encoding: .JSON)
This is described in the ReadMe file of Alamofire github - https://github.com/Alamofire/Alamofire#post-request-with-json-encoded-parameters
let parameters = [
"foo": [1,2,3],
"bar": [
"baz": "qux"
]
]
Alamofire.request(.POST, "https://httpbin.org/post", parameters: parameters, encoding: .JSON)
// HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}}
The Locu API provides this example using CURL to perform a location sensitive query:
curl -X POST https://api.locu.com/v2/venue/search -d '{
"api_key" : "f165c0e560d0700288c2f70cf6b26e0c2de0348f",
"fields" : [ "name", "location", "contact" ],
"venue_queries" : [
{
"location" : {
"geo" : {
"$in_lat_lng_radius" : [-37.7750, 122.4183, 5000]
}
}
}]
}'
This is my attempt in Swift:
let LOCU_API_KEY = "<API_KEY>"
let centerLatitude = mapView.region.center.latitude
let centerLongitude = mapView.region.center.longitude
let arr = [centerLatitude,centerLongitude,5000]
let url = NSURL(string: "https://api.locu.com/v2/venue/search")
var request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "POST"
var params = ["api_key":LOCU_API_KEY,"fields":["name","location","contact"], "venue_queries":[["location":["geo":["$in_lat_lng_radius":arr]]]]] as [String:AnyObject!]
var err: NSError?
NSJSONSerialization.dataWithJSONObject(params, options: nil, error: &err)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
if error == nil {
var err:NSError?
let httpResponse = response as NSHTTPURLResponse!
println(response.description)
if httpResponse.statusCode == 200 {
if var json = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error:&err) as? NSDictionary {
println(json)
}
}
}
})
I only get an HTTP 400 - Bad request for this and various simplifications, like just providing the api_key. Version 1_0 of the Locu works fine, although it doesn't have the features I need.
afRequestOperationManager.GET is performing a GET request rather than a POST request.
Furthermore, the request data must be in JSON, whereas you are using URL parameters.
As others have said you need to use .POST or in someway do a POST request not a GET.
Also, it looks like this line:
let urlString = "https://api.locu.com/v2/venue/"
Should be:
let urlString = "https://api.locu.com/v2/venue/search"
Right? Notice the "search" at the end. That is why you are getting 400 I assume (404 I guess?!).
Let us know if it worked.
The '-X POST part of the curl command means you need to do an HTTP POST, not an HTTP GET.
Try using afRequestOperationManager.POST instead.