0

I develop an iOS app using Swift.

I use the following method below to access an login ENDPOINT with PUT method.

    let loginData = LoginModel("myUser","myPassword")
    var loginClassJson:Data?
    do{
        loginClassJson =  try JSONEncoder().encode(loginData)
    } catch {
         fatalError("Unable To Convert in Json")
    }
            
    let completeUrl = URL(string: RESconstantes.URL_PRINCIPAL_TREINAGEDAVE + "/login" )!
    var request = URLRequest(url: completeUrl)
    
    let myConfig = URLSessionConfiguration.default
    let base64LoginString = EndpointController.getBase64StringLoginWithUserAndPasswordV2()
    myConfig.httpAdditionalHeaders = ["Authorization" : base64LoginString]
    
    request.httpMethod = "PUT"
    request.setValue("\(String(describing: loginClassJson!.count))", forHTTPHeaderField: "Content-Length")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = loginClassJson
            
    let sessionDelegate = SessionDelegate()
    let urlSession = URLSession(configuration: myConfig, delegate: sessionDelegate, delegateQueue: OperationQueue.main)
    
    let task = urlSession.dataTask(with: request as URLRequest, completionHandler: {
        (data, response, error) in
        
        if let error = error{
            print("errorX: ")
            print(error)
            return
        }
        if let data = data{
            let returnData = String(data: data, encoding: String.Encoding.ascii)
            print("dataX: ")
            print(returnData)
        }
        if let response = response{
            print("responseX: ")
            print(response)
        }
        
    })
    task.resume()
    print("END")

This is my URLSessionDelegate class

class SessionDelegate:NSObject, URLSessionDelegate
{
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    // usado para fazer o bypass na autenticação self-signed do certificado do servidor
    // We've got a URLAuthenticationChallenge - we simply trust the HTTPS server and we proceed
    print("start didReceive challenge 1")
    if true {
        print("didReceive challenge 2")
        completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
    }else{
        completionHandler(.performDefaultHandling, nil)
    }
}

It works perfectly for me, but now I try to create a code to access another ENDPOINT with POST method

let resDadoModel = ResDadoModel.getResenhaById(1)
    let jsonRequestUploadResenha = ResDadoModel.createMockJsonObjectResenhaDados(resDadoModel)
    let json: [String: Any] = jsonRequestUploadResenha
    guard let jsonData:Data = try? JSONSerialization.data(withJSONObject: json) else {
        print("guard jsonData error")
        return
    }        
    let completeUrl = URL(string: RESconstantes.URL_PRINCIPAL_TREINAGEDAVE + "/validaResenha" )!
    var request = URLRequest(url: completeUrl)
    
    let myConfig = URLSessionConfiguration.default
    let base64LoginString = EndpointController.getBase64StringLoginWithUserAndPasswordV2()
    myConfig.httpAdditionalHeaders = ["Authorization" : base64LoginString, "Content-Type":""]
    
    request.httpMethod = "POST"
    request.setValue("\(String(describing: jsonData.count))", forHTTPHeaderField: "Content-Length")
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = jsonData
            
    let sessionDelegate = SessionDelegate()
    let urlSession = URLSession(configuration: myConfig, delegate: sessionDelegate, delegateQueue: OperationQueue.main)
    
    let task = urlSession.dataTask(with: request as URLRequest, completionHandler: {
        (data, response, error) in
        
        if let error = error{
            print("errorX: ")
            print(error)
            return
        }
        if let data = data{
            let returnData = String(data: data, encoding: String.Encoding.ascii)
            print("dataX: ")
            print(returnData)
        }
        if let response = response{
            print("responseX: ")
            print(response)
        }            
    })
    task.resume()
    print("END")

But the code that I use to access the ENDPOINT called "validaResenha" is not working properly, I get a 405 method not allowed error.

I get the following response data

