【小(xiǎo)編推薦】使用(yòng)≤¥←∑Nickel開(kāi)發Web應用(yòng):從(¶✔cóng)第一(yī)行(xíng)代碼到♦↔(dào)Heroku部署

2015-08-13   | &nbs₹¥‍p; 發布者:梁國(guó)芳<    |   查•≥看(kàn):3320次

IT新聞
 偶爾會(huì)有(yǒu)一(yī)些(xiē)人(rén)咨詢Nickel項目,想要(yào)在深入之前看(kàn)一¶×(yī)些(xiē)真實的(de)Nickel代碼,簡單π✘π☆說(shuō),Nickel是(shì)一(yī)個(gè)用(yòng)Ru↕ ≠​st編寫的(de)Web應用(yòng)服務器(qì)。我們覺<Ω得(de)用(yòng)Nickel寫一(yī)個(gè)簡單→♦↓​的(de)Web應用(yòng)程序,把它部署到(dào₩φ&)Heroku,然後記錄下(xià)來(lái)是§↔(shì)一(yī)個(gè)很(hěn)好(hǎo)的(de)學習≠≤↓(xí)機(jī)會(huì)。

定義應用(yòng)的(de)使用(yòng)範圍

還(hái)有(yǒu)什(shén)麽比現(xiàn)學<"現(xiàn)用(yòng)更好(hǎo)的(de)學習(xí→★♥✘)方式呢(ne)?立即拿(ná)兩個(gè)項目練手吧(ba)!在優秀貢£↑↑¶獻者的(de)幫助下(xià),我們開(kāi)發了(le)Clog,±‌它是(shì)一(yī)個(gè)根據Git曆史提交生(s★βΩ∞hēng)成美(měi)觀的(de)更新日(rì)志(zhì)δ"γ£(changelog)的(de)小(xiǎo)工(‍≥gōng)具。Clog按照(zhào)Angular的(de)約定格式來(láαε★i)解析提交信息,這(zhè)種約定格式用(yòng)于大(®±•εdà)量流行(xíng)項目,諸如(rú£α):Angular、angular-translat‍♥e 、Hoodie 、Nickel 、Clap.rs等等。C$ log分(fēn)支于Node.js,開✔¶(kāi)始以傳統的(de)更新日(rì)志(zhì)項目為(wèi)基礎,≠↑後來(lái)有(yǒu)了(le)些(xiē)自®• (zì)己的(de)理(lǐ)念。

讓我們開(kāi)發一(yī)個(gè)網站(zhàn),從(có♥©πng)浏覽器(qì)就(jiù)能(néng)為(ε∞→wèi)任何GitHub公共倉庫生(shēng)成格式₩§♦良好(hǎo)的(de)更新日(rì)志(z¥↔₹↕hì),我們要(yào)在盡可(kě)能(néng)保持簡單的(d¥♣e)同時(shí)專注于重要(yào)的(de)部分(fēn)。

開(kāi)始我們的(de)Nickel應用(yòng)

本文(wén)假定你(nǐ)對(duì)Ruδ≈σst有(yǒu)基本的(de)了(le)解,如(rú)果你(nǐ)之前£↑™φ從(cóng)未用(yòng)Rust編寫任何東(d​✔ōng)西(xī),官方《Rust Book》的(de)入門(mén)部分(fēn)能(néng)回答(dá)所有(yǒu)的(de)基本問(w∏©®èn)題。

啓動一(yī)個(gè)Rust項目最簡單的(de)方式就(jiù)是(shìσ×™)使用(yòng)官方的(de)包管理(lǐ)器(qì)Cargo。

cargo new clog-website --bin

這(zhè)條命令會(huì)創建一(yī)個(gè)新↑✘的(de)目錄clog-website,裡(lǐ)面包 ↕含一(yī)個(gè)“Hello World&rdq∑≤uo;應用(yòng),我們可(kě)以通(tōng)≥β過以下(xià)的(de)命令編譯和(hé)運行(xín↔↑∏↔g)這(zhè)個(gè)應用(yòng):

cargo run

