📜 ⬆️ ⬇️

Perfect - REST server on Swift

image
Most iOS developers sooner or later become crowded in the world of iOS SDK. And the reason, by no means, is that IOS does not have enough opportunities for serious development, but the fact that most modern serious applications have a client-server architecture, but only the client world is available to iOS developers. Server development is left to server reindeer programmers, who are very jealous of the requirements of mobile developers to change something in the API. It does not add optimism to the fact that to implement the simplest API, one has to learn another programming language, with its own paradigms and nuances. Even in order to run around some pilot idea, one has to either attract people from the outside, or plunge into the world of alien dreams (PHP, Pyton, Ruby, C #). Is it all so bad in the Objectove-C / Swift? It turned out not at all. A little googling for server development, I came across a rather curious undertaking claiming to become a real cross-platform solution — that is, it works equally well in OSX and * nix systems (I don’t talk about Windows, there # hardly anyone moves - too many goodies).

Perfect - as the creators of the project say - The ideal web server and toolkit for developers using Swift programming language to create applications and other REST services. It is clear that “Ideal” is nothing more than a play on words, but at the same time, after getting acquainted with the proposed solution, you begin to incline to the fact that there is some truth in this statement.

In the "press" ran through the article that on the way a new programming language, which can become an industry standard with easy filing Apple. A language that is based on the Swift being promoted to the masses. As a rule, articles about this caused more questions, and even more irritation for those who are tired of retraining everything (Swift itself is changing quite quickly). However, delving into the study of the issue, it becomes clear that everything is much better than it seems.
')
Perfect is not a new language, server development. Perfect is a server environment that allows you to create REST API services using Swift's latest implementation exclusively (at the time of writing Swift 2.2) There is nothing beyond the scope of what client developers have to do daily.


What we will do: Create a business card page (blank) to show it when accessing the server. Let us demonstrate the ability to easily create REST API services that will respond to GET / POST requests. Let us demonstrate the mechanism of dynamic formation of static pages on the site. Moreover, we will do all this on Swift.

Mono
Mono lovers can say that a cross-platform server development tool has been around for a long time, and it’s hard to disagree if one essential “BUT” is for someone who is familiar with the server part of C # /. Net programming in Mono turns into pain and tears causing disgust.


Disclaimer
Evangelicals HATEOAS and HAL may pass by. There will be a mention of REST in the form that mobile developers are used to. The purity of such a REST can be questioned, but that will not stop it being one. There is no need to arrange holivars about whether an African elephant can be considered an elephant, or only Indian elephants can be considered an elephant.


So, the starting point of the trip will be the creation of the appropriate environment. Unfortunately, the way to go is not so obvious, and is associated with a number of very strange manipulations - from creating a Workspace to changing schemes in Xcode. Step-by-step tutorial is shown in the video prepared by the authors of the project. Describing each step in the article is rather a topic for the “translations” hub. I would like to focus on the practical application of the Perfect features, which are missing in the commercials, or are presented there in a monstrous mysterious way. By the way, some videos published by the authors are more harmful than useful.



For a start, let's understand the concepts. Perfect consists of two parts: the Server Library (PerfectLib), and the launching application with a minimalist interface (Perfect Server). Both applications are open source, and theoretically, you can edit / add to your needs. However, I strictly do not recommend you do this. Personally, I constantly have attempts to improve something. But it should be noted that Perfect is not adapted for use with Swift 3. And the language creators declare that Swift 3 will not have top-down support, which means that after the release of the third version of the language, Perfect is guaranteed to be updated, and you will have to completely get rid of the changes already made, in order to go to the new version of Swift.

If you have not yet reached the “Hello Perfect!” Stage - it's time to do it, download the environment you need here . (Some of the links on the project site are broken)

Next, create the index.html and template.html files, and then add them to our working draft. After adding, let's go to Build Phases and add the step “New Copy Files Phase”

image

Ultimately, the window should look like this:

image

Source code index.html
<HTML> <HEAD> <TITLE>My company name.</TITLE> </HEAD> <BODY BGCOLOR="FFFFFF"> <CENTER><IMG SRC="http://www.telemis.com/sites/default/files/Cloud-Plain-Blue_2.png" width="300px"> </CENTER> <HR> <a href="http://demonsoft.net">My name</a> is a link to another nifty site <H1>This is a Header</H1> <H2>This is a Medium Header</H2> Send me mail at <a href="mailto:support@yourcompany.com"> mail@gmail.com</a>. <P> This is a new paragraph! <P> <B>This is a new paragraph!</B> <B><I>This is a new sentence without a paragraph break, in bold italics.</I></B> <HR> </BODY> </HTML> 




Source code template.html
 <HTML> <HEAD> <TITLE>{{TITLE}}</TITLE> </HEAD> <BODY> <table border = "1px" width="100%"> <tr> <th>{{TITLE}}</th> </tr> </table> <table border = "1px" width="100%"> <tr> <th align="left">{{BODY}}</th> </tr> </table> <table border = "0px" width="100%" > <tr> <th align="right">{{FOOTER}}</th> </tr> </table> </BODY> </HTML> 



For the most part, all of these actions are reviewed in a video from the project page: www.youtube.com/watch?v=J441eJ40PH4 However, the considered case allows you to either host Web pages or use the REST API. We will try to combine both needs into one opportunity.

Completely replace the PerfectServerModuleInit code with the code below:
PerfectServerModuleInit
public func PerfectServerModuleInit()
{
Routing.Handler.registerGlobally()

// Root index.html page
Routing.Routes["*"] = { (_:WebResponse) in StaticFileHandler() }

// Request for static pages
Routing.Routes["GET", ["/index", "/list"]] = { (_:WebResponse) in return StaticPageHandler(staticPage: "index.html") }

// REST
Routing.Routes["GET", ["/hello"]] = { (_:WebResponse) in return HelloHandler() }
Routing.Routes["GET", ["/help"]] = { (_:WebResponse) in return HelpHandler() }
Routing.Routes["GET", ["/cars", "/car"]] = { (_:WebResponse) in return CarsJson() }


Routing.Routes["POST", ["/list"]] = { (_:WebResponse) in return CarsJson() }
}


The call to the PerfectServerModuleInit method is present in the server project (in my case MySwiftServer), but is not tied to any class. I rendered it into a separate .swift file.
PerfectServerModuleInit is the entry point to our Web service. It is similar to the main function. Method Called by the server library. Later I will explain what is happening here.

Now you need to add some more classes.

HelloHandler class
import Foundation
import PerfectLib

class HelloHandler:RequestHandler
{
func handleRequest(request: WebRequest, response: WebResponse)
{
response.appendBodyString("Hello World!\n")
response.appendBodyString("Hello Perfect!\n")
response.appendBodyString("Hello Swift Server!\n")
response.requestCompletedCallback()
}
}


The HelloHandler class does nothing useful and is mainly used to check that the server is up and running. You can see that the server’s response comes down to adding a line to the output buffer, and calling back the client (browser or client application).

Class StaticPageHandler
import Foundation
import PerfectLib

class StaticPageHandler:RequestHandler
{
var staticPage = "index.html"

internal init(staticPage:String) {
self.staticPage = staticPage
}

func handleRequest(request: WebRequest, response: WebResponse)
{
let file = ContentPage().page(request.documentRoot, pageFile: self.staticPage)

response.appendBodyString(file)
response.requestCompletedCallback()
}
}


The StaticPageHandler class allows you to host static pages with the specified names. “Default” will use index.html, but, in principle, it can be any other page added to the project.

Class HelpHandler
import Foundation
import PerfectLib

class HelpHandler:RequestHandler
{
func handleRequest(request: WebRequest, response: WebResponse)
{
let list = Routing.Routes.description.stringByReplacingString("+h", withString: "")
let html = ContentPage(title:"HELP", body:list).page(request.documentRoot)
response.appendBodyString("\(html)")
response.requestCompletedCallback()
}
}


The HelpHandler class allows you to get a list of commands processed by the server. Some other server environments (for example, MS Framework 4.5.1) provide an autodocumented REST API server. This is very convenient for developers of mobile applications - do not have to tugging server developers for the maintenance / addition of commands.
Update: In the following article, we improved the API documentation mechanism.

CarsJson class
import Foundation
import PerfectLib

class CarsJson:RequestHandler
{
func handleRequest(request: WebRequest, response: WebResponse)
{
let car1:[JSONKey: AnyObject] = ["Wheel":4, "Color":"Black"]
let car2:[JSONKey: AnyObject] = ["Wheel":3, "Color":["mixColor":0xf2f2f2]]
let cars = [car1, car2]
let restResponse = RESTResponse(data:cars)
response.appendBodyBytes(restResponse.array)
response.requestCompletedCallback()
}
}


The CarsJson class demonstrates how GET / POST requests work with a complex structure of returned data. The returned data is represented by an AnyObject object.

ContentPage class
import Foundation

public class ContentPage:NSObject
{
private var title = ""
private var body = ""
private var footer = ""

public init(title:String="", body:String="", footer:String="Copyright (C) 2016 _MY_COMPANY_NAME_. All rights reserved.")
{
self.title = title
self.body = body
self.footer = footer
}

func page(webRoot:String, pageFile:String="template.html") -> String
{
let template = self.loadIndexHtml(webRoot, pageFile:pageFile)
let htmlBody = self.body.stringByReplacingString("\n", withString: "
")

var page = template
page = page.stringByReplacingString("{{TITLE}}", withString: self.title)
page = page.stringByReplacingString("{{BODY}}", withString: htmlBody)
page = page.stringByReplacingString("{{FOOTER}}", withString: self.footer)

return page
}

private func loadIndexHtml(webRoot:String, pageFile:String) -> String {

let fileManager = NSFileManager.defaultManager()
let directory = fileManager.currentDirectoryPath
let path = directory + "/\(webRoot)/\(pageFile)"
do {
return try String(contentsOfFile: path)
} catch {
print("File didn't create")
}

return "404 NOT FOUND"
}
}


A service class that returns a static page and makes a replacement in the appropriate fields. By default, the template is used template.html, but in principle, any other template added to the project can be used.

Class RESTResponse
import Foundation
import PerfectLib

public class RESTResponse
{

public var data:AnyObject = "" // Data from Server to Client
public var status = "" // HTTP status or other code of operation.
public var message = "" // This message Client can show in the Alert View
public var errors = [String]() // All real errors for Console


public init(data:AnyObject="", status:String="200", message:String="", errors:[String]=[])
{
self.data = data
self.status = status
self.message = message
self.errors = errors
}

public var array: [UInt8] {

let result = ["data":self.data, "status":self.status, "message":self.message, "errors":self.errors]
return serialization(result)
}

private func serialization(object:AnyObject) -> [UInt8]
{
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(object, options: NSJSONWritingOptions.PrettyPrinted)
let count = jsonData.length / sizeof(UInt8)
var jsonArray = [UInt8](count: count, repeatedValue: 0)
jsonData.getBytes(&jsonArray, length:count * sizeof(UInt8))
return jsonArray

} catch let error as NSError {
print(error)
}

return [UInt8]()
}
}


The RESTResponse class is a key class using the REST API and, most controversial.
The returned data object has the following JSON wrapper structure:
{
«data»:{…. },
«errors»:[… ],
"message":"",
"status":200
}


1) Some developers categorically do not accept this format. But I didn’t see any convincing arguments against this format of returned data. But, there are certain advantages in a typed format. The “status” field according to historical tradition has the value “200” in case of successful operation. But, it is not directly related to HTTP responses. They can always be subtracted from the request headers, and there they are transferred to the client side by the server environment itself. In the “status” field you can transfer the meaning of meaning within the business logic of a web service, and not the HTTP layer. The “message” field contains the string that the user needs to demonstrate to notify any actions on the server side. For example, information about the session expiration, especially when from the point of view of the client application, everything is going well. The “errors”: [] field is an array of a term that notifies the client of any abnormal situations. It is useful to save and process this information on the client side or send it to a specialized log server for further processing. Finally, the “data” field: {.... } contains a complex data structure - exactly what is expected to be obtained on the client side. The main advantage of this approach is that if the server’s response does not satisfy the given scheme, it can be rejected by the client with a clear conscience. You can read about how to organize it here: habrahabr.ru/post/283012 Those developers who are opposed to the described approach can easily modify the property “array” so that the raw data is returned without the described wrapper.

