2

I am trying to make a request to a PHP server from my swift app. For some reason php is showing an empty array as the $_REQUEST variable. I have looked through stack overflow and implemented everything I can find that might help, but still getting an empty array in php. Here is the relevant swift code...

func connect(_ pin: String, completion: @escaping(Result<ConnectResponse?, Error>) -> ()) {
  let params: [String : Any] = [
    "mobile_pin_connect": pin,
    "device_info": UIDevice().model,
    "additional_info": UIDevice().systemVersion
  ]
  doRequest(params: params) { (data) in
    if let data = data {
      do {
        let res = try JSONDecoder().decode(Dictionary<String, String>.self, from: data)
        completion(.success(
          ConnectResponse(success: (res["success"] == "true"), connect_id: res["connect_id"] ?? nil, error: res["error"] ?? nil)))
      } catch {
        completion(.failure(error))
      }
    } else {
      print("in else block")
    }
  }
}

fileprivate func doRequest(params: [String: Any], completion: @escaping (Data?) -> ()) {
        let body = createJsonBody(params)!
        self.request.httpBody = body
        print("Sending request with thw following variables")
        print(String(data: body, encoding: .utf8)!)
        print(String(data: self.request.httpBody!, encoding: .utf8))
        URLSession.shared.dataTask(with: self.request) { (data, response, error) in
            if let error = error {
                print("Error in request: \(error)")
                completion(nil)
            }
            let stringResult = String(data: data!, encoding: .utf8)!
            let properResult = String(stringResult.map {
                $0 == "." ? "=" : $0
            })
            let decodedData = Data(base64Encoded: properResult)
            completion(decodedData)
        }.resume()
    }

fileprivate func createJsonBody(_ params: [String: Any]) -> Data? {
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: params)
            let body = Data(jsonData).base64EncodedData()
            return body
        } catch {
            print("Unable to create json body: " + error.localizedDescription, error)
            return nil
        }
    }

That sends the request to the server, the setup for the request is in the static var setup...

private static var sharedConnector: ApiConnector = {
        let url = URL(string: "https://mywebsiteURLhere.com/api/mobile/challenge")
        var request = URLRequest(url: url!)
        request.httpMethod = "POST"
        request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        let connector = ApiConnector(request)
        return connector
    }()

So I have the right header values for application/json I have the request method set to post, I am base64encoding the json data and in PHP I have the setup getting php://input...

$rawRequest = file_get_contents("php://input");

and dumping the $_REQUEST variable to an error log, but I always get array\n(\n)\n it is just showing an empty array I even did

error_log("Raw request from index.php");
error_log(print_r($rawRequest, true));

and it logs a completely empty line. I can't figure out why PHP is getting nothing in the request, from everything I have seen online I am doing the request correctly in swift. Any help is really appreciated. Thank you

Neglected Sanity
  • 1,770
  • 6
  • 23
  • 46
  • 2
    `error_log(print_r($rawRequest, true));` does not equal to `error_log(print_r($_REQUEST));` – Alex Dec 16 '19 at 18:55
  • 1
    First of all, even if you send your JSON successfully, `$_REQUEST` gets empty. Anyway, small fragments of code do not help to solve your issue. Show buildable and executable codes both of PHP and Swift. Problems may be hiding in the hidden parts. – OOPer Dec 16 '19 at 18:59

4 Answers4

1

As per your Swift Code, Can you please replace the following method.

fileprivate func createJsonBody(_ params: [String: Any]) -> Data? {
        do {

            let jsonData = try JSONSerialization.data(withJSONObject: params)
            let body = Data(jsonData)
            return body
        } catch {
            print("Unable to create json body: " + error.localizedDescription, error)
            return nil
        }
    }

You need to replace this line let body = Data(jsonData) with let body = Data(jsonData).base64EncodedData()

Urvish Patel
  • 175
  • 1
  • 11
1

Without seeing your PHP code, it is difficult to determine the entire picture. However, whatever steps you perform to encode your data via the client (Swift) you must reverse to successfully decode the message on the server.

For example, if you prepare and send the request from your client as follows.

Client:

  1. JSON encode data
  2. base-64 encode
  3. send data

The your server must reverse the steps to successfully decode the data.

Server:

  1. recv data
  2. base-64 decode data
  3. JSON decode data

Unless your server requires it, I would remove the base-64 encode step, as it only complicates your encode / decode process.

I have created a working example: https://github.com/stuartcarnie/stackoverflow/tree/master/q59329179

Clone it or pull down the specific code in your own project.

To test, open up a terminal and run the php server:

$ cd q59329179/php
$ php -S localhost:8080 router.php

PHP 7.3.9 Development Server started at Thu Dec 19 10:47:58 2019
Listening on http://localhost:8080
Document root is /Users/stuartcarnie/projects/stackoverflow/q59329179/php
Press Ctrl-C to quit.

Test it works with curl in another terminal session:

$ curl -XPOST localhost:8080 --data-binary '{"string": "foo", "number": 5}'

Note you should see output in the php session:

[Thu Dec 19 11:33:43 2019] Array
(
    [string] => foo
    [number] => 5
)

Run the Swift test:

