How to Make a Https Request to a Server in Swift

How to make a HTTPS request to a server in swift?

I am using the iOS native library. You can use the following function for connection and server certificate and client certificate authentication:

func ConnectionRequest(jsonString:NSDictionary, callback: (NSDictionary, String!) -> Void) {
let request = NSMutableURLRequest(URL: NSURL(string: "https://example.com:9222")!)

var result = NSDictionary()

do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(jsonString, options: [])
} catch{
request.HTTPBody = nil
}
request.timeoutInterval = 20.0 //(number as! NSTimeInterval)
request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("gzip", forHTTPHeaderField: "Accept-encoding")

let configuration =
NSURLSessionConfiguration.defaultSessionConfiguration()

let session = NSURLSession(configuration: configuration,
delegate: self,
delegateQueue:NSOperationQueue.mainQueue())
print("--------------------------------NSURLSession Request-------------------------------------------------->:\n \(jsonString)")
print(NSDate())


let task = session.dataTaskWithRequest(request){
(data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in

if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode != 200 {
print("response was not 200: \(response)")
return
}
else
{
print("response was 200: \(response)")

print("Data for 200: \(data)")

// In the callback you can return the data/response
callback(data, nil)
return
}
}
if (error != nil) {
print("error request:\n \(error)")
//Here you can return the error and handle it accordingly
return
}
}
task.resume()
}

Following are the code changes which work fine with Self Signed SSL certificate

func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {

if challenge.protectionSpace.authenticationMethod == (NSURLAuthenticationMethodServerTrust) {


let serverTrust:SecTrustRef = challenge.protectionSpace.serverTrust!
let certificate: SecCertificateRef = SecTrustGetCertificateAtIndex(serverTrust, 0)!
let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
let cerPath: String = NSBundle.mainBundle().pathForResource("example.com", ofType: "cer")!
let localCertificateData = NSData(contentsOfFile:cerPath)!


if (remoteCertificateData.isEqualToData(localCertificateData) == true) {
let credential:NSURLCredential = NSURLCredential(forTrust: serverTrust)

challenge.sender?.useCredential(credential, forAuthenticationChallenge: challenge)


completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))

} else {

completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge, nil)
}
}
else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate
{

let path: String = NSBundle.mainBundle().pathForResource("client", ofType: "p12")!
let PKCS12Data = NSData(contentsOfFile:path)!


let identityAndTrust:IdentityAndTrust = self.extractIdentity(PKCS12Data);



let urlCredential:NSURLCredential = NSURLCredential(
identity: identityAndTrust.identityRef,
certificates: identityAndTrust.certArray as? [AnyObject],
persistence: NSURLCredentialPersistence.ForSession);

completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, urlCredential);




}
else
{
completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge, nil);
}
}

struct IdentityAndTrust {

var identityRef:SecIdentityRef
var trust:SecTrustRef
var certArray:AnyObject
}

func extractIdentity(certData:NSData) -> IdentityAndTrust {
var identityAndTrust:IdentityAndTrust!
var securityError:OSStatus = errSecSuccess

let path: String = NSBundle.mainBundle().pathForResource("client", ofType: "p12")!
let PKCS12Data = NSData(contentsOfFile:path)!
let key : NSString = kSecImportExportPassphrase as NSString
let options : NSDictionary = [key : "xyz"]
//create variable for holding security information
//var privateKeyRef: SecKeyRef? = nil

var items : CFArray?

securityError = SecPKCS12Import(PKCS12Data, options, &items)

if securityError == errSecSuccess {
let certItems:CFArray = items as CFArray!;
let certItemsArray:Array = certItems as Array
let dict:AnyObject? = certItemsArray.first;
if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> {

// grab the identity
let identityPointer:AnyObject? = certEntry["identity"];
let secIdentityRef:SecIdentityRef = identityPointer as! SecIdentityRef!;
print("\(identityPointer) :::: \(secIdentityRef)")
// grab the trust
let trustPointer:AnyObject? = certEntry["trust"];
let trustRef:SecTrustRef = trustPointer as! SecTrustRef;
print("\(trustPointer) :::: \(trustRef)")
// grab the cert
let chainPointer:AnyObject? = certEntry["chain"];
identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: chainPointer!);
}
}
return identityAndTrust;
}

Changes done in the info.plist file

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>amazonaws.com.cn</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSThirdPartyExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
<key>amazonaws.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSThirdPartyExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
</dict>
<key>xyz.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSRequiresCertificateTransparency</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
</plist>

Hope this will be helpful.

How to make HTTP request in Swift?

You can use URL, URLRequest and URLSession or NSURLConnection as you'd normally do in Objective-C. Note that for iOS 7.0 and later, URLSession is preferred.

Using URLSession

Initialize a URL object and a URLSessionDataTask from URLSession. Then run the task with resume().

let url = URL(string: "http://www.stackoverflow.com")!

let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}

task.resume()

Using NSURLConnection

First, initialize a URL and a URLRequest:

let url = URL(string: "http://www.stackoverflow.com")!
var request = URLRequest(url: url)
request.httpMethod = "POST"

Then, you can load the request asynchronously with:

NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main) {(response, data, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
}

Or you can initialize an NSURLConnection:

let connection = NSURLConnection(request: request, delegate:nil, startImmediately: true)

Just make sure to set your delegate to something other than nil and use the delegate methods to work with the response and data received.

For more detail, check the documentation for the NSURLConnectionDataDelegate protocol

Testing on an Xcode playground

If you want to try this code on a Xcode playground, add import PlaygroundSupport to your playground, as well as the following call:

PlaygroundPage.current.needsIndefiniteExecution = true

This will allow you to use asynchronous code in playgrounds.

Making HTTP GET request with Swift 5

Right now, if there is an error, you are going to silently fail. So add some error logging, e.g.,

func httpRequest() {
let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http

let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
error == nil,
let data = data,
let string = String(data: data, encoding: .utf8)
else {
print(error ?? "Unknown error")
return
}

print(string)
}
task.resume()
}

That should at least give you some indication of the problem.

A few other considerations:

  1. If command line app, you have to recognize that the app may quit before this asynchronous network request finishes. One would generally start up a RunLoop, looping with run(mode:before:) until the network request finishes, as advised in the run documentation.

    For example, you might give that routine a completion handler that will be called on the main thread when it is done. Then you can use that:

    func httpRequest(completion: @escaping () -> Void) {
    let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http

    let task = URLSession.shared.dataTask(with: url) { data, response, error in
    defer {
    DispatchQueue.main.async {
    completion()
    }
    }

    guard
    error == nil,
    let data = data,
    let string = String(data: data, encoding: .utf8)
    else {
    print(error ?? "Unknown error")
    return
    }

    print(string)
    }
    task.resume()
    }

    var finished = false

    httpRequest {
    finished = true
    }

    while !finished {
    RunLoop.current.run(mode: .default, before: .distantFuture)
    }
  2. In standard macOS apps, you have to enable outgoing (client) connections in the “App Sandbox” capabilities.

  3. If playground, you have to set needsIndefiniteExecution.

  4. By default, macOS and iOS apps disable http requests unless you enable "Allow Arbitrary Loads” in your Info.plist. That is not applicable to command line apps, but you should be aware of that should you try to do this in standard macOS/iOS apps.

    In this case, you should just use https and avoid that consideration altogether.



Related Topics



Leave a reply



Submit