標簽: NodeJS
0
這個偽系列的第二篇,不過和之前的幾篇是同一天寫的。三分鍾熱度貌似還沒過。
1 靜態資源代理
上一篇,我們是通過判斷請求的路徑來直接返回結果的。簡單粗暴,缺點明顯:如果url后面加雜了queryString,因為判斷邏輯中沒有處理,那么將直接返回404頁面(其實也沒有其他的頁面)。
難道要一個一個加queryString的處理?有可行性,但麻煩。
再者,如果要添加新的html頁面,那么也要在處理邏輯中依次添加?html頁面還要引用js腳本,css樣式,樣式表中還要引用圖片,字體。。。。難道就要這樣無休止的添加下去?顯然是不可能的。
借助於NodeJS提供的url
API,我們可以提取請求url中的pathName部分,也就是忽略了域名以及queryString的部分,然后將他交給另一個path
API。他負責將pathName解析為當前操作系統可讀的路徑。
至此,答案就很明顯了:拿到路徑后,利用fs
模塊讀取,如果成功,則返回文件內容;否則直接404就好了。
這里我們只用到了
url
和path
模塊的很小一部分功能,我也就只寫這一點了。具體的定義及應用請參考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倉庫的實時構建結果。