$ cd q59329179/swift
$ swift run request

Note again, decoded output in php session:

[Thu Dec 19 11:20:49 2019] Array
(
    [string] => string value
    [number] => 12345
    [bool] =>
)
Stuart Carnie
  • 5,458
  • 1
  • 29
  • 27
0

Your request is probably not arriving through the POST structure, but is kept in the request body.

Try running this as your first PHP operation:

$raw = file_get_contents('php://input');

and see what, if anything, is now into $raw. You should see a Base64 encoded string there, that you need to decode - like this, if you need an array:

$info = json_decode(base64_decode($raw), true);
LSerni
  • 55,617
  • 10
  • 65
  • 107
0

I've tested your code and it's working fine. The issue might be at your PHP end. I've tested the following code on local server as well as on httpbin

The output from a local server (recent version of XAMPP (php 7.3.12)):

Sending request with thw following variables
eyJhZGRpdGlvbmFsX2luZm8iOiIxMy4yLjIiLCJtb2JpbGVfcGluX2Nvbm5lY3QiOiIxMjM0IiwiZGV2aWNlX2luZm8iOiJpUGhvbmUifQ==
result eyJhZGRpdGlvbmFsX2luZm8iOiIxMy4yLjIiLCJtb2JpbGVfcGluX2Nvbm5lY3QiOiIxMjM0IiwiZGV2aWNlX2luZm8iOiJpUGhvbmUifQ==
message ["additional_info": "13.2.2", "mobile_pin_connect": "1234", "device_info": "iPhone"]

Code:

ApiConnector.swift

import Foundation
import UIKit
class ApiConnector{
    var request: URLRequest
   private init(request: URLRequest) {
        self.request = request
    }

    public static var sharedConnector: ApiConnector = {
        let url = URL(string: "http://localhost/post/index.php")
        var request = URLRequest(url: url!)
        request.httpMethod = "POST"
        request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
        let connector = ApiConnector(request: request)
        return connector
    }()

    func connect(_ pin: String, completion: @escaping(Result<Dictionary<String, String>, Error>) -> ()) {
        let params: [String : Any] = [
            "mobile_pin_connect": pin,
            "device_info": UIDevice().model,
            "additional_info": UIDevice().systemVersion
        ]
        doRequest(params: params) { (data) in
            if let data = data {
                do {
                    let res = try JSONDecoder().decode(Dictionary<String, String>.self, from: data)
                    completion(.success(res))
                } catch {
                    completion(.failure(error))
                }
            } else {
                print("in else block")
            }
        }
    }

    fileprivate func doRequest(params: [String: Any], completion: @escaping (Data?) -> ()) {
        let body = createJsonBody(params)!
        self.request.httpBody = body
        print("Sending request with thw following variables")
        print(String(data: body, encoding: .utf8)!)
        URLSession.shared.dataTask(with: self.request) { (data, response, error) in
            if let error = error {
                print("Error in request: \(error)")
                completion(nil)
            }
            let stringResult = String(data: data!, encoding: .utf8)!
            print("result \(stringResult)")
            let properResult = String(stringResult.map {
                $0 == "." ? "=" : $0
            })
            let decodedData = Data(base64Encoded: properResult)
            completion(decodedData)
        }.resume()
    }

    fileprivate func createJsonBody(_ params: [String: Any]) -> Data? {
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: params)
            let body = Data(jsonData).base64EncodedData()
            return body
        } catch {
            print("Unable to create json body: " + error.localizedDescription, error)
            return nil
        }
    }
}

ViewController.swift

import UIKit
class ViewController: UIViewController {
 let session = URLSession.shared
    override func viewDidLoad() {
        super.viewDidLoad()

        ApiConnector.sharedConnector.connect("1234") { (result) in
            switch result {
            case .success(let message):
                print("message \(message)")
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }
}

index.php

echo file_get_contents("php://input");

You can verify your code by doing a request to https://httpbin.org/post

output:

Sending request with thw following variables
eyJkZXZpY2VfaW5mbyI6ImlQaG9uZSIsImFkZGl0aW9uYWxfaW5mbyI6IjEzLjIuMiIsIm1vYmlsZV9waW5fY29ubmVjdCI6IjEyMzQifQ==
result {
  "args": {}, 
  "data": "eyJkZXZpY2VfaW5mbyI6ImlQaG9uZSIsImFkZGl0aW9uYWxfaW5mbyI6IjEzLjIuMiIsIm1vYmlsZV9waW5fY29ubmVjdCI6IjEyMzQifQ==", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Accept-Language": "en-us", 
    "Content-Length": "108", 
    "Content-Type": "application/json; charset=utf-8", 
    "Host": "httpbin.org", 
    "User-Agent": "SessionTest/1 CFNetwork/1120 Darwin/19.0.0"
  }, 
  "json": null, 
  "origin": "122.173.135.243, 122.173.135.243", 
  "url": "https://httpbin.org/post"
}

in else block

If you are running an older version of PHP then You might need HTTP_RAW_POST_DATA

Have look at this SO for more info on PHP side.

Sahil Manchanda
  • 9,812
  • 4
  • 39
  • 89