Search code examples
swiftnsurlsessionurlsession

Using Swift with URLSession works with GET and PUT, but it gives error 405 Method Not Allowed when I use POST


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.


Solution

  • 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.