(Opgelost) HTTP Request Vanuit Swift App

Hallo,

Ik werk op dit moment aan een iOS app in Swift die moet communiceren met een Apache web server. Nadat ik de SSL certificaten die voor een beveiligde verbinding nodig zijn op de server geplaatst had, lukte het om met behulp van NSData(contentsOfURL: url!) data van een PHP pagina op te vragen. Echter, ik moet data naar het PHP script kunnen sturen via de HTTP Post methode, hiervoor is dan ook een NSURLSession nodig. Wanneer ik dit echter probeer krijg ik een statuscode 403 terug. De app lijkt dus geen toegang te krijgen tot de pagina, waar een web browser dit wel krijgt.

Heeft iemand enig idee waar dit aan zou kunnen liggen?

Als extra informatie of code nodig is kan ik dit natuurlijk plaatsen.

Vraag je wel een geldige pagina op?

https://en.wikipedia.org/wiki/HTTP_403

De pagina is geldig, dezelfde pagina gaf met contentsOfURL gewoon een response terug, enkel bij de NSURLSession verschijnt de 403 code.

Heb je App Transfer Security geactiveerd?

http://stackoverflow.com/questions/30739473/nsurlsession-nsurlconnection-http-load-failed-on-ios-9

Voor zover ik weet is App Transport Security in iOS 9 altijd geactiveerd, dat is de reden dat SSL certificaten / HTTPS nodig zijn. Een reguliere, onbeveiligde HTTP verbinding word door iOS tegengehouden. Het gaat hier echter om een 403 error, deze vind plaats aan de kant van de server, met andere woorden, de server staat geen requests van de app toe. Of in ieder geval, dat is wat ik ervan begrijp.

403 is een fout die de server geeft wanneer de cliënt geen toestemming heeft om de opgevraagde pagina te zien. Je krijgt dit bijvoorbeeld wanneer je een bestand opvraagt dat wel bestaat, maar waarop de server geen leesrechten heeft. Volgens Wikipedia:

Het klopt dat een 403 een dergelijke fout is. Zoals in mijn verhaal stond kan de pagina echter wel met een webbrowser en ook met ContentOfURL bezocht worden. Waarschijnlijk zie ik dan ook iets over het hoofd in mijn HTTP request, ontbrekende waarden of iets dergelijks, waardoor de server de verbinding niet toestaat en een 403 geeft. Vraag is wat. Dit is overigens de pagina in kwestie.

Ik krijg dit te zien:

Dank, blijkbaar is er dan toch iets niet in orde met het SSL certificaat. Ik zal dat probleem oplossen en kijken of mijn probleem daarmee is opgelost.

EDIT: ik had een fout gemaakt in de geplaatste URL, vergeten WWW toe te voegen waardoor het domein en common names in het certificaat niet overeenkwamen, ik heb de link in de vorige post aangepast, deze is als het goed is correct. Het probleem met xCode blijft aanwezig.

Nu zie ik {“login” : “denied”} .

Bedankt voor het proberen, dat is inderdaad correcte output, de pagina kan via een post request een gebruikersnaam en wachtwoord ontvangen, en geeft denied als deze incorrect is of ontbreekt. Het probleem is dus dat het sturen van die posts requests niet lukt vanuit mijn Swift app.

Hieronder heb ik een stuk code die ik zelf gebruik.
Je zal wel het een en ander aan moeten passen maar misschien dat je er iets aan hebt.

/*-------------------------------------------------------------------------
Author:
Date:
Description:

- JSONManager: is a class which does handle a url request for json data

-------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------
:IMPORT
-------------------------------------------------------------------------*/
#if os(OSX)
import Cocoa
#elseif os(iOS)
import UIKit
#elseif os(watchOS)
import UIKit
#endif
import Foundation

//TODO READ: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW44

/*-------------------------------------------------------------------------
:DEFINITIONS
-------------------------------------------------------------------------*/

public enum JSONRequestMethod:String {
    case POST = "POST",
    GET = "GET",
    PUT = "PUT",
    DELETE = "DELETE"
}

/**
 JSONErrorType
 - note: Error type to indicates the kind of error
 */
public enum JSONErrorType:Int {
    /** - parameter invalidURL: This error message is used when an url is invalid */
    case invalidURL = 10,
    /** - parameter invalidData: This error message is used when the data is invalid */
    invalidData = 11
}