2) I was somewhat taken aback by the approach that Perfect developers suggested using to return a JSON object. In the proposed tutorial, each JSON object was supposed to be formed by successively building a tree by listing key / value pairs of the form [JSONKey: JSONValue] (which is equivalent to [String: AnyObject]). It is supposed to use the following code:

public var json: String
{
let result = ["data":self.data, "status":self.status, "message":self.message, "errors":self.errors]

let jsonEncoder = JSONEncoder()
do {
let respString = try jsonEncoder.encode(result)
return respString
} catch let error as NSError {
print(error)
}
return ""
}
…..
response.appendBodyString(self.json)
response.requestCompletedCallback()


However, this code is completely inoperative, even with the data that is now contained in the CarsJson class. In addition, if you follow the logic of the tutorial of the creators of Perfect, for each returned object you will have to write your class to encode complex structures. In the variant proposed by me, quite obviously, it is possible to serialize an object of any nesting degree with several lines of code. This will not be difficult to see after starting the service.

Run!

If everything is done correctly, we will get the following page in the browser:
image

Greetings to the server: enter in the browser / hello line
image

Ask the server for information about the commands being executed:
image

Please note that all commands are divided into categories - GET, POST. If you use other predicates, they will also appear in this list.
However, the / list command is simultaneously present in both lists. It was she who added PerfectServerModuleInit to the GET and POST command, but (!) Has different handlers!
If you access / list from the browser’s command line, an index page will be displayed.
image

But if you use a special application (for example, a free Postman), then in the body of the Post request we will have the expected JSON:
image

Please note that JSON is fully consistent with the structure that was formed in the CarsJson class.

But what will happen if you remove the / list command from the GET list?
Routing.Routes["GET", ["/index", "/list"]] = { (_:WebResponse) in return StaticPageHandler(staticPage: "index.html") }

We get:
image

In this simple way, substituting the start page, or any stub page, you can easily disguise the use of any REST command. As a rule, if this is not a service with an open API, at the end of the development, commands of the / help type are deleted or blocked. But you can use this opportunity in another way: display information with details on the GET request with the command, forcing the static page handler to work.

It is completely expected that the / car and / cars commands will return the same structure but in the form of a web page.
image

With a certain skill, creating a REST API will be as easy as creating a ViewController with the necessary navigation.

Extreme fans can try running it all on Linux.

UPDATE:
An explanation appeared on why such a mysterious serialization of JSON responses was used in the tutorial - all NS methods (including NSJSONSerialization) stop working on Ubuntu.

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


All Articles