📜 ⬆️ ⬇️

We do well with Swift 4, Perfect, Protobuf and MySQL on a Linux server

You can look at three things for a long time: how water flows, how CoreFoundation is implemented in Linux Swift, and how Perfect documentation is not updated.


First, briefly for those who do not know: Perfect is one of the most stable Swift server frameworks. ( benchmark )


Task:


Perfect Server on Linux with MySQL and Protocol Buffers to communicate with the client application


Important requirement:


We are progressive hipsters with swifts (sarcasm), so give the latest version of Swift 4.0.2


Step 0. Installing the toolkit


  1. Install directly Swift 4.0.2 ( described in detail here )
  2. It is assumed that you already have MySQL installed. If not, there are a lot of tutorials (for example, for Ubuntu)
  3. Also, we need the Protocol Buffers compiler package (you can build from source, or you can)

Step 1. Set Up Perfect


Perfect has a great example of PerfectTemplate , which we will use. However, in the official Pull Request repository with the updated syntax and Russian documentation in the approval process, so we will use my fork.


git clone https://github.com/nickaroot/PerfectTemplate.git 

We will not wait and immediately try to run it.


 cd PerfectTemplate swift run 

If everything went smoothly, then we will see a collector, and then


 [INFO] Starting HTTP server localhost on 0.0.0.0:8181 

Hooray! The server should give us "Hello, World!" at http://127.0.0.1:8181


Step 2.0 Table test in MySQL


image


Step 2.1. MySQL Module Preparation


Open the Package.swift and add the PerfectMySQL dependency so that it turns out


 // 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"), 

And


 dependencies: ["PerfectHTTPServer", "PerfectMySQL"], 

Next, after all the import'ov add to main.swift


 import PerfectMySQL 

We declare variables for the connection with the base, not forgetting to substitute our values


 let testHost = "example.com" //   /  IP let testUser = "foo" //   let testPassword = "bar" //  let testDB = "swift_example_db" //    

Step 2.2. Processing the request and receiving data from the database


Although this process is described in the documentation, the PerfectMySQL module has already gone far beyond the documentation, and it turned out to collect the code only after studying commits (it’s not necessary)


Create a request handler fetchDataHandler (), to do this, after the handler () function, insert


 func fetchDataHandler(data: [String:Any]) throws -> RequestHandler { return { request, response in print("Request Handled!") response.completed() } } 

In the configuration, add the event handler


 ["method":"get", "uri":"/fetchDataHandler", "handler":fetchDataHandler], 

before


 ["method":"get", "uri":"/", "handler":handler], 

Connect to the database. To do this, insert the code after 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 } 

Next, create a prepared request to the database and execute it.


 let stmt = MySQLStmt(mysql) //   _ = stmt.prepare(statement: "SELECT * FROM test") //     test let querySuccess = stmt.execute() //   // ,    guard querySuccess else { print(mysql.errorMessage()) return } 

Things are going well - it remains only to process the results


 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) } 

Now simply output the resulting data array.


 print(arrayResults) response.setHeader(.contentType, value: "text/html") response.appendBody(string: "<html><title>Testing...</title><body>\(arrayResults)</body></html>") 

Check handler


 swift run 

If everything compiled without errors and started, then on http://127.0.0.1:8181/fetchData we will see the array obtained from MySQL


Step 3.1. Preparation Protocol Buffers


Create a file Person.proto with the help of an example


 syntax = "proto3"; message Person { string name = 1; int32 id = 2; string email = 3; } 

Compile the swift file


 protoc --swift_out=. Person.proto 

Open Package.swift and add the SwiftProtobuf dependency so that we can


 // 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"), 

And


 dependencies: ["PerfectHTTPServer", "PerfectMySQL", "SwiftProtobuf"], 

Import the module into main.swift


 import SwiftProtobuf 

Step 3.2. Creating a handler for receiving and sending Protobuf


Immediately add two ways


 ["method":"post", "uri":"/send", "handler":sendHandler], ["method":"post", "uri":"/receive", "handler":receiveHandler], 

SendHandler (data :) method to send 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() } } 

The question arises: What is ProtoSender and where to get it
Remember something important: As stated at the beginning, the Foundation is in the implementation stage, and you could gladly send all the data through the URLSession , but its shared () method is not available ( yet ) on the Linux platform


There is a solution


The solution is called cURL, and its wrapper is already implemented in PerfectCURL , which we will use


Already familiarly open Package.swift and add the PerfectCURL dependency


 // 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"), 

And


 dependencies: ["PerfectHTTPServer", "PerfectMySQL", "SwiftProtobuf", "PerfectCURL"], 

Import the module into main.swift


 import PerfectCURL 

Add a ProtoSender structure


 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") } } } 

You are almost at the very end of the article, it remains only to add


 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() } } 

Check performance


 swift run 

If everything started, we will open another terminal window and send a POST request.


 curl 127.0.0.1:8181/send --data "name=foobar&id=8&email=foobar@example.com" -X POST 

The first console window should display the data in the form


 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 

For additional verification, you can open 127.0.0.1/fetchData, but when sending data, do not forget that all id must be unique (id we pass as part of testing)


Now we can do Swift on the server


Repository with a ready project


Write wishes and criticism. The material was created in order to introduce the technique, so during the article everything was in one file (see the finished project).


')

Source: https://habr.com/ru/post/341980/


All Articles