/*-------------------------------------------------------------------------
:PROTOCOL
-------------------------------------------------------------------------*/


protocol JSONDelegate {
    
    func jsonDidFinish(jsonManager:JSONManager)
    func jsonAuthorization(jsonManager:JSONManager, completion:(username:String, password:String, cancelled:Bool)-> Void)
    
}

/*-------------------------------------------------------------------------
:CLASS
-------------------------------------------------------------------------*/

/**
JSONManager
- note: Manages the url requests for json data
*/

public class JSONManager:NSObject, NSURLSessionDelegate {
    
    /*--------------------------ATTRIBUTES-----------------------------------*/
    
    var delegate:JSONDelegate?
    
    private var openAuthorizationRequest:Bool = false
    
    /*--------------------------INIT/DEINIT------------------------------*/
    /*---------------------------METHODS----------------------------------*/
    
    
    /**
    convertToValidWikiUrlString
    */
    private func convertToValidWikiUrlString(string string:String)->String {
        var result = string
        result = result.stringByRemovingPercentEncoding!
        result = result.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
        return result
    }
    
    /**
     request
     */
    func request(baseurl baseurl:String, method:JSONRequestMethod, query:String, completion:(data:AnyObject?, error:NSError?, response:NSURLResponse?) -> Void) {
        
        let url = convertToValidWikiUrlString(string: baseurl)
        let request = NSMutableURLRequest(URL: NSURL(string: url)!, cachePolicy: NSURLRequestCachePolicy.UseProtocolCachePolicy, timeoutInterval: 60)
        
        if method == .GET {
            
            let convertedUrl = convertToValidWikiUrlString(string: baseurl)
            let url = NSURL(string: convertedUrl);
            
            if url == nil {
                /* urlString is not a valid url */
                completion(data: nil, error: NSError(domain: String(JSONErrorType.invalidURL), code: Int(JSONErrorType.invalidURL.rawValue), userInfo: nil), response: nil)
                return
            }
            
        } else {
            
            let postData = query.dataUsingEncoding(NSUTF8StringEncoding)
            let postLength = postData?.length
            
            request.HTTPMethod = method.rawValue
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            request.addValue("application/json", forHTTPHeaderField: "Accept")
            request.setValue("\(postLength)", forHTTPHeaderField: "Content-Length")
            
        }
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
        
        let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
        
            /* request completed, process the data */
            if error != nil {
                /* unable to perform the request */
                completion(data: nil, error: error, response: response)
                return
            }
            
            /* encode the data */
            let json = NSString(data: data!, encoding: NSASCIIStringEncoding)
            
            if json == nil {
                /* unable to encode the data, invalid data */
                completion(data: nil, error: NSError(domain: String(JSONErrorType.invalidData), code: Int(JSONErrorType.invalidData.rawValue), userInfo: nil), response: response)
                return
            }
            
            /* set the options for json serialization */
            let options = NSJSONReadingOptions.AllowFragments
            
            do {
                
                /* serialization */
                let results = try NSJSONSerialization.JSONObjectWithData(data!, options: options)
                
                /* return the results (which is a dictionary) to the completion block */
                completion(data: results, error: nil, response: response)
                return
                
                
            } catch {
       
                /* json serialization has failed, the data is invalid */
                completion(data: nil, error: NSError(domain: String(JSONErrorType.invalidData), code: Int(JSONErrorType.invalidData.rawValue), userInfo: nil), response: response)
                return
                
            }
            
        }
        