<NSHTTPURLResponse: 0x600002028560> { URL: https://my_url_endpoint/api/spservicos/v1/validaResenha } { Status Code: 405, Headers {
Allow =     (
    "POST, OPTIONS"
);
"Cache-Control" =     (
    "no-cache=\"set-cookie, set-cookie2\""
);
Connection =     (
    "Keep-Alive"
);
"Content-Language" =     (
    "en-US"
);
"Content-Length" =     (
    0
);
"Content-Type" =     (
    "text/plain"
);
Date =     (
    "Thu, 23 Dec 2021 23:16:21 GMT"
);
Expires =     (
    "Thu, 01 Dec 1994 16:00:00 GMT"
);
"Keep-Alive" =     (
    "timeout=10, max=100"
);
"Set-Cookie" =     (
    "LtpaToken2=L6DEf/sqCSjiI1rePW3wEWZo40oNAsxmNVBNTpIRm3FZZRSSgaqmUTDYdjTq2PNE4+FhiIOKw7Xzuta4+LpD3cUB8QKZQ/KVom/rFFQ50XNkpQezmgMlgsmDDgtodRxVU5eyo1P1NP6r/3M55eY4HkeD583kXQB3/+EH3dIryo0ii6Jn6PrxaspX5noEo0eSt+yF2AylLdU66fCcSMJw7LCrB8Tulna4xHe4Nb9i+O5z2mnTXoIgbozDGuXfS6Y20zPrsaN62Bx1X/nySf1luf1QMhrt6P4SPF6GVudm0s/Db9dS0b444kJA4kMSJ0NbZ2khMzV1zSg3eZY6xZg2kidV8Qczpe5bL2/DNrPQY/CrUo8wcdFE1ebfxDcVrjv3G+nH6uKOPWtbcHHx9Wp1gvHLxj3cJ5MP43AzxW/7GXPA7QlsmlquxW1Ck7OypsP2hrYCvCWubjGdM51cg8uqhIonI+uXRO6BlcXIsPOfpR+LbQfDNo+9vzXzB+CZKZmYnBX63ffWhX09Cr+Ua0a2Sw8mOcE5jXImlO49+ak0FHPkiiaSABzuOl6ALYg9J6LCxjm6MC9bKd7KbMPueJI/ugVeMyphQwss5AHxic8fVmo+7/XNRT6zr4I/01N8xFQsqrvx5+i2AhxWO1bdDKmpZQLPoTHMD7TPcFBkwDXLVqXPXkpkcGvg3mI8ssKOOlxwJT7/SETcqrCY5O8Yr505qdeZiNIj4kjKiLoLuNpE+ZI=; Path=/"
);
} }

Anyone has an idea why I was receiving a 405 error method not allowed? The POST method works for me if I use POSTMAN. It works if I use PUT or GET endpoints with Swift code, but it fails if I try to use endpoints with POST method in Swift.

I see something strange, the "Content-Type" is defined as text/plain in the response, but I set it as "application/json". I don't understand why the config was not being set.

If I call it via POSTMAN, it works, but for some reason I don't know why it not works when I use Swift.

--- EDIT ---

After @matt suggestion, I use Postman to generate the Swift code.

I copy and paste the Swift code to my project, this is the code:

