從零開始,做一個NodeJS博客(二):實現首頁-加載文章列表和詳情


標簽: NodeJS


0

這個偽系列的第二篇,不過和之前的幾篇是同一天寫的。三分鍾熱度貌似還沒過。

1 靜態資源代理

上一篇,我們是通過判斷請求的路徑來直接返回結果的。簡單粗暴,缺點明顯:如果url后面加雜了queryString,因為判斷邏輯中沒有處理,那么將直接返回404頁面(其實也沒有其他的頁面)。

難道要一個一個加queryString的處理?有可行性,但麻煩。

再者,如果要添加新的html頁面,那么也要在處理邏輯中依次添加?html頁面還要引用js腳本,css樣式,樣式表中還要引用圖片,字體。。。。難道就要這樣無休止的添加下去?顯然是不可能的。

借助於NodeJS提供的urlAPI,我們可以提取請求url中的pathName部分,也就是忽略了域名以及queryString的部分,然后將他交給另一個pathAPI。他負責將pathName解析為當前操作系統可讀的路徑。

至此,答案就很明顯了:拿到路徑后,利用fs模塊讀取,如果成功,則返回文件內容;否則直接404就好了。

這里我們只用到了urlpath模塊的很小一部分功能,我也就只寫這一點了。具體的定義及應用請參考NodeJS官方API文檔

url.prase( urlString )

接收一個url字符串,將它轉為URL對象。

url.parse('http://user:pass@host.com:8080/path/to/file?query=string#hash');

它的返回值將會是一個 Object:

Url {
    protocol: 'http:',
    slashes: true,
    auth: 'user:pass',
    host: 'host.com:8080',
    port: '8080',
    hostname: 'host.com',
    hash: '#hash',
    search: '?query=string',
    query: 'query=string',
    pathname: '/path/to/file',
    path: '/path/to/file?query=string',
    href: 'http://user:pass@host.com:8080/path/to/file?query=string#hash' 
 }

看看這個結構圖,應該更容易理解:

┌─────────────────────────────────────────────────────────────────────────────┐
│                                    href                                     │
├──────────┬┬───────────┬─────────────────┬───────────────────────────┬───────┤
│ protocol ││   auth    │      host       │           path            │ hash  │
│          ││           ├──────────┬──────┼──────────┬────────────────┤       │
│          ││           │ hostname │ port │ pathname │     search     │       │
│          ││           │          │      │          ├─┬──────────────┤       │
│          ││           │          │      │          │ │    query     │       │
"  http:   // user:pass @ host.com : 8080   /p/a/t/h  ?  query=string   #hash "
│          ││           │          │      │          │ │              │       │
└──────────┴┴───────────┴──────────┴──────┴──────────┴─┴──────────────┴───────┘

以上抄自:NodeJS官方API文檔:URL

所以,要拿到相對路徑,我們只需要取 Url.pathname 就可以了。

path.resolve( path[, ...] )

接收一組按順序排好的路徑,並把它組合為一個當前操作系統可識別的絕對路徑。

path.resolve('.', '/archive/my-first-article')

如果在windows中,當前工作目錄為 C:\Users\rocka,它將返回

'C:\Users\rocka\archive\my-first-article'

而在linux環境,/home/rocka目錄中,它的返回值是

'/home/rocka/archive/my-first-article'

與當前操作系統直接關聯。至於為何要跟當前工作目錄關聯,因為此時,它的第一個參數是 . ,不就是當前目錄嘛。

path.join( path[, ...] )

接收一組路徑字符串,然后將他們拼起來。可以用 .. 這樣的相對路徑。
返回值仍是字符串哦。

path.join('/foo', 'bar', 'baz/asdf', 'quux', '..')

將返回:

'/foo/bar/baz/asdf'

直接貼代碼咯(代碼片段,省略了require和listen等):

var root = path.resolve('.');

var server = http.createServer((request, response) => {
    console.log(`[Rocka Node Server] ${request.method}: ${request.url}`);
    // path name in url
    var pathName = url.parse(request.url).pathname;
    // file path based on operation system
    var filePath = path.join(root, pathName);
    console.log(`[Rocka Node Server] pathName: ${pathName}, filePath: ${filePath}`);
    if (request.method === 'GET') {
        // try to find and read local file
        fs.stat(filePath, (err, stats) => {
            // no error occured, read file
            if (!err && stats.isFile()) {
                response.writeHead(200, { 'Content-Type': 'text/html' });
                fs.createReadStream(filePath).pipe(response);
            // cannot find file, but received index request
            } else if (!err && pathName == '/') {
                response.writeHead(200, { 'Content-Type': 'text/html' });
                fs.createReadStream('./page/index.html').pipe(response);
            // file not found
            } else if (!err && !stats.isFile()) {
                response.writeHead(200, { 'Content-Type': 'text/html' });
                fs.createReadStream('./page/404.html').pipe(response);
            // error :(
            } else if (err) {
                response.writeHead(500);
                response.end(err.toString());
            }
        });
    }
});

嗯,看起來很工整。下面我們來試着跑一下。
哦,等會,根據剛才404頁面和首頁的位置,我的目錄結構應該重構了,就像這樣

  • page
    • 404.html
    • index.html
  • favicon.ico
  • package.json
  • Procfile
  • server.js

之后打開監聽的端口,看到首頁了,favicon也正常加載,完成!

2 讀取文章列表