        task.resume()
        
    }
    
    func request(url:String, method:JSONRequestMethod, query:String = "") {
        
        request(baseurl: url, method: method, query: query) { (data, error, response) -> Void in
            
            if error != nil {
                self.delegate?.jsonDidFinish(self)
                return
            }
            
            self.delegate?.jsonDidFinish(self)
            
        }
        
    }
    
    /*--------------------------DELEGATES------------------------------------*/
    
    func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didReceiveChallenge challenge: NSURLAuthenticationChallenge!, completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)!){
        
        let authorizeMethod = challenge.protectionSpace.authenticationMethod
        
        if authorizeMethod == NSURLAuthenticationMethodDefault {
            
            delegate?.jsonAuthorization(self, completion: { (username, password, cancelled) -> Void in
                
                if !cancelled {
                    let credentials = NSURLCredential(user: username, password: password, persistence: .Permanent)
                    challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
                    completionHandler(.UseCredential, credentials)
                }
                
            })
            
        }
        
        if authorizeMethod == NSURLAuthenticationMethodClientCertificate {
            Debug.console("NSURLAuthenticationMethodClientCertificate")
        }

        if authorizeMethod == NSURLAuthenticationMethodHTMLForm {
            Debug.console("NSURLAuthenticationMethodHTMLForm")
        }

        if authorizeMethod == NSURLAuthenticationMethodNegotiate {
            Debug.console("NSURLAuthenticationMethodNegotiate")
        }

        if authorizeMethod == NSURLAuthenticationMethodNTLM {
            Debug.console("NSURLAuthenticationMethodNTLM")
        }

        if authorizeMethod == NSURLAuthenticationMethodServerTrust {
            Debug.console("NSURLAuthenticationMethodServerTrust")
        }
        
        if authorizeMethod == NSURLAuthenticationMethodHTTPDigest {

            delegate?.jsonAuthorization(self, completion: { (username, password, cancelled) -> Void in
                
                if !cancelled {
                    let credentials = NSURLCredential(user: username, password: password, persistence: .Permanent)
                    challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
                    completionHandler(.UseCredential, credentials)
                }
                
            })

        }
        
        if authorizeMethod == NSURLAuthenticationMethodHTTPBasic {
            
            delegate?.jsonAuthorization(self, completion: { (username, password, cancelled) -> Void in
                
                if !cancelled {
                    let credentials = NSURLCredential(user: username, password: password, persistence: .Permanent)
                    challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
                    completionHandler(.UseCredential, credentials)
                }
                
            })
            
        }
        
        if authorizeMethod == NSURLAuthenticationMethodHTTPDigest {
            Debug.console(NSURLAuthenticationMethodHTTPDigest)
            
        }
        
        if authorizeMethod == NSURLAuthenticationMethodHTMLForm {
            Debug.console(NSURLAuthenticationMethodHTMLForm)
        }
        
        if authorizeMethod == NSURLAuthenticationMethodNTLM {
            Debug.console(NSURLAuthenticationMethodNTLM)
        }
        
        if authorizeMethod == NSURLAuthenticationMethodNegotiate {
            Debug.console(NSURLAuthenticationMethodNegotiate)
        }
        
        if authorizeMethod == NSURLAuthenticationMethodClientCertificate {
            Debug.console(NSURLAuthenticationMethodClientCertificate)
        }
        
        if authorizeMethod == NSURLAuthenticationMethodServerTrust {
            
            let credentials = NSURLCredential(trust: challenge.protectionSpace.serverTrust!)
            challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
            completionHandler(.UseCredential, credentials)
            
        }
        
    }
    
    public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
        
        let authorizeMethod = challenge.protectionSpace.authenticationMethod
        
        if authorizeMethod == NSURLAuthenticationMethodDefault {
            
            delegate?.jsonAuthorization(self, completion: { (username, password, cancelled) -> Void in
                
                if !cancelled {
                    let credentials = NSURLCredential(user: username, password: password, persistence: .Permanent)
                    challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
                    completionHandler(.UseCredential, credentials)
                }
                
            })
            
        }
        
        if authorizeMethod == NSURLAuthenticationMethodClientCertificate {
            Debug.console("NSURLAuthenticationMethodClientCertificate")
        }

        if authorizeMethod == NSURLAuthenticationMethodHTMLForm {
            Debug.console("NSURLAuthenticationMethodHTMLForm")
        }

        if authorizeMethod == NSURLAuthenticationMethodNegotiate {
            Debug.console("NSURLAuthenticationMethodNegotiate")
        }

        if authorizeMethod == NSURLAuthenticationMethodNTLM {
            Debug.console("NSURLAuthenticationMethodNTLM")
        }

        if authorizeMethod == NSURLAuthenticationMethodServerTrust {
            Debug.console("NSURLAuthenticationMethodServerTrust")
        }
        
        if authorizeMethod == NSURLAuthenticationMethodHTTPDigest {

            delegate?.jsonAuthorization(self, completion: { (username, password, cancelled) -> Void in
                
                if !cancelled {
                    let credentials = NSURLCredential(user: username, password: password, persistence: .Permanent)
                    challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
                    completionHandler(.UseCredential, credentials)
                }
                
            })

        }
        
        if authorizeMethod == NSURLAuthenticationMethodHTTPBasic {
            
            delegate?.jsonAuthorization(self, completion: { (username, password, cancelled) -> Void in
                
                if !cancelled {
                    let credentials = NSURLCredential(user: username, password: password, persistence: .Permanent)
                    challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
                    completionHandler(.UseCredential, credentials)
                }
                
            })
            
        }
        
        if authorizeMethod == NSURLAuthenticationMethodHTTPDigest {
            Debug.console(NSURLAuthenticationMethodHTTPDigest)
            
        }
        
        if authorizeMethod == NSURLAuthenticationMethodHTMLForm {
            Debug.console(NSURLAuthenticationMethodHTMLForm)
        }
        
        if authorizeMethod == NSURLAuthenticationMethodNTLM {
            Debug.console(NSURLAuthenticationMethodNTLM)
        }
        
        if authorizeMethod == NSURLAuthenticationMethodNegotiate {
            Debug.console(NSURLAuthenticationMethodNegotiate)
        }
        
        if authorizeMethod == NSURLAuthenticationMethodClientCertificate {
            Debug.console(NSURLAuthenticationMethodClientCertificate)
        }
        
        if authorizeMethod == NSURLAuthenticationMethodServerTrust {
            
            let credentials = NSURLCredential(trust: challenge.protectionSpace.serverTrust!)
            challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
            completionHandler(.UseCredential, credentials)
            
        }
        
    }
    
    public func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) {
        Debug.console("\(error)")
    }
    
    public func URLSessionDidFinishEventsForBackgroundURLSession(session: NSURLSession) {
        
    }
    
    
}