var semaphore = DispatchSemaphore (value: 0)

    let parameters = "{ \n    \"token\":\"MY_TOKEN\",\n    \"resenha\": {\n        \"codAP\":\"353750303020001\",\n        \"codPropriedade\":\"0\",\n        \"cpfVeterinario\":\"01787568814\",\n        \"coordGeoLat\": \"37.421565\",\n        \"coordGeoLong\": \"-122.084\",\n        \"cpfCnpjProdutor\": \"89058500810\",\n        \"dataNascimentoAnimal\": \"01/08/1981\",\n        \"fotos\": null,\n        \"graficas\": null,\n        \"id\": \"1\",\n        \"idComposicaoPelagem\": \"50\",\n        \"idCorOlhoDir\": \"39902\",\n        \"idCorOlhoEsq\": \"39902\",\n        \"idEspecie\": \"5\",\n        \"idPelagem\": \"6\",\n        \"idRaca\": \"34\",\n        \"idResenhaAnterior\":\"0\",\n        \"idSexo\": \"2501\",\n        \"machoCastrado\": \"N\",\n        \"microChipAnimal\": \"123456989012377\",\n        \"microchipMae\": \"\",\n        \"nomeAnimal\": \"MACADANIAS111\",\n        \"numeroAssocRaca\": \"\",\n        \"numeroPassaporte\": \"\",\n        \"outrasCaracteristicas\": null,\n        \"quantAnimaisEq\": \"05\",\n        \"quantAnimaisAs\": \"0\",\n        \"quantAnimaisMu\": \"02\",\n        \"retifica\": false\n    }\n}"
    let postData = parameters.data(using: .utf8)

    var request = URLRequest(url: URL(string: "https://MY_ENDPOINT/validaResenha")!,timeoutInterval: Double.infinity)
    request.addValue("Basic THIS_IS_BASIC_AUTH_VALIDATION", forHTTPHeaderField: "Authorization")
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("JSESSIONID=0000_B1PZRSVcyzEkDUkMxvk9ig:18jgnbg8n; LtpaToken2=q9JsIHVBKPsCYKGRohXJAKnXED3HRXXGlaswzYpnPSLS0B+c/WbiW+QcUMwmw/8xcb7VL1bVvbUh0ZAvMR3TNcGudWUkg9f0z5K0n0P2pJ5Frte6trqVLhPoKuI5E7zwC3Yg+XCsPBNFy0aukkrWNiCWAqbyGI3nir6UuX5qLER4H+bEYfk4cFw58eHGSIN/FTVjH7WW7aEAfkYNXxWzSDnNVJDtihZVXw+oJSfe74Vz8Scv33cPPZH2W74KvKwj09FOo+EJsvFcC2aDUQclYqwuo91HIaIpqcYb17cSCX95xn9KHErlC48M1bU03txKaDVcmUrOCrveCs7pVPNCz066cil5bzjXeYlXDlmUw5MT45Zgg8EmaJ9gi+iC2zPCU+W088OGEriphXpto40ww3irTN9rtnhIppB5U+drRFW6u25UmDkAjx899TNzC/XsJqkRXn9GinQv6xiD+Axnv3AgudQZBGyeYcNNJDRfA+jzbr6HE1NuNSkxY6aP1OvCLGkNDSA16chO7f4IjjR9jkvDH2m3+ajxa7as1rVNK9R0HHcZJLExvOJn3sJ1LKDwMNPTDjKGPLuWOVwTPQL2pzIitLjkeyx8A2Qcqo5p8U/+aa11Z/x8WA0bQRscZLWMtEHE6WLnHFqBaylCAzARF0Y5cOI/TYCW2xd99ux2WaJnumVlskr2uNCWdwSMOp78gvmzdmonQUH0Ko/k4wa8HcJPyMV5NK52gArIyGmeKXo=", forHTTPHeaderField: "Cookie")

    request.httpMethod = "POST"
    request.httpBody = postData

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

    task.resume()
    semaphore.wait()
    
    print("end")

But I got now a 401 error. For some reason the basic Auth is not being accepted by the code or by the server. I checked the user and password and they are correct.

fabiobh
  • 705
  • 2
  • 13
  • 33
  • Is there anything in the server logs? Sounds like you're either missing headers or something else in your request or the server is not configured as expected. – Charles A. Dec 24 '21 at 00:15
  • 1
    If you can do this successfully in Postman then just ask Postman to generate the Swift code for you! This is one of Postman's best features; it's silly not to take advantage of it. – matt Dec 24 '21 at 00:19
  • 1
    "the "Content-Type" is defined as text/plain in the response" Because the response _is_ plain text (of length zero). – matt Dec 24 '21 at 00:25
  • @CharlesA. I think theres nothing wrong with server code because Postman works fine for me when I call the POST method – fabiobh Dec 26 '21 at 16:35
  • @fabiobh Yeah, it's just possible that the logs might tell you what was wrong with the request or if the request was arriving at all. – Charles A. Dec 27 '21 at 18:39

2 Answers2

0

I find out the fix for my problem.

I was using "http://MY_ENDPOINT/validaResenha" in my constants file instead of using "https://MY_ENDPOINT/validaResenha".

After add the "s" to "http" letter (it becomes "https") everything starts to work for me.

The strange part is that GET and PUT methods works fine because of the redirect from HTTP to HTTPS, but in the case of POST calls, I got this error.

It's fixed now.

fabiobh
  • 705
  • 2
  • 13
  • 33
  • Makes sense, iOS will block non-https traffic unless you disable app transport security. – Charles A. Dec 27 '21 at 18:38
  • I already enable http traffic in the app, because I use some http endpoints to test the app. In this case, the problem was the redirect from http to https that loses some data in the redirect. I think this is the problem. I test on POSTMAN using http and it gives me the same error. – fabiobh Dec 27 '21 at 21:39
0

iOS block HTTP use HTTPS or add to your Info.plist file

Alirza Eram
  • 134
  • 11