http-server包
前端開發人員不論是在開發還是測試中都會用到http伺服器,方便快捷的使用更加有助於我們編寫和除錯程式碼,而http-server則是一個十分好用的包,它幾乎不用配置,可以使用任何一個目錄生成一個http伺服器。
簡單說一說http-server的使用。
首先全域性安裝它:
> npm install http-server -g
複製程式碼
安裝成功後就可以使用命令列來啟動一個http服務,進入你想啟動的目錄,使用命令:
> http-server
複製程式碼
開啟瀏覽器訪問http://localhost:8080/即可看到已經使用當前目錄啟動了http server。
然而這裡並不是討論http-server的使用,我們可以仿照http-server包手寫一個功能近似的靜態服務,以更深入的瞭解和學習。
功能說明
一個靜態伺服器都能做什麼?
- 首先應支援使用計算機上任何一個目錄生成http服務;
- 可以在瀏覽器中瀏覽目錄結構
- 可以訪問伺服器上的檔案
- 支援命令列,可讓使用者自己指定訪問的埠
以上就是http伺服器應該有的大致功能,我們使用node.js手寫程式碼來實現它。
準備工作
自己手寫一個http服務要有基本的檔案讀取,輸入輸出等工作要做,這些大部分功能node.js提供的庫都可以實現,但是還需要有些第三方包的支援,下面簡單說一下。
mime包
mime包是一個可以根據檔案來分析出http內容型別的包,http的內容型別不難理解,當訪問一個html頁面時,瀏覽器會根據嚮應頭的Content-Type
型別來呈現內容。
使用npm install mime
安裝後即可使用:
const mime = require('mime');
var contentType = mime.getType('/Users/yuet/workspace/static-server/public/node.jpeg');//image/jpeg
複製程式碼
可以看到,傳入正確的檔案路徑即可得到對應的內容型別,這樣可以省去我們自己寫程式碼判斷生成Content-Type
。
chalk包
chalk包顧名思義,它譯為粉筆,有了它就可以方便的在控制檯輸出內容時使用不同的顏色,同樣安裝它npm install chalk
即可使用:
const chalk = require('chalk');
console.log(chalk.red('hello node'));
複製程式碼
以上使用chalk.red()
,將輸出內容變為紅色顯示,chalk有很多顏色方法支援,使用它可以突出重點內容,方便使用者檢視。
debug包
debug包可以在node.js環境中幫助我們除錯。
舉例:
var a = require('debug')('worker:a')
, b = require('debug')('worker:b');
a('doing lots of uninteresting work');
b('doing some work');
複製程式碼
呼叫debug包會返回一個方法,它可以指定作業系統中某環境變數名,以上在作業系統中可以設定兩個環境變數work:a
和work:b
,當前若是為work:a的環境,則只會輸出a方法中的內容,這有利於我們區別開發和生產環境。
預設配置
啟動一個http伺服器,埠、host主機、以及目錄地址這三個內容必不可少,當然使用者在使用時可以更改,但是需要有一個預設的配置:
const path = require('path');
let config = {
host: '127.0.0.1',
port: 8080,
dir: path.join(__dirname, '../public')
};
複製程式碼
指定本機,預設埠號為8080,預設目錄就為當前專案中的/public目錄。將此配置物件匯出供外面使用。
程式碼基本結構
可以封裝一個類,將靜態檔案操作、伺服器的啟停寫在裡面:
const config = require('./config');
class Server {
constructor() {
this.config = config;
}
//封裝讀取檔案等操作
handleRequest(req, res) {}
/**
* 啟動伺服器
*/
start() {}
/**
* 傳送檔案
*/
sendFile(req, res, filePath, stat) {}
/**
* 傳送錯誤訊息
*/
sendError(req, res, e) {}
}
new Server().start();
複製程式碼
以上程式碼就是大致的結構,其中將預設的配置物件讀入並掛到類的this上,這樣可以方便的呼叫配置資訊。
核心程式碼
靜態伺服器是基於http的,因此要引入http模組來啟動一個http服務,並監聽埠:
start() {
let server = http.createServer(this.handleRequest);
let {host, port} = this.config;
server.listen({
host,
port
}, () => {
debug(`伺服器已經啟動在 http://${host}:${chalk.greenBright(port)}/ 上...`);
});
}
複製程式碼
當啟動了服務,使用者在瀏覽器中輸入了一個地址,此地址可能是一個目錄,也有可能是一個確切的檔案,所以要分別處理。
let {pathname} = url.parse(req.url, true);
let resource = path.join(this.config.dir, pathname);
let s = await stat(resource);
if (s.isDirectory()) {
//是目錄
} else {
//是檔案
}
複製程式碼
以上程式碼,首先使用url的模組parse方法將瀏覽器地址解析出來得到絕對路徑,並使用fs模組的stat方法判斷是否是一個目錄,這裡使用了async-await操作簡化了非同步程式碼。
當是目錄的時候,我們的工作就是把目錄下的所有檔案都讀取出來並展示給使用者:
let files = await readdir(resource);//目錄下的所有檔案
files = files.map((i) => {
return {
fileName: i,
path: path.join(pathname, i)
}
});
let html = ejs.render(temp, {files});
res.setHeader('Content-Type', 'text/html;charset=utf8');
res.end(html);
複製程式碼
其中fs.readdir()
方法可以將當前目錄下的所有檔案全部返回,它返回一個Array型別,使用map對映將檔案地址拼接到物件中的目的則是為了ejs模版渲染。這樣在模版中迴圈輸出,即可將當前目錄下的所有檔案展示在瀏覽器中。
ejs模版:
<ul>
<%files.forEach((i)=>{%>
<li><a href="<%=i.path%>"><%=i.fileName%></a></li>
<%})%>
</ul>
複製程式碼
若使用者訪問的是個確切的檔案,那麼使用流的方式輸出即可:
let contentType = mime.getType(filePath)
? mime.getType(filePath)
: 'text/plain';
res.setHeader('Content-Type', contentType + ';charset=utf8');
fs.createReadStream(filePat).pipe(res);
複製程式碼
可以看到使用mime模組得到真正的內容型別,使用流pipe到響應物件中即可。
使用命令列執行
到此為止程式碼已基本實現,但是執行程式時依然使用node命令來執行,這顯然不符合要求,比如http-server包,在全域性安裝之後是使用http-server
命令來執行。那麼怎麼也達到這樣的效果呢?
配置命令列工具
在package.json檔案中,使用bin配置節來配置自己的命令:
package.json:
"bin": {
"my-http-server": "bin/www"
},
複製程式碼
這個配置節的意義是當執行my-http-server命令,執行bin/www檔案。
接下來就是在專案目錄中建立bin/www檔案:
#! /usr/bin/env node
console.log('hello');
複製程式碼
其中井號驚歎號!/usr/bin/env node
是要說明去環境變數中找到node所在的目錄,使用node來執行下面的指令碼。
這時,開啟控制檯使用npm link命令把我們的命令連結到全域性
這時就可以正確使用我們自己編寫的命令了:
> my-http-server
複製程式碼
執行結果:
hello
複製程式碼
使用命令列傳遞使用者引數
配置好命令列工具後,下面的任務就是處理命令列引數,以達到使用者自己指定埠的目的。
yargs包
處理命令列引數,完全可以自己寫程式碼擷取,但是第三方yargs已經做好了這部分工作,我們只需安裝使用就好,使用npm安裝:npm install yargs
使用方法:
#! /usr/bin/env node
const yargs=require('yargs').argv;
console.log(yargs);
複製程式碼
此時執行命令列,可以使用引數:
my-http-server --port 3000
複製程式碼
執行結果:
_: [],
help: false,
version: false,
port: 3000,
'$0': '/usr/local/bin/my-http-server' }
複製程式碼
可以看到,使用yargs包,可以將命令列引數轉為物件,我們可以很方便的拿到port埠號進行操作。
配置命令並傳參覆蓋預設引數:
#! /usr/bin/env node
const yargs=require('yargs');
const Server=require('../src/app');
let commandParameter=yargs.option('port',{
alias:'p',
default:3000,
type:Number,
description:'埠號'
}).option('host',{
default:'127.0.0.1',
type:String,
description:'IP地址,預設為127.0.0.1'
}).option('dir',{
default:process.cwd(),
type:String,
description:'靜態檔案地址,預設為當前目錄'
}).usage('my-http-server [options]').argv;
new Server(commandParameter).start();
複製程式碼
這樣,在www檔案中接收到的引數傳入new Server(),覆蓋掉預設引數即可:
constructor(args) {
this.config = {
...config,
...args //命令列引數覆蓋預設引數
};
}
複製程式碼