Als je op de link klikt voer je een get-request uit, terwijl je een post-request wil uitvoeren voor inloggen.

 func jsonAuthorization(jsonManager:JSONManager, completion:(username:String, password:String, cancelled:Bool)-> Void)

Nog ter aanvulling op mijn code, hier heb ik een protocol opgesteld met de hierboven genoemde delegatie.
Wanneer je website een autorisatie vereist (http://php.net/manual/en/features.http-auth.php)
Dan wordt dit afgehandeld via “didReceiveChallenge” in mijn opstelling NSURLAuthenticationMethodHTTPBasic delegeer ik hier de jsonAuthorization.
De completion block is bedoeld voor het meegeven van gebruikersnaam/wachtwoord (bijv. via een inlog scherm).

Het kan zijn dat je een ander authenciation methode toepast, in dat geval moet je de “didReceiveChallenge” ook aanpassen.

Dank voor de uitleg, ik zal er vanavond mee aan de slag, hopen dat het lukt.

Dat zijn wel een heleboel if-jes in die code :slight_smile:

@JoelCoster: Schrijf je je eigen NSURLSession code? Misschien kun je het eens proberen met AlamoFire.

Ik schrijf momenteel inderdaad de code voor de NSURLSession zelf, als ik er niet uitkom met die code die hierboven geplaatst is zal ik AlamoFire eens goed gaan bekijken.

@koen,

Dit zijn maar een paar if-jes en die had ik er nog in staan ivm debuggen en afstemmen op mijn REST-like database.
Wat niet van belang is kan je altijd verwijderen. De code is met wat aanpassingen i.i.g. geschikt om http(s) post/get/put/delete requests uit te voeren en json data te versturen of binnen te halen.

Het probleem is opgelost door een combinatie van twee dingen; allereerst de toevoeging van de volgende twee regels uit het script van @defores:

request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

Na het toevoegen van deze regels werkten GET requests opeens wel met mijn session code, eerst lukte dit alleen met NSData(contentsOfURL: url!)
POST requests werkten echter nog steeds niet, hierbij bleef de 403 error verschijnen. Uiteindelijk bleek het probleem te zijn dat mijn host Mod Security standaard aan heeft staan voor alle domeinen, na dit uitgeschakeld te hebben werkten ook POST requests. Nogmaals bedankt voor het delen van de code @defores, zonder bovenstaande regels was het niet gelukt dit probleem op te lossen.

Graag gedaan!

Zelf liep ik in het begin er ook op vast (.htaccess), zelf heb ik dit iets in de trend van.

RewriteCond %{REQUEST_URI} ^(.)$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .
service/api.php?query=%1&%{QUERY_STRING} [QSA,L]

Daarbij is het altijd handig om de kennis zelf in huis te hebben i.p.v. te grijpen naar third-party oplossingen.