用來記錄你搜尋過的單字的伺服器
Preparation
- Vapor - 一個(gè)後端的Swift框架
- PostgreSQL - 使用的Database
- owlbot dictionary - 一個(gè)字典API
- Heroku - 可以發(fā)布Server的平臺(tái),以及提供Database
Steps
- 建立一個(gè)伺服器遵班,在自己電腦上運(yùn)作
- 處理HTTP請求
- 跟owlbot字典 要查詢單字
- 儲(chǔ)存新增的單字脱茉,以及查詢到的單字資訊
Step 1 - 建立一個(gè)伺服器坯苹,在自己電腦上運(yùn)作
聽到要用Swift開發(fā)資料庫,不用緊張画机,在我們的這個(gè)時(shí)代,很多事情都有很好引導(dǎo),我們重要的是要學(xué)會(huì)使用現(xiàn)有的工具芋簿。跟著我一步一步完成這個(gè)伺服器吧。
哦璃饱,不只跟著我与斤,在這次關(guān)於Vapor的教程中,我會(huì)參考Ray Wenderlich的教程,每個(gè)影片基本上都在10分鐘之內(nèi)撩穿,快又有內(nèi)容的教學(xué)磷支,值得一看,但我們會(huì)做一些修改以符合我們的功能食寡。
介紹Vapor - 讓我們隨著這個(gè)影片來開始一個(gè)我們的Vapor專案吧雾狈!
首先,去到你的Terminal
vapor new LookupWord
去到該目錄
cd LookupWord
用Vapor執(zhí)行Xcode
vapor xcode
然後稍等一下之後冻河,在對話看選擇y
打開Xcode
Select the `App` scheme to run.
Open Xcode project?
y/n> y
讓我們先來看看這個(gè)Xcode的專案是不是正常運(yùn)作箍邮,重新選擇Target之後執(zhí)行??專案。
喔耶叨叙!It's works!
Step 2 - 處理HTTP請求*
好了锭弊,現(xiàn)在我們需要來寫點(diǎn)程式了,
回到Xocde擂错,然後去到main.swift
你可以按住?Shift
加上 ?Command
然後按O
來快速打開檔案味滞。
好的,到了main.swift
之後钮呀,把程式碼變成像下面呈現(xiàn)的一樣剑鞍。
import Vapor
let drop = Droplet()
drop.get("word", String.self) { req, word in
// We need to add some code later, to get the definition.
// return a json.
return try JSON(node: [
"new word": word
])
}
drop.run()
經(jīng)過改動(dòng)之後,讓我們再看看這個(gè)程序的功能爽醋,在執(zhí)行?? 一次蚁署,
這段程式碼會(huì)處理送到0.0.0.0:8080/word/[the word you want to search]
的請求。
現(xiàn)在這個(gè)階段蚂四,他會(huì)回傳給瀏覽器一個(gè)JSON格式的資訊光戈,例如:
現(xiàn)在用你的瀏覽器(Chrome)前往http://0.0.0.0:8080/word/swift
你會(huì)看到他回傳{"new word":"swift"}
,那這樣就遂赠,對啦久妆!
太好了,這樣我們第二部也完成了跷睦。
Step 3 - 跟owlbot字典 要查詢單字
關(guān)於如何在傳送HTTP的請求, 可以參考這裡筷弦。
在這裡,我們需要從owlbot dictionary拿到字的定義抑诸,所以我們要加一些程式碼來替代// We need to add some code later, to get the definition.烂琴,你的
main.swfit`會(huì)變成:
import Vapor
import Foundation
let drop = Droplet()
drop.get("word", String.self) { req, word in
// get the shared URLSession
let session = URLSession.shared
// define the URL
let wordURLString: String = "https://owlbot.info/api/v1/dictionary/\(word)"
guard let url = URL(string: wordURLString) else {
print("Error: cannot create URL")
return try JSON(node: [
"error": "Error: cannot create URL"
])
}
// create the session task
let task = session.dataTask(with: url, completionHandler: { (data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// transform to JSON object
let json = try? JSONSerialization.jsonObject(with: responseData, options: [])
// cast JSON to Array
guard let jsonArray = json as? [Any] else {
print("Error: wrong data type")
return
}
// get each definition
for jsonDict in jsonArray {
if let wordDict = jsonDict as? [String:String] {
print("\n \(word) \n")
let definition = wordDict["defenition"] ?? "no definition"
let example = wordDict["example"] ?? "no example"
let type = wordDict["type"] ?? "no type"
print("definition : \(definition)")
print("example : \(example)")
print("type : \(type)")
} else {
print("Error: wrong data type")
}
}
})
task.resume()
session.finishTasksAndInvalidate()
return try JSON(node: [
"new word": word
])
}
drop.run()
現(xiàn)在,再回到瀏覽器哼鬓,前往http://0.0.0.0:8080/word/success
监右,然後你會(huì)看到以下的訊息出現(xiàn)在Xcode的Console裡。
GET /word/success
success
definition : the accomplishment of an aim or purpose.
example : "the president had some <b>success in</b> restoring confidence"
type : noun
success
definition : archaic
example : "the good or ill success of their maritime enterprises"
type : noun
Step 4 - 儲(chǔ)存新增的單字异希,以及查詢到的單字資訊
好了健盒!現(xiàn)在我們到了最後一個(gè)步驟了绒瘦,我們將要在本地建立一個(gè)資料庫,先測試扣癣,然後再將我們的伺服器發(fā)佈至Heroku的平臺(tái)惰帽,一個(gè)免費(fèi)提供空間給限制使用量的平臺(tái),他也會(huì)提供資料庫給我們使用父虑。
為了要先設(shè)定資料庫该酗,這邊有另一個(gè)Ray Wenderlich的教學(xué),非常有用也很清楚士嚎。
To install database, we need to open the terminal
First, install Homebrew
首先呜魄,要安裝資料庫,我們會(huì)使用到Homebrew莱衩,如果你已經(jīng)安裝過爵嗅,可以跳過這個(gè)步驟。
先開啟Terminal笨蚁,並安裝Homebrew:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
接著確保它有最新的內(nèi)容
brew update
在安裝postgres資料庫
brew postgre
開始postgres的程式
postgres -D /usr/local/var/postgres
再用你自己的使用者名稱建立一個(gè)資料庫睹晒,whoami
是一個(gè)指令將會(huì)回傳你的使用者名稱
createdb `whoami`
好了,讓我們來看看資料庫建立好了沒
psql
如果你看到以下資訊括细,那就沒問題啦
psql (9.6.1)
Type "help" for help.
lee=#
輸入\q
離開資料庫存取程式伪很。
所以現(xiàn)在,你已經(jīng)有一個(gè)資料庫在你的電腦上工作了奋单,酷吧锉试!
接著要做幾件事情,來設(shè)定Vapor去使用這個(gè)資料庫:
- Import他的相關(guān)程序包
- 設(shè)置Droplet使用這個(gè)資料庫
- 建立一個(gè)配置的檔案
First, check out the provider page
讓我們?nèi)タ匆幌耉aporprovider的頁面
複製這個(gè)連結(jié)加入Package.swift
的檔案裡:
.Package(url: "https://github.com/vapor/postgresql-provider", majorVersion: 1, minor: 0)
所以你的Package.swift
會(huì)看起來像是:
import PackageDescription
let package = Package(
name: "LookupWord",
dependencies: [
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 3),
.Package(url: "https://github.com/vapor/postgresql-provider", majorVersion: 1, minor: 0)
],
exclude: [
"Config",
"Database",
"Localization",
"Public",
"Resources",
"Tests",
]
)
然後我們需要重新生成一次Xcode的相依性览濒,所以在執(zhí)行一次以下命令
vapor xcode
為了要設(shè)定Droplet使用它键痛,我們再一次打開main.swift
,然後import VaporPostgreSQL
import VaporPostgreSQL
並且加上一行程式碼在let drop = Droplet()
之後
try drop.addProvider(VaporPostgreSQL.Provider)
main.swift
會(huì)看起來像是:
import Vapor
import VaporPostgreSQL
import Foundation
let drop = Droplet()
try drop.addProvider(VaporPostgreSQL.Provider)
// test the connection of database
drop.get("version") { req in
if let db = drop.database?.driver as? PostgreSQLDriver {
let version = try db.raw("SELECT version()")
return try JSON(node: version)
} else {
return "No db connection"
}
}
drop.get("word", String.self) { req, word in
// get the shared URLSession
let session = URLSession.shared
// define the URL
let wordURLString: String = "https://owlbot.info/api/v1/dictionary/\(word)"
guard let url = URL(string: wordURLString) else {
print("Error: cannot create URL")
return try JSON(node: [
"error": "Error: cannot create URL"
])
}
// create the session task
let task = session.dataTask(with: url, completionHandler: { (data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// transform to JSON object
let json = try? JSONSerialization.jsonObject(with: responseData, options: [])
// cast JSON to Array
guard let jsonArray = json as? [Any] else {
print("Error: wrong data type")
return
}
// get each definition
for jsonDict in jsonArray {
if let wordDict = jsonDict as? [String:String] {
print("\n \(word) \n")
let definition = wordDict["defenition"] ?? "no definition"
let example = wordDict["example"] ?? "no example"
let type = wordDict["type"] ?? "no type"
print("definition : \(definition)")
print("example : \(example)")
print("type : \(type)")
} else {
print("Error: wrong data type")
}
}
})
task.resume()
session.finishTasksAndInvalidate()
return try JSON(node: [
"new word": word
])
}
drop.run()
接下來匾七,要新增一個(gè)配置檔案,
先建立在這個(gè)專案底下找到config
江兢,
並在這個(gè)資料夾體下載新增一個(gè)叫做secret
的資料夾昨忆,
接著新增一個(gè)檔案叫做postgresql.json
,也就是我們的配置檔案杉允。
講你自己的資料替換"user"還有"database"
{
"host": "127.0.0.1",
"user": "Your user name",
"password": "",
"database": "Your user name",
"port": 5432
}
好了邑贴,那就讓我們再來執(zhí)行一次,如果你前往瀏覽器去到以下的網(wǎng)址
http://0.0.0.0:8080/version
你會(huì)看到這個(gè)資料庫的相關(guān)版本資訊叔磷,你就可以確定你已經(jīng)讓Vapor使用這個(gè)資料庫了拢驾!
[{"version":"PostgreSQL 9.6.1 on x86_64-apple-darwin16.1.0, compiled by Apple LLVM version 8.0.0 (clang-800.0.42.1), 64-bit"}]
現(xiàn)在我們需要加上兩個(gè)Model給我們的資料庫,
前往Terminal, 然後去到你的專案的路徑改基,
接著用以下的指令新增兩個(gè)Model:
touch Sources/App/Models/Word.swift
touch Sources/App/Models/Definition.swift
接著再重新生成一次專案的相依性繁疤,透過指令:
vapor xcode
然後再把相對應(yīng)的程式碼,放到該檔案裡面:
Word.swift
import Vapor
import Fluent
import Foundation
final class Word: Model {
var id: Node?
var exists: Bool = false
var word: String
init(word: String) {
self.id = nil
self.word = word
}
init(node: Node, in context: Context) throws {
id = try node.extract("id")
word = try node.extract("word")
}
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"word": word,
])
}
}
extension Word: Preparation {
static func prepare(_ database: Database) throws {
try database.create("words", closure: { words in
words.id()
words.string("word")
})
}
static func revert(_ database: Database) throws {
try database.delete("words")
}
}
extension Word {
func definitions() throws -> Children<Definition> {
return children()
}
}
Definition.swift
import Vapor
import Fluent
import Foundation
final class Definition: Model {
var id: Node?
var exists: Bool = false
var word_id: Node?
var definition: String
var example: String
var type: String
init(word_id: Node,definition: String, example: String, type: String) {
self.id = nil
self.word_id = word_id
self.definition = definition
self.example = example
self.type = type
}
init(node: Node, in context: Context) throws {
id = try node.extract("id")
word_id = try node.extract("word_id")
definition = try node.extract("definition")
example = try node.extract("example")
type = try node.extract("type")
}
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"word_id": word_id,
"definition": definition,
"example": example,
"type": type,
])
}
}
extension Definition: Preparation {
static func prepare(_ database: Database) throws {
try database.create("definitions", closure: { definitions in
definitions.id()
definitions.parent(Word.self, optional: false, unique: false, default: nil)
definitions.string("definition")
definitions.string("example")
definitions.string("type")
})
}
static func revert(_ database: Database) throws {
try database.delete("definitions")
}
}
extension Definition {
func word() throws -> Parent<Word> {
return try parent(word_id)
}
}
最後,讓我們的Droplet使用這兩個(gè)Model
let drop = Droplet()
try drop.addProvider(VaporPostgreSQL.Provider)
drop.preparations += Word.self
drop.preparations += Definition.self
這樣就可以了稠腊,讓我們來加上一些程式碼來測試一下躁染,把程式碼修改成下的內(nèi)容:
這邊有做一些修改,相較於前面的程式碼架忌,這邊使用
drop.client
去跟字典要資料
import Vapor
import VaporPostgreSQL
import HTTP
let drop = Droplet()
try drop.addProvider(VaporPostgreSQL.Provider)
drop.preparations += Word.self
drop.preparations += Definition.self
// test the connection of database
drop.get("version") { req in
if let db = drop.database?.driver as? PostgreSQLDriver {
let version = try db.raw("SELECT version()")
return try JSON(node: version)
} else {
return "No db connection"
}
}
//Redirect to word
drop.get() { req in
// change to your URL
return Response(redirect: req.uri.appendingPathComponent("word").path)
}
// Show all the words
drop.get("word") { req in
return try JSON(node: Word.all().makeNode())
}
// Show single word
drop.get("word", String.self) { req, wordString in
// Check if the word exist
if let word = try Word.query().filter("word", wordString).first() {
// if exist, show all the definition
return try JSON(node: word.definitions().all().makeNode())
} else {
// create a new word and save
var word = Word(word: wordString)
try word.save()
let wordDictResponse = try drop.client.get("https://owlbot.info/api/v1/dictionary/\(wordString)")
print(wordDictResponse.json?.array ?? "no response")
if let jsonArray = wordDictResponse.json?.array {
for jsonDict in jsonArray {
print(jsonDict)
if let jsonDefinition = jsonDict as? JSON {
let definition = jsonDefinition["defenition"]?.string ?? "no definition"
let example = jsonDefinition["example"]?.string ?? " "
let type = jsonDefinition["type"]?.string ?? "no type"
//create Definition
var newDefinition = Definition(word_id: word.id!, definition: definition, example: example, type: type)
try! newDefinition.save()
}
}
}
return try JSON(node: word.definitions().all().makeNode())
}
}
drop.run()
為了從我們的Server發(fā)出HTTP請求吞彤,我們需要做一些小調(diào)整,前往
Config/clients.json
叹放,然後將verifyHost
以及verifyCertificates
改成false
Warning Note: Use extreme caution when modifying these settings.
Config/clients.json
{
"tls": {
"verifyHost": false,
"verifyCertificates": false,
"certificates": "mozilla"
}
}
Hurrah!! now go test the application again by run it and use browser the lookup a word.
好的饰恕,讓我們再一次測試一下我們的伺服器,重新執(zhí)行??井仰,並且前往瀏覽器:
輸入例如http://0.0.0.0:8080/word/happy
如果一切都正確埋嵌,他將會(huì)回傳:
[{"definition":"feeling or showing pleasure or contentment.","example":"\"Melissa came in looking happy and excited\"","id":1,"type":"adjective","word_id":1},
{"definition":"fortunate and convenient.","example":"\"he had the happy knack of making people like him\"","id":2,"type":"adjective","word_id":1},
{"definition":"informal","example":"\"they tended to be grenade-happy\"","id":3,"type":"adjective","word_id":1}]
恭喜你!糕档!代表你成功啦莉恼!
現(xiàn)在我們擁有我們所需要的功能了:
- 透過對我們的伺服器發(fā)送HTTP請求,查詢字的定義
- 我們的伺服器從owlbot dictionary拿到定義
- 伺服器將新的單字以及定義儲(chǔ)存在資料庫
發(fā)佈至Heroku
不過速那,雖然功能都有了俐银,但現(xiàn)在都還是在本地端執(zhí)行,在我們自己的電腦裡端仰,這樣子的話沒有辦法讓我們用手機(jī)來拿到資料捶惜,
所以我們要把它發(fā)佈到雲(yún)端,
再次借用Ray Wenderlich的發(fā)佈教學(xué)
首先荔烧,先建立Git的repository(讓我們可以記錄開發(fā)檔案的任何變動(dòng))
git init
依照Git的運(yùn)作吱七,新增所有檔案,
git add .
並且commit確認(rèn)發(fā)送鹤竭。
git commit -m "init"
最後踊餐,透過Vapor的指令,上傳到Heroku
vapor heroku init
維持一切預(yù)設(shè)的設(shè)定臀稚,所以被詢問是否要修改都回答N
Would you like to provide a custom Heroku app name?
y/n>n
https://boiling-ocean-81373.herokuapp.com/ | https://git.heroku.com/boiling-ocean-81373.git
Would you like to provide a custom Heroku buildpack?
y/n>n
Setting buildpack...
Are you using a custom Executable name?
y/n>n
Setting procfile...
Committing procfile...
Would you like to push to Heroku now?
y/n>n
You may push to Heroku later using:
git push heroku master
Don't forget to scale up dynos:
heroku ps:scale web=1
接著吝岭,建立雲(yún)端的資料庫
heroku addons:create heroku-postgresql:hobby-dev
執(zhí)行以下指令:
heroku config
會(huì)按看到資料庫的相關(guān)地址,待會(huì)會(huì)用到這個(gè)地址吧寺。
DATABASE_URL: postgres://yfghktvrmwrael:6e48ccb331711093e9ee11bc89d2ef49db4d2bde8a9b596f7b5275e8fb2c3bfc@ec2-107-20-149-243.compute-1.amazonaws.com:5432/d5cds2laqgtqqu
接著窜管,設(shè)定Procfile
檔案裡面,
vi Procfile
按i
來插入編輯
新增這些資訊在最後
--config:servers.default.port=$PORT --config:postgresql.url=$DATABASE_URL
整個(gè)檔案會(huì)看起來像是這樣:
web: App --env=production --workdir="./"
web: App --env=production --workdir=./ --config:servers.default.port=$PORT --config:postgresql.url=$DATABASE_URL
最後回到Xcode
編輯Config/secret/postgresql.json
如下:
根據(jù)你拿到的地址稚机,
{
"url": "postgres://yfghktvrmwrael:6e48ccb331711093e9ee11bc89d2ef49db4d2bde8a9b596f7b5275e8fb2c3bfc@ec2-107-20-149-243.compute-1.amazonaws.com:5432/d5cds2laqgtqqu"
}
然後回到Terminal
commit所以變動(dòng):
git add .
git commit -m "modified Procfile"
最後的最後幕帆,發(fā)佈至Heroku
git push heroku master
接著,等待...
經(jīng)過一段漫長時(shí)間等待之後赖条,讓我們來測試一下失乾!
remote: -----> Compressing...
remote: Done: 64.2M
remote: -----> Launching...
remote: Released v10
remote: https://[your app].herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/[your app].git
1b544e4..6a8e4df master -> master
前往你拿到的網(wǎng)址https://[your app].herokuapp.com/
恭喜你3N酢!仗扬!你現(xiàn)在有一個(gè)自己的伺服器症概,會(huì)記錄所有你問他的單字,
感謝你早芭,和我一起Swift彼城。
更多相關(guān)資訊,請前往我的Blog