- PVSM.RU - https://www.pvsm.ru -
Можно долго смотреть на три вещи: как течет вода, как имплементируется CoreFoundation [1] в Linux Swift, и как не обновляется документация Perfect [2]
Сначала кратко для тех, кто не в курсе:Perfect — это один из самых стабильных серверных фреймворков на Swift. (benchmark [3])
Сервер Perfect на Linux c MySQL и Protocol Buffers для общения с приложением-клиентом
Мы прогрессивные хипстеры со свифтом (sarcasm), поэтому дайте самую последнюю версию Swift 4.0.2
У Perfect есть отличный пример PerfectTemplate, которым мы и воспользуемся. Однако, в официальном репозитории Pull Request с обновленным синтаксисом и русской документацией в процессе одобрения, поэтому воспользуемся моим форком.
git clone https://github.com/nickaroot/PerfectTemplate.git
Не будем ждать и сразу же попробуем запустить его
cd PerfectTemplate
swift run
Если все прошло гладко, то мы увидим сборщик, а затем
[INFO] Starting HTTP server localhost on 0.0.0.0:8181
Откроем Package.swift и добавим зависимость PerfectMySQL так, что получится
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.3"),
.package(url: "https://github.com/PerfectlySoft/Perfect-MySQL", from: "3.0.0"),
А также
dependencies: ["PerfectHTTPServer", "PerfectMySQL"],
Далее после всех import'ов в main.swift добавим
import PerfectMySQL
Объявим переменные для соединения с базой, не забывая подставить свои значения
let testHost = "example.com" // имя хоста / его IP
let testUser = "foo" // имя пользователя
let testPassword = "bar" // пароль
let testDB = "swift_example_db" // имя базы данных
Хотя данный процесс и описан в документации, модуль PerfectMySQL уже шагнул далеко дальше документации, и собрать код получилось лишь после изучения коммитов (не надо так)
Создадим обработчик запроса fetchDataHandler(), для этого после функции handler() вставим
func fetchDataHandler(data: [String:Any]) throws -> RequestHandler {
return {
request, response in
print("Request Handled!")
response.completed()
}
}
В конфигурации добавим событие обработчика
["method":"get", "uri":"/fetchDataHandler", "handler":fetchDataHandler],
перед
["method":"get", "uri":"/", "handler":handler],
Подключаемся к БД. Для этого вставим код после print("Request Handled!")
let mysql = MySQL() // cоздаем экземпляр MySQL для работы с ним
let connected = mysql.connect(host: testHost, user: testUser, password: testPassword)
guard connected else {
// проверяем, что подключение успешно
print(mysql.errorMessage())
return
}
defer {
mysql.close() // этот блок гарантирует нам, что по завершению соединение будет закрыто вне зависимости от полученного результата
}
// выбираем базу данных
guard mysql.selectDatabase(named: testDB) else {
Log.info(message: "Failure: (mysql.errorCode()) (mysql.errorMessage())")
return
}
Далее создаем подготовленный запрос к базе и выполняем его
let stmt = MySQLStmt(mysql) // экземпляр запроса
_ = stmt.prepare(statement: "SELECT * FROM test") // подготавливаем выборку из таблицы test
let querySuccess = stmt.execute() // выполняем запрос
// убеждаемся, что запрос прошел
guard querySuccess else {
print(mysql.errorMessage())
return
}
Дело за малым — осталось лишь обработать полученные результаты
let results = stmt.results()
let fieldNames = stmt.fieldNames() // не упомянутая в документации функция, отдает имена полей в таблице
var arrayResults: [[String:Any]] = [] // подготовим массив для данных
_ = results.forEachRow { row in
var rowDictionary = [String: Any]()
var i = 0 // требуется для итерации по именам полей
while i != results.numFields {
rowDictionary[fieldNames[i]!] = row[i] // пишем в словарь полученные данные в виде ["имя_поля":"значение"]
i += 1
}
arrayResults.append(rowDictionary)
}
Теперь просто выведем полученный массив данных
print(arrayResults)
response.setHeader(.contentType, value: "text/html")
response.appendBody(string: "<html><title>Testing...</title><body>(arrayResults)</body></html>")
Проверим обработчик
swift run
Создадим файл Person.proto с содеражнием примера
syntax = "proto3";
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
Скомпилируем swift-файл
protoc --swift_out=. Person.proto
Откроем Package.swift и добавим зависимость SwiftProtobuf так, что получится
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.3"),
.package(url: "https://github.com/PerfectlySoft/Perfect-MySQL", from: "3.0.0"),
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.0.1"),
А также
dependencies: ["PerfectHTTPServer", "PerfectMySQL", "SwiftProtobuf"],
Импортируем модуль в main.swift
import SwiftProtobuf
Сразу добавим два пути
["method":"post", "uri":"/send", "handler":sendHandler],
["method":"post", "uri":"/receive", "handler":receiveHandler],
Метод sendHandler(data:) для отправки protobuf
func sendHandler(data: [String:Any]) throws -> RequestHandler {
return {
request, response in
if !request.postParams.isEmpty {
var name: String? = nil
var id: Int32? = nil
var email: String? = nil
for param in request.postParams { // парсим POST-параметры в переменные
if param.0 == "name" {
name = param.1
} else if param.0 == "id" {
id = Int32(param.1)
} else if param.0 == "email" {
email = param.1
}
}
if let personName = name, let personId = id, let personEmail = email {
var person = Person()
person.name = personName
person.id = personId
person.email = personEmail
do {
let data = try person.serializedData() // сериализуем в формат Data
print("Serialized Proto into Data")
print("Sending Proto…")
ProtoSender().send(data) // отправляем сериализованные данные
} catch {
print("Failed to Serialize Protobuf Object into Data")
}
}
}
response.setHeader(.contentType, value: "text/plain")
response.appendBody(string: "1")
response.completed()
}
}
Возникает вопрос: Что такое ProtoSender и где его взять
Запомните кое-что важное: Как было сказано в начале, Foundation находится в стадии имплементации, и можно было бы с удовольствием отправлять все данные через URLSession, однако его метод shared() недоступен (пока что [9]) на платформе Linux
Называется решение cURL, а его обертка уже реализована в PerfectCURL, которым мы и воспользуемся
Уже привычно откроем Package.swift и добавим зависимость PerfectCURL
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/PerfectlySoft/Perfect-HTTPServer.git", from: "3.0.3"),
.package(url: "https://github.com/PerfectlySoft/Perfect-MySQL", from: "3.0.0"),
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.0.1"),
.package(url: "https://github.com/PerfectlySoft/Perfect-CURL.git", from: "3.0.1"),
А также
dependencies: ["PerfectHTTPServer", "PerfectMySQL", "SwiftProtobuf", "PerfectCURL"],
Импортируем модуль в main.swift
import PerfectCURL
Добавим структуру ProtoSender
struct ProtoSender {
func send(_ data: Data) {
let url = "http://localhost:8181/receive" // путь к обработчику приема
do {
_ = try CURLRequest(url, .failOnError, .postData(Array(data))).perform() // Array(data) т.к. формат [UInt8]
} catch {
print("Sending failed")
}
}
}
Вы почти в самом конце статьи, осталось лишь добавить receiveHandler
func receiveHandler(data: [String:Any]) throws -> RequestHandler {
return {
request, response in
print("Proto Received!")
if let bytes = request.postBodyBytes {
let data = Data(bytes: bytes) // Protobuf присылается в бинарном виде, парсим в Data
do {
let person = try Person(serializedData: data) // парсим Protobuf
print("Proto was received and converted into a person with: nname: (person.name) nid: (person.id) nemail: (person.email)")
let mysql = MySQL() // Можно использовать один раз на все функции
let connected = mysql.connect(host: testHost, user: testUser, password: testPassword)
guard connected else {
print(mysql.errorMessage())
return
}
defer {
mysql.close()
}
guard mysql.selectDatabase(named: testDB) else {
Log.info(message: "Failure: (mysql.errorCode()) (mysql.errorMessage())")
return
}
let stmt = MySQLStmt(mysql)
_ = stmt.prepare(statement: "INSERT INTO test (id, name, email) VALUES (?, ?, ?)") // вставляем в базу полученные значения
stmt.bindParam(Int(person.id)) // биндим по порядку, как в php
stmt.bindParam(person.name)
stmt.bindParam(person.email)
let querySuccess = stmt.execute()
guard querySuccess else {
print(mysql.errorMessage())
return
}
} catch {
print("Failed to Decode Proto")
}
}
response.setHeader(.contentType, value: "text/plain")
response.appendBody(string: "1")
response.completed()
}
}
swift run
Если все запустилось, то откроем еще одно окно терминала и пошлем POST-запрос
curl 127.0.0.1:8181/send --data "name=foobar&id=8&email=foobar@example.com" -X POST
В первом окне консоли должны отобразиться данные в виде
Serialized Proto into Data
Sending Proto…
Proto Received!
Proto was received and converted into a person with:
name: foobar
id: 8
email: foobar@example.com
Для дополнительной проверки можно открыть 127.0.0.1/fetchData, но при отправке данных не забывать, что все id должны быть уникальны (id мы передаем в рамках тестирования)
Репозиторий с готовым проектом [10]
Пишите пожелания и критику. Материал создан в целях познакомить с техникой, поэтому в ходе статьи все было в одном файле (см. готовый проект).
Автор: Funtrum
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/267818
Ссылки в тексте:
[1] имплементируется CoreFoundation: https://github.com/apple/swift-corelibs-foundation
[2] Perfect: http://perfect.org
[3] benchmark: https://medium.com/@rymcol/updated-benchmarks-for-the-top-server-side-swift-frameworks-vs-node-js-9da4a0491eca
[4] подробно описано здесь: https://swift.org/download/#snapshots
[5] вот: http://help.ubuntu.ru/wiki/mysql
[6] так: https://gist.github.com/sofyanhadia/37787e5ed098c97919b8c593f0ec44d8
[7] http://127.0.0.1:8181: http://127.0.0.1:8181
[8] http://127.0.0.1:8181/fetchData: http://127.0.0.1:8181/fetchData
[9] пока что: https://github.com/apple/swift-corelibs-foundation/blob/master/Docs/Status.md
[10] Репозиторий с готовым проектом: https://github.com/nickaroot/Perfect-Protobuf-MySQL-Demo.git
[11] Источник: https://habrahabr.ru/post/341980/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.