Я использую PHP & JSON для извлечения некоторых данных из базы данных.
Это мой PHP файл
<?php
error_reporting(0);
ini_set('error_reporting', E_ALL);
ini_set('display_errors','Off');
$mysqli = new mysqli("localhost", "root", $_REQUEST['password'], "");
if ($mysqli->connect_errno) {
echo "Failed to connect to DB.";
die();
} else {
$dbs = array();
$res = $mysqli->query("SHOW DATABASES");
$res->data_seek(0);
if ($res->num_rows > 0) {
while($row = $res->fetch_assoc()) {
$dbs[] = $row;
}
echo json_encode($dbs);
} else {
echo "Failed to get list of databases from server.";
die();
}} ?>
Если пароль неверный, система выдает «Не удалось подключиться к БД»
В моей программе есть вещи для обработки ошибок, но я застрял в одной части.
let urlString = "http://\(hostTextField.text):\(portTextField.text)/dblist.php? &password=\(passTextField.text)"let url: NSURL = NSURL(string: urlString)!
let urlSession = NSURLSession.sharedSession()
println(url)
println(urlSession)
//2
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
println(response)
println(data)
if (error != nil) {
println("Can't connect using credentials")
dispatch_async(dispatch_get_main_queue(), {
HUDController.sharedController.hide(afterDelay: 0.1)
})
sleep(1)
var refreshAlert = UIAlertController(title: "Camaleon Reports", message: "Can't connect to the database", preferredStyle: UIAlertControllerStyle.Alert)
refreshAlert.addAction(UIAlertAction(title: "Retry", style: .Default, handler: { (action: UIAlertAction!) in
println("Yes Logic")
}))
self.presentViewController(refreshAlert, animated: true, completion: nil)
return }
var err: NSError?
var jsonResult: [Dictionary<String, String>] = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as [Dictionary<String, String>]
// 3
if (err != nil) {
println("Still cant connect....")
println("JSON Error \(err!.localizedDescription)")
}
var jsonDB : [Dictionary<String, String>] = jsonResult
for currentDictionary in jsonDB{
var currentEntry = currentDictionary["Database"] as String!
Моя программа падает, если у меня нет правильного пароля, но есть правильный IP-адрес и порт / пользователь для базы данных MYSQL.
Вылетает с этим:
fatal error: unexpectedly found nil while unwrapping an Optional value
и указывает на JsonResult. Это имеет смысл, потому что я не получаю две строки.
Моя проблема в том, что если мой пароль отключен, то мой PHP-файл отображает строку. Как я могу найти эту строку, чтобы использовать оператор if и предотвратить сбой приложения?
Ваша проблема, вероятно, в этой строке (обернуто для ясности):
var jsonResult: [Dictionary<String, String>] =
NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &err) as [Dictionary<String, String>]
Когда ваш PHP-скрипт сообщает об ошибке, просто возвращая строку, он возвращает неверный JSON. Когда вы используете NSJSONSerialization.JSONObjectWithData
чтобы разобрать, этот метод вернет nil
если JSON недействителен, как ваш.
Затем вы берете это значение и назначаете его объявленной вами переменной Swift, которая не является необязательной. Попытка назначить nil
переменной, не объявленной ни ?
или же !
это ошибка во время выполнения в Swift. (Вы не получаете ошибку во время компиляции, потому что вы используете as
бросить значение.)
Один из способов исправить это — изменить ваш PHP, чтобы ошибка соответствовала JSON:
echo "{ \"error\": \"Failed to connect to DB.\" }"; # or something, my PHP is rusty
Но это все еще оставляет вашу программу Swift в хрупком состоянии; получение чего-либо, кроме правильного JSON с сервера, приведет к сбою.
Лучше объявить jsonResult
переменная как необязательная:
var jsonResult: [Dictionary<String, String>]? =
NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &err) as [Dictionary<String, String>]?
Тогда в вашем коде вы можете явно проверить, jsonResult
это ноль, и если это так, вы знаете, что произошла ошибка, и можете вернуться и посмотреть на data
объект, чтобы увидеть, что это было.
Даже это, тем не менее, может оставить вас в беде. Корень документа JSON не обязательно должен быть словарем; это может быть массив. И даже если это словарь, значения могут не быть строками; это могут быть числа, логические значения, вложенные массивы или словари!
Относительно слабая проверка типов в Objective-C позволяет легко с этим справляться, но Swift более строг. Лучше всего использовать один из специфичных для Swift JSON библиотеки. Это сделает ваш код гораздо более надежным.
Удачи!
Есть два вопроса. Один из них — PHP, а второй — Swift.
Ваш PHP действительно никогда не должен просто сообщать об ошибке. Я бы предложил, чтобы он всегда возвращал JSON. Это облегчит для вашего клиентского кода правильное обнаружение и обработку ошибок.
<?php
header("Content-Type: application/json");
$response = array();
error_reporting(0);
ini_set('error_reporting', E_ALL);
ini_set('display_errors','Off');
if (!isset($_REQUEST['password'])) {
$response["success"] = false;
$response["error_code"] = 1;
$response["error_message"] = "No password provided";
echo json_encode($response);
exit();
}
$mysqli = new mysqli("localhost", "root", $_REQUEST['password'], "");
if ($mysqli->connect_errno) {
$response["success"] = false;
$response["error_code"] = 2;
$response["mysql_error_code"] = $mysqli->connect_errno;
$response["error_message"] = $mysqli->connect_error;
echo json_encode($response);
exit();
}
if ($res = $mysqli->query("SHOW DATABASES")) {
$dbs = array();
$res->data_seek(0);
if ($res->num_rows > 0) {
while($row = $res->fetch_assoc()) {
$dbs[] = $row;
}
$response["success"] = true;
$response["results"] = $dbs;
} else {
$response["success"] = false;
$response["error_code"] = 3;
$response["error_message"] = "Failed to get list of databases from server.";
}
$res->close();
} else {
$response["success"] = false;
$response["error_code"] = 4;
$response["mysql_error_code"] = $mysqli->errno;
$response["error_message"] = $mysqli->error;
}
$mysqli->close();
echo json_encode($response);
?>
Обратите внимание, это:
Определяет application/json
заголовок для Content-Type
;
Всегда возвращает словарь, содержащий
ключ «успеха», который является истинным или ложным;
если ошибка, error_code
указание типа ошибки (1 = пароль не указан; 2 = ошибка подключения; 3 = базы данных не найдены; 4 = ошибка SQL);
если ошибка, error_msg
строка, указывающая строку сообщения об ошибке; а также
если успех, results
массив (так же, как вы использовали для возврата на корневом уровне).
Со стороны Swift вам необходимо:
Измените его, чтобы искать эти различные ошибки уровня приложения сервера (обратите внимание, я делаю структуру верхнего уровня словарем, а ваш исходный массив словарей — конкретным значением;
Вы можете заранее проверить statusCode
из response
объект, чтобы убедиться, что сервер дал вам 200
код возврата (например, 404
означает, что страница не найдена и т. д.);
Возможно, вы также захотите проверить наличие ошибок синтаксического анализа JSON (в случае, если какая-то ошибка на сервере помешала вернуть правильно сформированный JSON); а также
Вы действительно должны быть в процентах, экранируя пароль (потому что, если он включен +
или же &
символы, это не будет передано успешно в противном случае).
Таким образом, у вас может быть что-то вроде:
let encodedPassword = password.stringByAddingPercentEncodingForURLQueryValue()!
let body = "password=\(encodedPassword)"let request = NSMutableURLRequest(URL: URL!)
request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)!
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
// detect fundamental network error
guard error == nil && data != nil else {
print("network error: \(error)")
return
}
// detect fundamental server errors
if let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode != 200 {
// some server error
print("status code was \(httpResponse.statusCode); not 200")
return
}
// detect parsing errors
guard let responseObject = try? NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String : AnyObject] else {
// some problem parsing the JSON response
print(String(data: data!, encoding: NSUTF8StringEncoding))
return
}
// now parse app-level response to make sure `status` was `true`
guard let success = responseObject!["success"] as? NSNumber else {
print("Problem extracting the `success` value") // we should never get here
return
}
if !success.boolValue {
print("server reported error")
if let errorCode = responseObject!["error_code"] as? NSNumber {
switch (errorCode.integerValue) {
case 1:
print("No password provided")
case 2:
print("Connection failed; probably bad password")
case 3:
print("No databases found")
case 4:
print("Some SQL error")
default:
print("Unknown error code: \(errorCode)") // should never get here
}
}
if let errorMessage = responseObject!["error_message"] as? String {
print(" message=\(errorMessage)")
}
return
}
if let databases = responseObject!["results"] as? [[String : AnyObject]] {
print("databases = \(databases)")
}
}
task.resume()
Процентный код находится в String
категория:
extension String {
// see RFC 3986
func stringByAddingPercentEncodingForURLQueryValue() -> String? {
let characterSet = NSCharacterSet(charactersInString:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
return self.stringByAddingPercentEncodingWithAllowedCharacters(characterSet)
}
}
Пара других вспомогательных наблюдений:
Никогда не отправляйте пароли в открытом виде. Поместите их в тело POST
запрос (не URL), а затем используйте https
URL.
Лично я не использовал бы часть пароля MySQL аутентификации уровня приложения. Я бы сохранил логику аутентификации MySQL в кодировке на стороне сервера, а затем использовал бы вашу собственную таблицу аутентификации пользователей.