為(wèi)了(le)使用(yòng)Nickel,首先我們‍λ需要(yào)在Cargo.toml将其添為(wèi)依賴項,此時(shí€Ω←")Cargo.toml應該是(shì)這(zhè)樣的(de):

 

[package]
name = "clog-webs★πite"
version = " ×&0.1.0"
authors = ["Y&±☆₩our Name <your@name.com>™♦γ;"]

[depende¥€λ÷ncies]
nickel = "*"
現(xiàn)在,Nickel已經添加為(wèi)一(yī)個(gα☆$←è)依賴項,我們嘗試讓服務器(qì)對(duì)于任何請(qǐng)求都(d 'ōu)返回一(yī)個(gè)“Hello World&rdq'≠uo;,使用(yòng)下(xià)面的(de)代碼替換main.rs♥‍​£中的(de)內(nèi)容:

 

#[macro_use]
extern crate nickel;

÷'♥
use nickel::Nickel;

fn main(✘∏<★) {
    let mut server = Nickel:α‌↓:new();
    server.get(&quo≥✘ t;**", middleware!(&quΩ★​ot;Hello from Nickel"£>;));
    server.listen("✔'127.0.0.1:6767");
}

然而,當我們嘗試使用(yòng)“Car ↑‍go run”命令運行(xíng)應≠φ用(yòng)程序的(de)時(shí)候,出現(xi♥>±àn)了(le)下(xià)面的(de)錯(cuò)✔σ"₹誤:

 

$ cargo run
   Compiling clog-wβ₹ebsite v0.1.0 (file:///Users/cbur∞≈λgdorf/Documents/hacking/clog-websiteφ& )
src/main.rs:9:12: 9:5 ↔★★5 error: no method named `get` foun≤‍ d for type `nickel::nickel::Nickel ↓` in the current scope
sα₩≈Ωrc/main.rs:9     serv✘∑λ♠er.get("**", mid∑≥ dleware!("Hello from Ni↑©ckel"));
                ≠∏>         ^~~~~~~~~~~~~±•♠~~~~~~~~~~~~~~~~~~~~~~~~~~~~~φ≤♦~
src/main.rs:9:12: 9:55 help: it≤¥§ems from traits can only be used if ©®♣the trait is in scope; the f♣δ♦‍ollowing trait is implemented but not ∞α±in scope, perhaps add a ×γ±≈`use` for it:
src/main.rs:9:12: 9$←≠:55 help: candidate #πε☆1: use `nickel::router::http_router​ ‌::HttpRouter`
其中的(de)原因很(hěn)簡單,雖然HTTP動詞處理(lǐ)器(qì)(HTTP Verb hand↕↕ler)已經注冊了(le)get,、put、 ∞λ‌post、 delete這(zhè)些(xiē)處理(lǐ)方法,且似乎≤→"直接存在于Nickel::()new返回的(de)Nickel對¥↑≠π(duì)象中,但(dàn)實際上(shàng),這(φ£♠ zhè)些(xiē)方法都(dōu)是(shì)Htt"‌pRouter特性(trait)在Nickel對(duì)φ 象中的(de)實現(xiàn)。

 

特性 (traits) 是(shì)Rust語言中一(yī∞♣εσ)個(gè)非常強大(dà)的(de)概念,詳&‍細解釋它超出了(le)本文(wén)的(de•λ★∏)範圍,如(rú)果你(nǐ)還(hái)沒有(yǒu)完全領會(huì​↕₹)它們,我建議(yì)你(nǐ)從(cóng)頭讀(♠‍₩βdú)一(yī)遍官方《Rust Book》的(de)特性 (traits) ×↕章(zhāng)節。

幸運的(de)是(shì)Rust的(de)編譯♥≥器(qì)足夠智能(néng),這(zhè<≥γ)裡(lǐ)給了(le)我們正确的(de)提示,我們需要(yà↓'≤o)把HttpRouter特性引用(yòng)進當前作(zuò)用(yòng)₩★域,因為(wèi)Nickel在頂層模塊中暴露出Ht♠§tpRouter特性,所以我們隻需簡單更改,就'→↔(jiù)能(néng)将其引入作(zuò)用(yòn ♣g)域。

use nickel::{ Nickel, HttpRoute→®σ£r };

一(yī)旦我們将其引進作(zuò)用(yòng)域β♠π,Nickel對(duì)象的(de)方法就(jiù)變得(de)可(kě)用Ωε(yòng),你(nǐ)可(kě)以認為(wèi)它是(←₽§shì)擴展方法。

我們修複這(zhè)個(gè)問(wèn)題以後,再通(t★∞ōng)過“Cargo run”命φ↓±令運行(xíng)我們的(de)服務器(qì),在浏覽器(qλ"¥ì)中打開(kāi)http://127.0.0.1:6767就(jiù)可(kě)以看(kàn)到(dào)&ldqu₽♦o;Hello from Nickel”。這(zhè)不 ¥(bù)會(huì)太驚奇,對(duì)嗎(ma)?

生(shēng)成更新日(rì)志(zhì)

為(wèi)了(le)生(shēng)成我們的(de)第一(yī)份更☆↔→≥新日(rì)志(zhì),需要(yào)做(zuò)三件 ≥(jiàn)事(shì):

為(wèi)了(le)實現(xiàn)下(xià)載部分(fēn),我們創'≠β建一(yī)個(gè)名為(wèi)git.rs的(de)文(wén)件(jδ☆'£iàn),包含以下(xià)的(de)內(nèi)容:

use std::process::Comm↑•§and;
use std::io::{Error, Ω☆λErrorKind};


pub fn♥  clone (repo_url: &a↕>mp;str, into_directory: &s≤♥tr) -> Result<String, Erro ₽$r> {
    let output = try!≥≠(Command::new("gi¶₩≈♥t")
                    .arg(&→→σquot;clone")
       ₩ε             .arg(repo_url)
 ★₽'                   .arg(into_director'¥™y)
                    .output())♥β'♦;

    match output.status.succe‌<≤ss() {
        true => Ok(String≥¥Ω→::from_utf8_lossy(&output.stdout).α↑∞ into_owned()),
        falsφ↑β×e => Err(Error::new(ErrorKindβπ​::Other, format!("{}", Stφα≥↑ring::from_utf8_lossy(λ£​α&output.stderr))))
    }
}'  

我們不(bù)會(huì)逐行(xíng)講解代碼,通(tōng↑≈•)過方法标記,我們就(jiù)可(kě)以清楚的(de)理(lǐ)解π £,第一(yī)個(gè)參數(shù)指定要(yào)克隆Git倉庫的(de)↕×π&URI,第二個(gè)參數(shù)指定存放(fàng)路(l£<☆₹ù)徑。返回一(yī)個(gè)Resu↔δ✔lt<String, Error> ‍,要(yào)麽是(shì)一(yī)個(gè)發送到(dào)标₹♣準輸出(stdout)的(de)攜帶信息的(de)字δ∏符串(String),要(yào)麽是(shì)✘‌一(yī)個(gè)發送到(dào)标準錯(cuò>®)誤(stderr)的(de)攜帶信息的(de)字符串錯(cuò)誤(Err↕&∏or)。

在我們使用(yòng)之前,需要(yào)編寫 £與Clog交互的(de)代碼,我們創建一±✔₹←(yī)個(gè)名為(wèi)clog€↕_interop.rs的(de)文(wén)件(j≈φφiàn),包含以下(xià)內(nèi)容:

use std::fs::{self, Fi<'ε≠le};
use std::path::Path;
use ♣α≤std::io::{Read};
use cl♣>"αog::Clog;

pub fn generate_changelog☆¶ (repository: &str, rep‌‌ λo_url: &str) -> Str₽♥Ω•ing {
    let mut clog = C÷‍•log::with_dir(repository).unwra λ☆p_or_else(|e| {
        fs::remoλ₹ve_dir_all(repository).ok() λ∞;
        panic!("Failed to >λπγclone repository: {}", e);
  ↕‍σ  });
    let changelog_file_↔<$₩name = format!("changelog_≥€∑β{}.md", repository);
  ≈    clog.repository(repo_url);
    ↕∏↑clog.write_changelog_to(Path‍¥::new(repository).join(&changel∞₩<og_file_name));

    let mut conten♣₩☆₽ts = String::new();

    File::op♥©en(&Path::new(repository) ¶.join(&changelog_fi¥∑≥le_name))
        .map(|‍← ✘mut f| f.read_to_string(&aδ♣↑mp;mut contents).ok()).ok();

    fs™$::remove_dir_all(repository).ok()₽>$π;

    contents
}

用(yòng)這(zhè)個(gè)方法獲取一(yī)個(gè)&a←®₹mp;str類型的(de)本地(dì)Git倉©↕±©庫路(lù)徑和(hé)一(yī)個(g ←è)GitHub的(de)URL,Clog生(shē≈←✔&ng)成鏈接時(shí)需要(yào)知(z  ™hī)道(dào)GitHub的(de)URL,這(zhè)個(gè)方法會₩ ‌(huì)生(shēng)産markdown格式∞>σ&的(de)更新日(rì)志(zhì),通(t§∞↔ōng)過String類型返回,并且會(huì)在π 生(shēng)成更新日(rì)志(zh©÷ì)後馬上(shàng)删除本地(dì)Git庫。

直到(dào)現(xiàn)在,我們構建了(le)基本代碼塊來☆β→®(lái)生(shēng)成我們的(de)第一(yī)個​‍≤α(gè)更新日(rì)志(zhì),main.rs文(wΩ‍én)件(jiàn)看(kàn)起來(lái)像這(zhè)樣ε€:

#[macro_use]
extern crate nickel;
ext$¥φern crate clog;

use&®φ nickel::{ Nickel, HttpRout∞γ≥πer };

mod git;
mod clog_interop;β∏​

fn main() {
    let <♥φ"mut server = Nickel::new();

    let ±Ω$§repo_name = "some​→₹-unique-id";
    let repo_uri<Ω§× = "https://githu✘₩$≠b.com/angular/angular";

   "γ$β git::clone(repo_uri, repo_name)₹♦.ok();

    let cha'↔♦ngelog = clog_intero₽•​•p::generate_changelog(repo_nδ∑₩ame, repo_uri);

   ✘ ♣ server.get("**", middl÷↕"eware!(&changelog as σφ↓&str));
    server.liλ✔←λsten("127.0.0.1:6767&quo©♦÷t;);
}

此處對(duì)代碼做(zuò)一(yī)$¶些(xiē)說(shuō)明(míng),為(wèi★←)了(le)使用(yòng)Clog,我們需要•<±(yào)像之前添加Nickel一(yī)樣将Clog添加到(dào)♦♠λCargo.toml,然後添加“exter€←Ωn crate clog”語句。我們還(há✔♣→↑i)需要(yào)添加git模塊和(hé)clog_interop模塊,為(w ≈±∏èi)了(le)讓編譯器(qì)能(néng)✘♣ 包含這(zhè)些(xiē)模塊,我們需要(yào)把這(zhè)兩個(gè÷☆)Rust文(wén)件(jiàn)放(fàng)進我們的(de)工(g±π¶≠ōng)程目錄中。

其餘的(de)相(xiàng)當明(míng)了(l✔✘×e),我們硬編碼(hardcode)repo_uri來($± ‍lái)獲取Angular 2的(de)儲存庫,克隆到(dào)本地(dì)&→♥ldquo;some-uniqe-id”目錄。這 ×(zhè)些(xiē)下(xià)一(yī)步做(zuò)好(hσ§ǎo)準備。

最有(yǒu)趣的(de)部分(fēn)是☆↑(shì)使用(yòng)middlewa¶×¶re!對(duì)每次請(qǐng)求都(dōu)把&a∏→≥mp;changelog轉換為(wèi)&a≈∑≥<mp;str然後返回。

很(hěn)多(duō)新人(rén)在這(zhè$φ)裡(lǐ)會(huì)有(yǒu)共同的(de←βΩ)疑惑,但(dàn)Nickel團隊的(de)Ryman在此 做(zuò)了(le)一(yī)個(gè)很(hěn)好(π‌hǎo)的(de)解釋。

大(dà)意是(shì)程序會(huì)在每一(♦"yī)個(gè)請(qǐng)求到(dào)來(lái)時(shí)被調λ§₩★用(yòng),而你(nǐ)不(bù)能(néng)轉移(β®所有(yǒu)權)String,因為(wèi)隻能(nén®<g)被轉移一(yī)次。

如(rú)果我們重新運行(xíng)服務器(qì),并在浏覽器(qì)中打開λΩ&(kāi)網頁,将看(kàn)到(dào)這(zhè)個(gè‌♦♥®)樣子(zǐ):

萬歲!這(zhè)是(shì)Angular 2項目markdown ‍€∞格式的(de)更新日(rì)志(zhì)。

使用(yòng)JSON數(shù)據

現(xiàn)在我們已經用(yòng)服務器(qì)克隆代•×$碼庫,生(shēng)成markdown格式的(de)更新日(rì)志(zhì&δ♥),然後返回給我們的(de)浏覽器(qì),目•♥₹前還(hái)不(bù)錯(cuò)。為(wèi)了(le)使其更好(hǎ₹♥≈o)更靈活,我們需要(yào)創建合适的(π¶‍de)JSON API讓前端使用(yòng)。

我們勾勒一(yī)下(xià)JSON請(qǐng)求和(hé)響應≠φ的(de)樣子(zǐ):

Request Object

{
    "resposito£÷ry": "https://gith≥><ub.com/angular/angular"
}

目前,我們除了(le)repository之外(wài)不(λγbù)需要(yào)其他(tā)任何東(dōng÷€φ♠)西(xī)。然而,我們以後可(kě)能(néng)添加諸如(β♠↑rú)version_name和(hé)σ subtitle來(lái)為(wèi)前端暴露其他★σ×(tā)功能(néng)。

Response Object

{
    "changelog": &q÷©Ω☆uot;the formatted markdown st♥λ£ring",
    "error&qu♥δot;: "foo"
} •

同樣也(yě)讓響應保持簡單,除了(le)changelog之外(₽σ₽wài)和(hé)防止非正常工(gōng)作(πα ↑zuò)的(de)error信息之外(wài)≠ §,不(bù)再需要(yào)其他(tā)東(dōng)西(xδ©↑&ī)。

為(wèi)了(le)接受和(hé)返回JSON對(duì)象,首先需要(yà↓←≥​o)創建新的(de)structs,命名為(wèi)ClogC★→​onfig和(hé)ClogResult,并且分(fēn)别創 ©>建文(wén)件(jiàn)clog_config.rs和(hé)clog_®εresult.rs。

clog_config.rs

#[derive(RustcDecodable, RustcEn'<codable)]
pub struct ClogConfig {
‍     pub repository: St↓®±ring,
}

clog_result.rs

#[derive(RustcDecodable, RustcE∞§ncodable)]
pub struct ClogResult {
∑'✘γ
    pub changelog: String,
    pub eφΩ♠rror: String
}

為(wèi)了(le)避免進行(xíng)手動J↑ SON解析和(hé)格式化(huà)代碼,可(kě)以讓Rust≤±€為(wèi)我們自(zì)動實現(xiàn≈β♠)RustcDecodable 和(hé) Rustc®‍$♥Encodable特性。

有(yǒu)了(le)這(zhè)兩個(gè)結構,我們來(lá→<i)編寫能(néng)接受和(hé)返回JSON結構的(de)POST處理(l‍★ǐ)器(qì)。

server.post("/generate&•<₽quot;, middleware! { |request, responsβ§"↓e|

    let clog_conγ"'¥fig = request.json_as::<ClogConfig&¶¥∞gt;().unwrap();

    let res♣←✘<ult = if let Err(err) = ¥ ≥git::clone(&clog_config.rep $αository, &repo_name) {
  ♥₹™      ClogResult {
     ×π       changelog: "".♣← to_owned(),
            error: err.de₽✘✔ scription().to_owned(<δ≈≠),
        }
    } else {
     ∏ ©   let changelog = clog_interop::gen↓₽✔ erate_changelog(&repo_name, &cl​↔≈og_config.repository);

        C≠ logResult {
        ‌λ    changelog: changelo₹™↑βg,
            error: "✔↓".to_owned()
        }
 Ω∏≠γ   };

    json::encodφ"∞×e(&result).unwrap()
});

我們來(lái)看(kàn)看(kàn)這(zhè)個(gè)代∑<β碼,使用(yòng)server.post注冊一(±→yī)個(gè)處理(lǐ)器(qì)來(l"€±→ái)應答(dá)/generate路(₽♥'εlù)由下(xià)的(de)POST請(qǐng)求。之前我們用(↑ σδyòng)一(yī)個(gè)簡單的(d​ e)字符串來(lái)使用(yòng)middle‌ε∏'ware宏,現(xiàn)在我們使用(yòng)的(de)是(sh♠→ ♠ì)塊語法的(de)形式,讓處理(lǐ)器(qì)能(néng)訪問(≤¶wèn)request和(hé)response對(♠§↓duì)象。

Nickel支持使用(yòng)json_as方法來(✘γ☆πlái)解析JSON體(tǐ)。美(měi)中不(bù)足的(de)是(s €¥hì),和(hé)HttpRouter一(yī)樣,使用(yòng)它的(¥>↓de)功能(néng)時(shí)需要(&↔<÷yào)将JsonBody特性引入作(zuò)用(yòng)域>∑α↔目前還(hái)不(bù)要(yào)分(fēn)心,稍後¶®∏再看(kàn)需要(yào)引入的(de)所有(yǒu)模塊。

我們将JSON解析為(wèi)ClogConfig對✘Ω§(duì)象的(de)實例,由clog_config持有(yǒu)這(zh₽₽✘∏è)個(gè)實例,進一(yī)步嘗試克隆這(zhè)個(gè)給定的(d¶®↑e)repository。現(xiàn)在沒有(yǒu)再将一§♦≥★(yī)個(gè)庫硬編碼進去(qù),我們α¥需要(yào)規劃因為(wèi)拼寫錯(cu'δò)誤或其他(tā)原因導緻克隆一(yī)個($Ωgè)庫失敗之後的(de)解決方法。如(ε λrú)果克隆失敗,我們簡單的(de)設置ch→"↔ angelog為(wèi)空(kōng)字符串≥∞,并将error設置為(wèi)返回的(de)錯(cuò)誤信息。

如(rú)果克隆成功,我們如(rú)之前那(nà)樣生(shēng)成更新日♥∏(rì)志(zhì)從(cóng)而設置ch÷λ§angelog和(hé)error,注意Rust中if/else結構也↑¥↓α(yě)是(shì)基于表達式,所以我們才可(kě)↔¶↕以使用(yòng)如(rú)此簡潔的(de)寫法,将let result  <•= 置于if之前。

同樣重要(yào)的(de)是(shì),在返回給調用(yòng)者之前,★≥$我們需要(yào)使用(yòng)json::encode将C​Ω&​logResult對(duì)象轉換成JSON格式。

這(zhè)組新代碼還(hái)少(shǎo)了(le)很(hěn)♥♦>重要(yào)的(de)一(yī)部分( ‍₩fēn),我們必須調整導入的(de)模塊和(hé)Cargo.toml中的(d↑₹₹✔e)依賴項。

#[macro_use]
extern crate nic♣₽ ∏kel;
extern crate clog;
extern crate →≠•♣rustc_serialize;

u↕'π♠se nickel::{ Nickel, JsonBody, ✘$ ↔HttpRouter };
use clog_config::ClogCon×≈>fig;
use clog_result::ClogResult;
u λ€€se rustc_serialize::j$≤son;
use std::error::Error;

mod git®±≤•;
mod clog_interop;
mo→‍d clog_config;
mod clog_result;

最讓人(rén)詫異的(de)是(shì)目前對(duì)JSON的(dπ¶>e)支持沒有(yǒu)放(fàng)到(dào)‌±∑™标準庫,而是(shì)被拉取到(dào)rustc-serβ∞♦¶ialize庫。另外(wài),在Rust代碼中使用(yòng)下(x↑ε≈ià)劃線時(shí),還(hái)要(yào)在Carβλ✔go.toml中增加rustc-serialize = &ldquδ÷σo;*”。

現(xiàn)在可(kě)以用(yòng)curl≈₩嘗試我們的(de)API了(le)。

curl 'http://127.0.0.1:67‍&67/generate' -H 'Cache-Control: no-caΩ≈​βche' -H 'Content-Type: application/json"×;charset=UTF-8' --data-&€&binary {\n  "repos​¥'itory": "https://github.co®$​∏m/thoughtram/clog"\n}' --com∏≥pressed

目前還(hái)是(shì)有(yǒu)一(yī)個(ε→≠gè)很(hěn)大(dà)的(de)缺陷,對(duì)于每一(yī)≥¶個(gè)請(qǐng)求,我們克隆到(dào)一(yī)個(gè)↕<叫some-uniqe-id的(de)目錄中去(qù),但(dàn​β≈)如(rú)果API同時(shí)接受兩個(gè<δ)請(qǐng)求,程序就(jiù)會(huì) φ出問(wèn)題。為(wèi)了(le)讓我們克隆的(de)↔>目錄使用(yòng)到(dào)真正唯一(yī)的(de)名字,我們來(lá≥♥πi)修改代碼,添加一(yī)個(gè)名為(wèiβ≤)uuid的(de)包(crate)。

uuid包添加以後,修複這(zhè)個(gè)問•£≈♦(wèn)題變得(de)非常簡單,隻需要(↓€♣yào)将處理(lǐ)器(qì)內(nèi)部的(de)re¥±←po_name設置一(yī)下(xià)。

let repo_name = Uuid::new_v4().↑ ♥to_string();

我們先不(bù)管這(zhè)裡(lǐ)引入α¶>Ω的(de)改變,如(rú)果你(nǐ)有(yǒu)什♦♦(shén)麽問(wèn)題,請(qǐng)随意跳(tiào)轉到(d‌∑φαào)最終方案 

随著(zhe)這(zhè)個(gè)變化(huà),我們的(d©₩ e)API已經準備好(hǎo)投入使用(yòn ™↔g)。我們可(kě)以向提高(gāo)工(gōng) ​✔'效的(de)方向做(zuò)一(yī)步改進,通(tōng)過σ>λ$解析器(qì)分(fēn)析庫名而避免鍵入整個(gè≥↑₩™)URL。這(zhè)将是(shì)最終✔ 的(de)解決辦法,但(dàn)我不(bù)打算(suàn)在這(zhè)₹≤裡(lǐ)涉及,畢竟它是(shì)一(y≈♦≈ī)個(gè)非常簡單的(de)步驟。

為(wèi)前端服務

現(xiàn)在我們有(yǒu)了(le)API,隻需®≤↕±要(yào)一(yī)個(gè)簡單的(de)前端提供最基本★₩的(de)使用(yòng)。如(rú)果我們的(de)目标是(shì‍<)合适的(de)可(kě)拓展方案,我們服務∑ε₽•的(de)前端可(kě)能(néng)不(bù)會(huì)和(hé)提©♦☆✘供API服務的(de)服務器(qì)是(shì)同一( ‌yī)個(gè)。我們這(zhè)篇博文(wén)的(de)目的(de)§®♣是(shì)展示Nickel的(de)功能(nén®ααg),這(zhè)裡(lǐ)僅僅為(wèi)了(le)保證項♥π目的(de)完整性。

對(duì)于前端,我們不(bù)會(huì)涉≠✘及得(de)像後端那(nà)麽深,跳(tiào)轉到(dào ©÷₩)GItHub的(de)庫 , 這(zhè)個(gè)庫 包含了(le)這(zhè)篇文(wén)章(zhāng)的(de)所有(÷<yǒu)代碼。

讓我們姑且認為(wèi)我們正在建設一(yī)個(gè)簡單的♥¶(de)Web應用(yòng)程序,一(y±×"αī)個(gè)為(wèi)用(yòng)戶輸入GitHub上(shσ‌σβàng)倉庫網址的(de)文(wén)本框,和(hé)一(yī)個(g¶α₹←è)從(cóng)我們構建的(de)API請(qǐng)α₹↕‌求變更的(de)按鈕。

為(wèi)了(le)服務于前端,我們創建一(yī)個(gè)assets目">£錄,其中包含css、js、templates子( ♠₩<zǐ)目錄,将index.html放(fà¶₹λng)置在templates目錄,其中包含的(de)Javascr‌‌✘<ipt文(wén)件(jiàn)和(hé)CS€ S文(wén)件(jiàn)分(fēn)别放(fàng)置js和(hé)css‌×↕•目錄。

為(wèi)了(le)能(néng)為(wèi)前端服務,我們還(hái)必'←須告訴Nickel那(nà)些(xiē)靜(jìng)态服務器(qì"σ≠)文(wén)件(jiàn)。

server.utilize(StaticFilesHan→★dler::new("assets"✘>));
server.utilize(StaticFilesH♦↑✘andler::new("assets/temα§​‍plates"));

我們分(fēn)别挂載assets和(hé)assets/φ♠≥♦templates,以便在頂層目錄中訪問(wèn)i♣‌ndex.html,但(dàn)是(shì)Ja₽¶βvascript和(hé)CSS還(hái)是€≥↔₽(shì)通(tōng)過子(zǐ)目錄訪問(wèn)。π§

現(xiàn)在有(yǒu)一(yī)個(gè)可(↑Ωkě)以運行(xíng)的(de)網站(λ∞zhàn)了(le)。

把應用(yòng)部署到(dào)Heroku

但(dàn)是(shì)現(xiàn)在我們的(de)應用(yòng)↔₹僅僅很(hěn)好(hǎo)的(de)運行(xíng)在α©¥™我們的(de)開(kāi)發機(jī)上(shàng),如(βαrú)果能(néng)給世界看(kàn)看(kàn)不(bù)σβ→≥是(shì)更棒嗎(ma)?我們把他(tā<π )托管到(dào)Heroku上(shàng),托管一(yī)個(g€♠♥×è)Nickel應用(yòng)到(dào)Heroku非常簡單,如(rú)λ♣✔>果你(nǐ)之前從(cóng)未用(yòng)過Heroku,你(nǐ)需要☆β(yào)在他(tā)們的(de)網站(zhàn)上↔→'∑(shàng)注冊,并下(xià)載下(xià)一(yī)步将用(yòng)<≈到(dào)的(de)Heroku CLI tool

一(yī)旦你(nǐ)安裝了(le)Heroku C​✔LI并登錄,你(nǐ)可(kě)以通(tōng'£§£)過下(xià)面的(de)命令從(cóng)現(xiàn)有(y¥☆δǒu)資源庫創建應用(yòng)程序。

heroku create demo-clog-website --buil↑ασδdpack <A href="https://github.c•§¶om/emk/heroku-buildpack-rust.gi&qu☆≠ot;>https://github.com/e✔≠mk/heroku-buildpack-rust.gi</A>•λ£

注意:Heroku将會(huì)使用(yòng)heroku creat¶€♦e命令的(de)第一(yī)個(gè)參數↑ ∞'(shù)作(zuò)為(wèi)站(zhàn)點的(de)子(  ∞zǐ)域名。這(zhè)裡(lǐ)是(shì↕<∑€)http://demo-clog-website‌∑≠±.herokuapp.com.

這(zhè)将為(wèi)你(nǐ)的(de)庫增加一(yī)個(gè‍Ω)叫做(zuò)heroku的(de)Git遠(yuǎn)↑£™程分(fēn)支。部署站(zhàn)點僅僅是(shì)運∞≥φ行(xíng)git push heroku master命令那(nà)麽ε↔♠α簡單。

$ git push heroku master
Couπ≈>♠nting objects: 28, done.
De≠  ‌lta compression using up to ≥¶8 threads.
Compressing obj♠≤≥∑ects: 100% (24/24), done.
Writiλεng objects: 100% (28/28), 5¥λ.73 KiB | 0 bytes/s, done.
Total 28 ₹‌ (delta 5), reused 0 (delt¥₽↔a 0)
remote: Compressi↑¶$ ng source files... do€λ₩ne.
remote: Building source:
remote: ••★§
remote: -----> Fetching custom‍≥±¥ git buildpack... done
remote: 
β©"
remote:  !     Push↕< rejected, no Cedar-suppo€✔rted app detected
remote: HINT: This β≥↕occurs when Heroku cannot detect the ∑₹Ω≠buildpack
remote:  ₹•     to use for this application a§™δutomatically.
remote: Seeβ←§< <a href="https://devc©£enter.heroku.com/articles/buildpaΩ$♦↕cks">https:/∏↕/devcenter.heroku.com/articles/bui$∏ ©ldpacks</a>
remote: 
remote:>♣₩ Verifying deploy....
∏∞remote: 
remote: !   Push rejected to ®₹↓¥demo-clog-website.
remote: 
T&•εo <a href="http₩≠↕×s://git.heroku.com/demo-clog-we♦≥↑bsite.git">htt"•✔ps://git.heroku.com/d♥≈emo-clog-website.git</a>
δ&
 ! [remote rejected] master -> masλ☆€•ter (pre-receive hook decl₩×ined)
error: failed to push some refs ∞‍♠ to 'https://git.heroku.com/demo∏<¥←-clog-website.git'

噢!我們的(de)應用(yòng)好(hǎo)像缺少≠≠€(shǎo)東(dōng)西(xī),H ✘§₩eroku拒絕構建這(zhè)個(gè)應用(yòng)。H↔→®σeroku能(néng)托管各種不(bù)同技(β♠jì)術(shù)棧的(de)應用(yòng→≥),通(tōng)過指定構建包(buildpack)來(lái)配置服務器★φ(qì),使其工(gōng)作(zuò)。我們創建應用(yòng)↓¥•×的(de)時(shí)候,設置的(de)是(shì)一(yα&​βī)個(gè)非官方的(de)Rust構建包(但πΩ(dàn)是(shì)可(kě)用(yò‍♥←ng)),但(dàn)是(shì)還(hái)缺少(shǎo)兩種重要<✘λ (yào)的(de)文(wén)件(jiàn),♠÷<ε用(yòng)來(lái)提示Heroku應該如(rú₩γ)何對(duì)待我們的(de)程序。

RustConfig

VERSION="1.1.0"‍®;

RustConfig文(wén)件(jiàα©±εn)告訴構建包我們想用(yòng)哪個(gè)版本的(de)γ☆ Rust編譯器(qì)構建我們的(de)應用(yòng)。

Procfile

web: ./target/release/clog-websi₩  te

Procfile告訴Heroku調用(yòng)哪個(gè)命令來(l ♣Ωái)開(kāi)始這(zhè)個(gè)進程,因為(wèi)Cargo®π✘把可(kě)執行(xíng)文(wén)件€↔→♣(jiàn)放(fàng)在target/release/our-app-®σname,所以我們進行(xíng)對(du©‍Ωì)應的(de)設置。

創建并提交這(zhè)兩個(gè)文(wén)件(j®γ iàn)到(dào)我們的(de)庫之後,再次通(tōng)過git push‌£↓φ heroku master部署站(zh‍<↓γàn)點,這(zhè)次部署能(néng)正常運行(xín®☆ ‌g),因此我們來(lái)檢查一(yī)下(xià)在線站(zhà$§₹n)點。

站(zhàn)點隻給出一(yī)個(gè)Heroku♣®γ通(tōng)用(yòng)的(de)錯(cuò)誤頁面,♦"↕©我們使用(yòng)heroku logs命令檢查一(yī)下(xià)輸$♥出。

heroku[web.1]: Starting process withσπ command `./target/release/c→←γ log-website`
app[web.×¶1]: Listening on <a href="htt∏₩& p://127.0.0.1:6767">http://12®σ©↓7.0.0.1:6767</a>
app[web.1∏​✘★]: Ctrl-C to shutdown servγ'$er
heroku[web.1]: Error R10 (B↔φ£oot timeout) -> Web proc€β∞ess failed to bind to $PORT within 60‍​↑‌ seconds of launch
heroku[web.1]: ✘®Stopping process with SIGKILL
heroku[wε&σ&eb.1]: State changed from sta ✔rting to crashed
heroku[web.1]: Statσ≈&e changed from crashed to starting
he£≥roku[web.1]: Process exited wit≠£®h status 137

從(cóng)日(rì)志(zhì)中,♦​可(kě)以清晰的(de)看(kàn)到(dào),服務器(qì€φ♣)成功啓動,但(dàn)是(shì)在60秒(miǎ↔↔<¥o)後因為(wèi)綁定到(dào)$PORT端口不(bù)成功,超時(s™Ωhí)而關閉。

Heroku希望我們綁定他(tā)們在環境變量中指定的(dε​↕♥e)随機(jī)端口号,然而我們可(kě)以使用(yòn$≤≈€g)硬編碼的(de)6767端口。

修複這(zhè)個(gè)問(wèn)題,隻需要β≈"←(yào)簡單的(de)從(cóng)環境變量中讀(←Ω•dú)取PORT,如(rú)果PORT不(b↓¥ù)存在,再默認綁定到(dào)6767。

fn get_server_port() -> u16 {
£≤÷    env::var("PORT&qu≤βot;).unwrap_or("6767"§↑≤.to_string()).parse().unwrap≥&× ()
}

server.listen(("0.¶≤≥0.0.0", get_serv®∏™₽er_port()));

修複好(hǎo)這(zhè)個(gè)問(→§§wèn)題後,網站(zhàn)就(jiù)可(kě)以正常運行(xíng★λ)了(le)。

 

你(nǐ)可(kě)以到(dào) demo-clog-website.herokuapp.com 進行(xíng)一(yī)些(xiē)嘗試。網'±₹←站(zhàn)包含前面提到(dào)的(de)UR§∑L解析器(qì)改善,因此輸入庫(repositorφ♥y)可(kě)以使用(yòng)user/repo的(★☆←de)形式。你(nǐ)可(kě)以在 "™;這(zhè)裡(lǐ)(GitHub) απ∞₽找到(dào)這(zhè)個(gè)演示項目的(de)全↕∞¥部代碼。

現(xiàn)在,是(shì)不(bù)是(shì)為(wèi)你(n ≤¶®ǐ)的(de)首個(gè)Nickel項目開(kāi)了(le)一(yī)個‌★‌φ(gè)好(hǎo)頭?