上一篇我說過,暫時使用單獨的文件來存放文章。所以文件名不僅要是文章的標題,還唯一標識着這篇文章對應的文件。
目錄又要改了:

  • archive
    • my-first-article
    • my-second-article
  • page
    • 404.html
    • index.html
  • script
    • index.js
  • favicon.ico
  • package.json
  • Procfile
  • server.js

讀取目錄內所有文件,又要用到fs模塊了。這次用的是 fs.readdir(path[, options], callback)
它讀取指定目錄內所有文件,並把兩個參數 (err, files) 傳遞給回調函數。files 是一個包含目錄內所有文件名的數組。

這樣思路就很清晰了!我們讀取 archive 下的所有文件,然后解析成json發出去就好。我說過要用Ajax的嘛。
(先別管怎么接收)

這個請求不同於文件請求,是一個“莫須有”的路徑,需要我們來定義一下。我說了:

/api/index-article-list

這就是API路徑。好了,現在把他寫到服務器中去:

var server = http.createServer((request, response) => {
    // parse the path
    var pathName = url.parse(request.url).pathname;
    var filePath = path.join(root, pathName);
    if (request.method === 'GET') {
        // this is a api request
        if (pathName.indexOf('/api/') >= 0) {
            switch (pathName) {
                // api address
                case '/api/index-article-list':
                    // read all archives
                    fs.readdir('./archive', (err, files) => {
                        if (err) {
                            console.log(err);
                        } else {
                            response.writeHead(200, { 'Content-Type': 'application/json' });
                            // parse the Array<String> to json string
                            response.end(JSON.stringify(files));
                        }
                    });
                    break;
                default:
                    break;
            }
        } else {
            // read local file
            });
        }
    }
});

收到請求時,看看它是不是有定義的API請求。如果是的話,執行並返回,否則才去找文件。

服務端准備完成了,接下來是頁面的動態加載:
貼一下我的 index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Rocka's Node Blog</title>
    <script src="../script/index.js"></script>
</head>

<body>
    <h1>Rocka's Node Blog</h1>
    <hr>
    <h3>Blog Archive</h3>
    <ul id="index-article-list">
    </ul>
    <blockquote id="index-article-content">Article should be shown here.</blockquote>
</body>

</html>

接下來是 index.js

'use strict';

function loadArticleList() {
    var ul = document.getElementById('index-article-list');

    function success(response) {
        var resData = JSON.parse(response);
        resData.forEach((title) => {
            var newLine = document.createElement('li');
            newLine.innerHTML = `<a href="javascript:void(0);">${title}</a>`;
            ul.appendChild(newLine);
        });
    }

    function fail(code) {
        var newLine = document.createElement('li');
        newLine.innerText = `List Load Faild: Please Refresh Page And Try Again.`;
        ul.appendChild(newLine);
    }

    var request = new XMLHttpRequest(); // New XMLHttpRequest Object

    request.onreadystatechange = () => { // invoked when readyState changes
        if (request.readyState === 4) { // request succeed
            // response result:
            if (request.status === 200) {
                // succeed: update article
                return success(request.response);
            } else {
                // failed: show error code
                return fail(request.status);
            }
        }
    }

    // send request
    request.open('GET', '/api/index-article-list');
    request.send();
}

window.onload = () => {
    console.log('Welcome to Rocka\'s Node Blog! ');
    loadArticleList();
}

這里我用了js原生的 XMLHttpRequest ,並沒有用jQuery提供的Ajax方法。
至於為什么,可能是之前一直用jQuery,有點審美疲勞了,想換個方法玩玩。
不過,一用上原生方法,我就發現,這是什么智障寫法!!
一個破 Object ,還on這個on那個,自己還帶那么多方法和屬性,你以為你是誰啊,我憑什么要記這么多方法這么多屬性!!!
果然jQuery大法好啊!!!!

Write less, do more. —— jQuery

3 讀取文章詳情

按照先前的定義,文章是存在單獨的文件里的。里面只有一句簡簡單單的話:

my-first-article my-second-article
I'm Very Excited! Stay young, Stay simple.

於是這就很明顯了!直接請求 /archive/${title} ,由於有這個文件的存在,內容就可以出來了!
這下連API都不用寫了!

於是,愉快的在 index.js 中補刀一個函數(不要吐槽 XMLHttpRequest 的寫法):

function loadArticleContent(articleTitle) {
    var bq = document.getElementById('index-article-content');

    function success(response) {
        bq.innerText = response;
    }

    function fail(code) {
        bq.innerText = 'Article Load Faild: Please Refresh Page And Try Again.';
        bq.innerText += `Error Code: ${code}`;
    }

    var request = new XMLHttpRequest();

    request.onreadystatechange = () => {
        if (request.readyState === 4) {
            if (request.status === 200) {
                return success(request.response);
            } else {
                return fail(request.status);
            }
        }
    }

    request.open('GET', `/archive/${articleTitle}`);
    request.send();
}

然后在加載標題時,把它的href指向這個函數就好了!

newLine.innerHTML = `<a href="javascript:loadArticleContent('${title}');">${title}</a>`;

保存,運行,刷新,提交,構建,完成!

愉快的一天就這么結束了233

倉庫地址

GitHub倉庫:BlogNode

主倉庫,以后的代碼都在這里更新。

HerokuApp:rocka-blog-node

上面GitHub倉庫的實時構建結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM