日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Express 的使用

發(fā)布時(shí)間:2024/8/23 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Express 的使用 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

以下內(nèi)容,基于 Express 4.x 版本

Node.js 的 Express

Express?估計(jì)是那種你第一次接觸,就會(huì)喜歡上用它的框架。因?yàn)樗娴姆浅:?jiǎn)單,直接。

在當(dāng)前版本上,一共才這么幾個(gè)文件:

lib/ ├── application.js ├── express.js ├── middleware │?? ├── init.js │?? └── query.js ├── request.js ├── response.js ├── router │?? ├── index.js │?? ├── layer.js │?? └── route.js ├── utils.js └── view.js

這種程度,說(shuō)它是一個(gè)“框架”可能都有些過(guò)了,幾乎都是工具性質(zhì)的實(shí)現(xiàn),只限于 Web 層。

當(dāng)然,直接了當(dāng)?shù)貙?shí)現(xiàn)了 Web 層的基本功能,是得益于?Node.js?本身的 API 中,就提供了?net?和?http?這兩層,?Express?對(duì)?http?的方法包裝一下即可。

不過(guò),本身功能簡(jiǎn)單的東西,在?package.json?中卻有好長(zhǎng)一串?dependencies?列表。

Hello World

在跑?Express?前,你可能需要初始化一個(gè)?npm?項(xiàng)目,然后再使用?npm?安裝?Express:

mkdir p cd p npm init npm install express --save

新建一個(gè)?app.js?:

const express = require('express'); const app = express(); app.all('/', (req, res) => res.send('hello') ); app.listen(8888);

調(diào)試信息是通過(guò)環(huán)境變量?DEBUG?控制的:

const process = require('process'); process.env['DEBUG'] = 'express:*';

這樣就可以在終端看到帶顏色的輸出了,嗯,是的,帶顏色控制字符,vim 中直接跑就 SB 了。

應(yīng)用 Application

Application?是一個(gè)上層統(tǒng)籌的概念,整合“請(qǐng)求-響應(yīng)”流程。?express()?的調(diào)用會(huì)返回一個(gè)?application?,一個(gè)項(xiàng)目中,有多個(gè)?app?是沒(méi)問(wèn)題的:

const express = require('express');const app = express(); app.all('/', (req, res) => res.send('hello')); app.listen(8888);const app2 = express(); app2.all('/', (req, res) => res.send('hello2')); app2.listen(8889);

多個(gè)?app?的另一個(gè)用法,是直接把某個(gè)?path?映射到整個(gè)?app?:

const express = require('express');const app = express();app.all('/', (req, res) => {res.send('ok'); });const app2 = express(); app2.get('/xx', (req, res, next) => res.send('in app2') ) app.use('/2', app2)app.listen(8888);

這樣,當(dāng)訪問(wèn)?/2/xx?時(shí),就會(huì)看到?in app2?的響應(yīng)。

前面說(shuō)了?app?實(shí)際上是一個(gè)上層調(diào)度的角色,在看后面的內(nèi)容之前,先說(shuō)一下?Express?的特點(diǎn),整體上來(lái)說(shuō),它的結(jié)構(gòu)基本上是“回調(diào)函數(shù)串行”,無(wú)論是?app?,或者?route,?handle,?middleware這些不同的概念,它們的形式,基本是一致的,就是?(res, req, next) => {}?,串行的流程依賴?next()?的顯式調(diào)用。

我們把?app?的功能,分成五個(gè)部分來(lái)說(shuō)。

路由 - Handler 映射

app.all('/', (req, res, next) => {}); app.get('/', (req, res, next) => {}); app.post('/', (req, res, next) => {}); app.put('/', (req, res, next) => {}); app.delete('/', (req, res, next) => {});

上面的代碼就是基本的幾個(gè)方法,路由的匹配是串行的,可以通過(guò)?next()?控制:

const express = require('express');const app = express();app.all('/', (req, res, next) => {res.send('1 ');console.log('here');next(); });app.get('/', (req, res, next) => {res.send('2 ');console.log('get');next(); });app.listen(8888);

對(duì)于上面的代碼,因?yàn)橹貜?fù)調(diào)用?send()?會(huì)報(bào)錯(cuò)。

同樣的功能,也可以使用?app.route()?來(lái)實(shí)現(xiàn):

const express = require('express');const app = express();app.route('/').all( (req, res, next) => {console.log('all');next(); }).get( (req, res, next) => {res.send('get');next(); }).all( (req, res, next) => {console.log('tail');next(); });app.listen(8888);

app.route()?也是一種抽象通用邏輯的形式。

還有一個(gè)方法是?app.params?,它把“命名參數(shù)”的處理單獨(dú)拆出來(lái)了(我個(gè)人不理解這玩意兒有什么用):

const express = require('express');const app = express();app.route('/:id').all( (req, res, next) => {console.log('all');next(); }).get( (req, res, next) => {res.send('get');next() }).all( (req, res, next) => {console.log('tail'); });app.route('/').all( (req, res) => {res.send('ok')});app.param('id', (req, res, next, value) => {console.log('param', value);next(); });app.listen(8888);

app.params?中的對(duì)應(yīng)函數(shù)會(huì)先行執(zhí)行,并且,記得顯式調(diào)用?next()?。

Middleware

其實(shí)前面講了一些方法,要實(shí)現(xiàn)?Middleware?功能,只需要?app.all(/.*/, () => {})?就可以了,?Express?還專門提供了?app.use()?做通用邏輯的定義:

const express = require('express');const app = express();app.all(/.*/, (req, res, next) => {console.log('reg');next(); });app.all('/', (req, res, next) => {console.log('pre');next(); });app.use((req, res, next) => {console.log('use');next(); });app.all('/', (req, res, next) => {console.log('all');res.send('/ here');next(); });app.use((req, res, next) => {console.log('use2');next(); });app.listen(8888);

注意?next()?的顯式調(diào)用,同時(shí),注意定義的順序,?use()?和?all()?順序上是平等的。

Middleware?本身也是?(req, res, next) => {}?這種形式,自然也可以和?app?有對(duì)等的機(jī)制——接受路由過(guò)濾,?Express?提供了?Router?,可以單獨(dú)定義一組邏輯,然后這組邏輯可以跟?Middleware一樣使用。

const express = require('express'); const app = express(); const router = express.Router();app.all('/', (req, res) => {res.send({a: '123'}); });router.all('/a', (req, res) => {res.send('hello'); });app.use('/route', router);app.listen(8888);

功能開(kāi)關(guān),變量容器

app.set()?和?app.get()?可以用來(lái)保存?app?級(jí)別的變量(對(duì),?app.get()?還和?GET?方法的實(shí)現(xiàn)名字上還沖突了):

const express = require('express');const app = express();app.all('/', (req, res) => {app.set('title', '標(biāo)題123');res.send('ok'); });app.all('/t', (req, res) => {res.send(app.get('title')); });app.listen(8888);

上面的代碼,啟動(dòng)之后直接訪問(wèn)?/t?是沒(méi)有內(nèi)容的,先訪問(wèn)?/?再訪問(wèn)?/t?才可以看到內(nèi)容。

對(duì)于變量名,?Express?預(yù)置了一些,這些變量的值,可以叫?settings?,它們同時(shí)也影響整個(gè)應(yīng)用的行為:

  • case sensitive routing
  • env
  • etag
  • jsonp callback name
  • json escape
  • json replacer
  • json spaces
  • query parser
  • strict routing
  • subdomain offset
  • trust proxy
  • views
  • view cache
  • view engine
  • x-powered-by

(上面這些值中,干嘛不放一個(gè)最基本的?debug?呢……)

除了基本的?set() / get()?,還有一組?enable() / disable() / enabled() / disabled()?的包裝方法,其實(shí)就是?set(name, false)?這種。?set(name)?這種只傳一個(gè)參數(shù),也可以獲取到值,等于?get(name)?。

模板引擎

Express?沒(méi)有自帶模板,所以模板引擎這塊就被設(shè)計(jì)成一個(gè)基礎(chǔ)的配置機(jī)制了。

const process = require('process'); const express = require('express'); const app = express();app.set('views', process.cwd() + '/template');app.engine('t2t', (path, options, callback) => {console.log(path, options);callback(false, '123'); });app.all('/', (req, res) => {res.render('demo.t2t', {title: "標(biāo)題"}, (err, html) => {res.send(html)}); });app.listen(8888);

app.set('views', ...)?是配置模板在文件系統(tǒng)上的路徑,?app.engine()?是擴(kuò)展名為標(biāo)識(shí),注冊(cè)對(duì)應(yīng)的處理函數(shù),然后,?res.render()?就可以渲染指定的模板了。?res.render('demo')?這樣不寫擴(kuò)展名也可以,通過(guò)?app.set('view engine', 't2t')?可以配置默認(rèn)的擴(kuò)展名。

這里,注意一下?callback()?的形式,是?callback(err, html)?。

端口監(jiān)聽(tīng)

app?功能的最后一部分,?app.listen()?,它完成的形式是:

app.listen([port[, host[, backlog]]][, callback])

注意,?host?是第二個(gè)參數(shù)。

backlog?是一個(gè)數(shù)字,配置可等待的最大連接數(shù)。這個(gè)值同時(shí)受操作系統(tǒng)的配置影響。默認(rèn)是 512 。

請(qǐng)求 Request

這一塊倒沒(méi)有太多可以說(shuō)的,一個(gè)請(qǐng)求你想知道的信息,都被包裝到?req?的屬性中的。除了,頭。頭的信息,需要使用?req.get(name)?來(lái)獲取。

GET 參數(shù)

使用?req.query?可以獲取 GET 參數(shù):

const express = require('express'); const app = express();app.all('/', (req, res) => {console.log(req.query);res.send('ok'); });app.listen(8888);

請(qǐng)求:

# -*- coding: utf-8 -*- import requests requests.get('http://localhost:8888', params={"a": '中文'.encode('utf8')})

POST 參數(shù)

POST 參數(shù)的獲取,使用?req.body?,但是,在此之前,需要專門掛一個(gè) Middleware ,?req.body才有值:

const express = require('express'); const app = express();app.use(express.urlencoded({ extended: true })); app.all('/', (req, res) => {console.log(req.body);res.send('ok'); });app.listen(8888); # -*- coding: utf-8 -*-import requestsrequests.post('http://localhost:8888', data={"a": '中文'})

如果你是整塊扔的 json 的話:

# -*- coding: utf-8 -*-import requests import jsonrequests.post('http://localhost:8888', data=json.dumps({"a": '中文'}),headers={'Content-Type': 'application/json'})

Express?中也有對(duì)應(yīng)的?express.json()?來(lái)處理:

const express = require('express'); const app = express();app.use(express.json()); app.all('/', (req, res) => {console.log(req.body);res.send('ok'); });app.listen(8888);

Express?中處理?body?部分的邏輯,是單獨(dú)放在?body-parser?這個(gè) npm 模塊中的。?Express?也沒(méi)有提供方法,方便地獲取原始 raw 的內(nèi)容。另外,對(duì)于 POST 提交的編碼數(shù)據(jù),?Express?只支持 UTF-8 編碼。

如果你要處理文件上傳,嗯,?Express?沒(méi)有現(xiàn)成的 Middleware ,額外的實(shí)現(xiàn)在?github.com/expressjs/multer?。( Node.js 天然沒(méi)有“字節(jié)”類型,所以在字節(jié)級(jí)別的處理上,就會(huì)感覺(jué)很不順啊)

Cookie

Cookie 的獲取,也跟 POST 參數(shù)一樣,需要外掛一個(gè)?cookie-parser?模塊才行:

const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cookieParser()) app.all('/', (req, res) => {console.log(req.cookies);res.send('ok'); });app.listen(8888);

請(qǐng)求:

# -*- coding: utf-8 -*-import requests import jsonrequests.post('http://localhost:8888', data={'a': '中文'},headers={'Cookie': 'a=1'})

如果 Cookie 在響應(yīng)時(shí),是配置?res?做了簽名的,則在?req?中可以通過(guò)?req.signedCookies?處理簽名,并獲取結(jié)果。

來(lái)源 IP

Express?對(duì)?X-Forwarded-For?頭,做了特殊處理,你可以通過(guò)?req.ips?獲取這個(gè)頭的解析后的值,這個(gè)功能需要配置?trust proxy?這個(gè)?settings?來(lái)使用:

const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cookieParser()) app.set('trust proxy', true); app.all('/', (req, res) => {console.log(req.ips);console.log(req.ip);res.send('ok'); });app.listen(8888);

請(qǐng)求:

# -*- coding: utf-8 -*-import requests import json#requests.get('http://localhost:8888', params={"a": '中文'.encode('utf8')}) requests.post('http://localhost:8888', data={'a': '中文'},headers={'X-Forwarded-For': 'a, b, c'})

如果?trust proxy?不是?true?,則?req.ip?會(huì)是一個(gè) ipv4 或者 ipv6 的值。

響應(yīng) Response

Express?的響應(yīng),針對(duì)不同類型,本身就提供了幾種包裝了。

普通響應(yīng)

使用?res.send?處理確定性的內(nèi)容響應(yīng):

res.send({ some: 'json' }); res.send('<p>some html</p>'); res.status(404); res.end(); res.status(500); res.end();

res.send()?會(huì)自動(dòng)?res.end()?,但是,如果只使用?res.status()?的話,記得加上?res.end()?。

模板渲染

模板需要預(yù)先配置,在?Request?那節(jié)已經(jīng)介紹過(guò)了。

const process = require('process'); const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cookieParser())app.set('trust proxy', false); app.set('views', process.cwd() + '/template'); app.set('view engine', 'html'); app.engine('html', (path, options, callback) => {callback(false, '<h1>Hello</h1>'); });app.all('/', (req, res) => {res.render('index', {}, (err, html) => {res.send(html);}); });app.listen(8888);

這里有一個(gè)坑點(diǎn),就是必須在對(duì)應(yīng)的目錄下,有對(duì)應(yīng)的文件存在,比如上面例子的?template/index.html?,那么?app.engine()?中的回調(diào)函數(shù)才會(huì)執(zhí)行。都自定義回調(diào)函數(shù)了,這個(gè)限制沒(méi)有任何意義,?path, options?傳入就好了,至于是不是要通過(guò)文件系統(tǒng)讀取內(nèi)容,怎么讀取,又有什么關(guān)系呢。

Cookie

res.cookie?來(lái)處理?Cookie?頭:

const process = require('process'); const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cookieParser("key"))app.set('trust proxy', false); app.set('views', process.cwd() + '/template'); app.set('view engine', 'html'); app.engine('html', (path, options, callback) => {callback(false, '<h1>Hello</h1>'); }); app.all('/', (req, res) => {res.render('index', {}, (err, html) => {console.log('cookie', req.signedCookies.a);res.cookie('a', '123', {signed: true});res.cookie('b', '123', {signed: true});res.clearCookie('b');res.send(html);}); });app.listen(8888);

請(qǐng)求:

# -*- coding: utf-8 -*-import requests import jsonres = requests.post('http://localhost:8888', data={'a': '中文'},headers={'X-Forwarded-For': 'a, b, c','Cookie': 'a=s%3A123.p%2Fdzmx3FtOkisSJsn8vcg0mN7jdTgsruCP1SoT63z%2BI'}) print(res, res.text, res.headers)

注意三點(diǎn):

  • app.use(cookieParser("key"))?這里必須要有一個(gè)字符串做?key?,才可以正確使用簽名的 cookie 。
  • clearCookie()?仍然是用“設(shè)置過(guò)期”的方式來(lái)達(dá)到刪除目的,cookie()?和?clearCookie()?并不會(huì)整合,會(huì)寫兩組?b=xx?進(jìn)頭。
  • res.send()?會(huì)在連接上完成一個(gè)響應(yīng),所以,與頭相關(guān)的操作,都必須放在?res.send()?前面。

頭和其它

res.set()?可以設(shè)置指定的響應(yīng)頭,?res.rediect(301, 'http://www.zouyesheng.com')?處理重定向,?res.status(404); res.end()?處理非 20 響應(yīng)。

const process = require('process'); const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cookieParser("key"))app.set('trust proxy', false); app.set('views', process.cwd() + '/template'); app.set('view engine', 'html'); app.engine('html', (path, options, callback) => {callback(false, '<h1>Hello</h1>'); });app.all('/', (req, res) => {res.render('index', {}, (err, html) => {res.set('X-ME', 'zys');//res.redirect('back');//res.redirect('http://www.zouyesheng.com');res.status(404);res.end();}); });app.listen(8888);

res.redirect('back')?會(huì)自動(dòng)獲取?referer?頭作為?Location?的值,使用這個(gè)時(shí),注意?referer為空的情況,會(huì)造成循環(huán)重復(fù)重定向的后果。

Chunk 響應(yīng)

Chunk?方式的響應(yīng),指連接建立之后,服務(wù)端的響應(yīng)內(nèi)容是不定長(zhǎng)的,會(huì)加個(gè)頭:?Transfer-Encoding: chunked?,這種狀態(tài)下,服務(wù)端可以不定時(shí)往連接中寫入內(nèi)容(不排除服務(wù)端的實(shí)現(xiàn)會(huì)有緩沖區(qū)機(jī)制,不過(guò)我看?Express?沒(méi)有)。

const process = require('process'); const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cookieParser("key"))app.set('trust proxy', false); app.set('views', process.cwd() + '/template'); app.set('view engine', 'html'); app.engine('html', (path, options, callback) => {callback(false, '<h1>Hello</h1>'); });app.all('/', (req, res) => {const f = () => {const t = new Date().getTime() + '\n';res.write(t);console.log(t);setTimeout(f, 1000);}setTimeout(f, 1000); });app.listen(8888);

上面的代碼,訪問(wèn)之后,每過(guò)一秒,都會(huì)收到新的內(nèi)容。

大概是?res?本身是 Node.js 中的?stream?類似對(duì)象,所以,它有一個(gè)?write()?方法。

要測(cè)試這個(gè)效果,比較方便的是直接 telet:

zys@zys-alibaba:/home/zys/temp >>> telnet localhost 8888 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GET / HTTP/1.1 Host: localhostHTTP/1.1 200 OK X-Powered-By: Express Date: Thu, 20 Jun 2019 08:11:40 GMT Connection: keep-alive Transfer-Encoding: chunkede 1561018300451e 1561018301454e 1561018302456e 1561018303457e 1561018304458e 1561018305460e 1561018306460

每行前面的一個(gè)字節(jié)的?e?,為 16 進(jìn)制的 14 這個(gè)數(shù)字,也就是后面緊跟著的內(nèi)容的長(zhǎng)度,是?Chunk?格式的要求。

Tornado 中的類似實(shí)現(xiàn)是:

# -*- coding: utf-8 -*-import tornado.ioloop import tornado.web import tornado.gen import timeclass MainHandler(tornado.web.RequestHandler):@tornado.gen.coroutinedef get(self):while True:yield tornado.gen.sleep(1)s = time.time()self.write(str(s))print(s)yield self.flush()def make_app():return tornado.web.Application([(r"/", MainHandler),])if __name__ == "__main__":app = make_app()app.listen(8888)tornado.ioloop.IOLoop.current().start()

Express?中的實(shí)現(xiàn),有個(gè)大坑,就是:

app.all('/', (req, res) => {const f = () => {const t = new Date().getTime() + '\n';res.write(t);console.log(t);setTimeout(f, 1000);}setTimeout(f, 1000); });

這段邏輯,在連接已經(jīng)斷了的情況下,并不會(huì)停止,還是會(huì)永遠(yuǎn)執(zhí)行下去。所以,你得自己處理好:

const process = require('process'); const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(cookieParser("key"))app.set('trust proxy', false); app.set('views', process.cwd() + '/template'); app.set('view engine', 'html'); app.engine('html', (path, options, callback) => {callback(false, '<h1>Hello</h1>'); });app.all('/', (req, res) => {let close = false;const f = () => {const t = new Date().getTime() + '\n';res.write(t);console.log(t);if(!close){setTimeout(f, 1000);}}req.on('close', () => {close = true;});setTimeout(f, 1000); });app.listen(8888);

req?掛了一些事件的,可以通過(guò)?close?事件來(lái)得到當(dāng)前連接是否已經(jīng)關(guān)閉了。

req?上直接掛連接事件,從?net?http?Express?這個(gè)層次結(jié)構(gòu)上來(lái)說(shuō),也很,尷尬了。 Web 層不應(yīng)該關(guān)心到網(wǎng)絡(luò)連接這么底層的東西的。

我還是習(xí)慣這樣:

app.all('/', (req, res) => {res.write('<h1>123</h1>');res.end(); });

不過(guò)?res.write()?是不能直接處理 json 對(duì)象的,還是老老實(shí)實(shí)?res.send()?吧。

我會(huì)怎么用 Express

先說(shuō)一下,我自己,目前在?Express?運(yùn)用方面,并沒(méi)有太多的時(shí)間和復(fù)雜場(chǎng)景的積累。

即使這樣,作為技術(shù)上相對(duì)傳統(tǒng)的人,我會(huì)以我以往的 web 開(kāi)發(fā)的套路,來(lái)使用?Express?。

我不喜歡日常用?app.all(path, callback)?這種形式去組織代碼。

首先,這會(huì)使?path?定義散落在各處,方便了開(kāi)發(fā),麻煩了維護(hù)。

其次,把?path?和具體實(shí)現(xiàn)邏輯?callback?綁在一起,我覺(jué)得也是反思維的。至少,對(duì)于我個(gè)人來(lái)說(shuō),開(kāi)發(fā)的過(guò)程,先是想如何實(shí)現(xiàn)一個(gè)?handler?,最后,再是考慮要把這個(gè)?handle?與哪些?path?綁定。

再次,單純的?callback?缺乏層次感,用?app.use(path, callback)?這種來(lái)處理共用邏輯的方式,我覺(jué)得完全是扯談。共用邏輯是代碼之間本身實(shí)現(xiàn)上的關(guān)系,硬生生跟網(wǎng)絡(luò)應(yīng)用層 HTTP 協(xié)議的?path?概念抽上關(guān)系,何必呢。當(dāng)然,對(duì)于?callback?的組織,用純函數(shù)來(lái)串是可以的,不過(guò)我在這方面并沒(méi)有太多經(jīng)驗(yàn),所以,我還是選擇用類繼承的方式來(lái)作層次化的實(shí)現(xiàn)。

我自己要用?Express?,大概會(huì)這樣組件項(xiàng)目代碼(不包括關(guān)系數(shù)據(jù)庫(kù)的?Model?抽象如何組織這部分):

./ ├── config.conf ├── config.js ├── handler │?? ├── base.js │?? └── index.js ├── middleware.js ├── server.js └── url.js
  • config.conf?是 ini 格式的項(xiàng)目配置。
  • config.js?處理配置,包括日志,數(shù)據(jù)庫(kù)連接等。
  • middleware.js?是針對(duì)整體流程的擴(kuò)展機(jī)制,比如,給每個(gè)請(qǐng)求加一個(gè) UUID ,每個(gè)請(qǐng)求都記錄一條日志,日志內(nèi)容有請(qǐng)求的細(xì)節(jié)及本次請(qǐng)求的處理時(shí)間。
  • server.js?是主要的服務(wù)啟動(dòng)邏輯,整合各種資源,命令行參數(shù)?port?控制監(jiān)聽(tīng)哪個(gè)端口。不需要考慮多進(jìn)程問(wèn)題,(正式部署時(shí)?nginx?反向代理到多個(gè)應(yīng)用實(shí)例,多個(gè)實(shí)例及其它資源統(tǒng)一用?supervisor?管理)。
  • url.js?定義路徑與?handler?的映射關(guān)系。
  • handler?,具體邏輯實(shí)現(xiàn)的地方,所有?handler?都從?BaseHandler?繼承。

BaseHandler?的實(shí)現(xiàn):

class BaseHandler {constructor(req, res, next){this.req = req;this.res = res;this._next = next;this._finised = false;}run(){this.prepare();if(!this._finised){if(this.req.method === 'GET'){this.get();return;}if(this.req.method === 'POST'){this.post();return;}throw Error(this.req.method + ' this method had not been implemented');}}prepare(){}get(){throw Error('this method had not been implemented');}post(){throw Error('this method had not been implemented');}render(template, values){this.res.render(template, values, (err, html) => {this.finish(html);});}write(content){if(Object.prototype.toString.call(content) === '[object Object]'){this.res.write(JSON.stringify(content));} else {this.res.write(content);}}finish(content){if(this._finised){throw Error('this handle was finished');}this.res.send(content);this._finised = true;if(this._next){ this._next() }}}module.exports = {BaseHandler};if(module === require.main){const express = require('express');const app = express();app.all('/', (req, res, next) => new BaseHandler(req, res, next).run() );app.listen(8888); }

要用的話,比如?index.js?:

const BaseHandler = require('./base').BaseHandler;class IndexHandler extends BaseHandler {get(){this.finish({a: 'hello'});} }module.exports = {IndexHandler};

url.js?中的樣子:

const IndexHandler = require('./handler/index').IndexHandler;const Handlers = [];Handlers.push(['/', IndexHandler]);module.exports = {Handlers};

日志

后面這幾部分,都不屬于?Express?本身的內(nèi)容了,只是我個(gè)人,隨便想到的一些東西。

找一個(gè)日志模塊的實(shí)現(xiàn),功能上,就看這么幾點(diǎn):

  • 標(biāo)準(zhǔn)的級(jí)別: DEBUG,INFO,WARN, ERROR 這些。
  • 層級(jí)的多個(gè)?logger?。
  • 可注冊(cè)式的多種?Handler?實(shí)現(xiàn),比如文件系統(tǒng),操作系統(tǒng)的?rsyslog?,標(biāo)準(zhǔn)輸出,等。
  • 格式定義,一般都帶上時(shí)間和代碼位置。

Node.js 中,大概就是?log4js?了,github.com/log4js-node/log4js-node?。

const log4js = require('log4js');const layout = {type: 'pattern',pattern: '- * %p * %x{time} * %c * %f * %l * %m',tokens: {time: logEvent => {return new Date().toISOString().replace('T', ' ').split('.')[0];}} }; log4js.configure({appenders: {file: { type: 'dateFile', layout: layout, filename: 'app.log', keepFileExt: true },stream: { type: 'stdout', layout: layout }},categories: {default: { appenders: [ 'stream' ], level: 'info', enableCallStack: false },app: { appenders: [ 'stream', 'file' ], level: 'info', enableCallStack: true }} });const logger = log4js.getLogger('app'); logger.error('xxx');const l2 = log4js.getLogger('app.good'); l2.error('ii');

總的來(lái)說(shuō),還是很好用的,但是官網(wǎng)的文檔不太好讀,有些細(xì)節(jié)的東西沒(méi)講,好在源碼還是比較簡(jiǎn)單。

說(shuō)幾點(diǎn):

  • getLogger(name)?需要給一個(gè)名字,否則?default?的規(guī)則都匹配不到。
  • getLogger('parent.child')?中的名字,規(guī)則匹配上,可以通過(guò)?.?作父子繼承的。
  • enableCallStack: true?加上,才能拿到文件名和行號(hào)。

ini 格式配置

json 作配置文件,功能上沒(méi)問(wèn)題,但是對(duì)人為修改是不友好的。所以,個(gè)人還是喜歡用 ini 格式作項(xiàng)目的環(huán)境配置文件。

Node.js 中,可以使用?ini?模塊作解析:

const s = ` [database] host = 127.0.0.1 port = 5432 user = dbuser password = dbpassword database = use_this_database[paths.default] datadir = /var/lib/data array[] = first value array[] = second value array[] = third value `const fs = require('fs'); const ini = require('ini');const config = ini.parse(s); console.log(config);

它擴(kuò)展了?array[]?這種格式,但沒(méi)有對(duì)類型作處理(除了?true?false),比如,獲取?port?,結(jié)果是?"5432"?。簡(jiǎn)單夠用了。

WebSocket

Node.js 中的 WebSocket 實(shí)現(xiàn),可以使用?ws?模塊, github.com/websockets/ws?。

要把?ws?的 WebSocket Server 和?Express?的?app?整合,需要在?Express?的?Server?層面動(dòng)手,實(shí)際上這里說(shuō)的?Server?就是 Node.js 的?http?模塊中的?http.createServer()?。

const express = require('express'); const ws = require('ws');const app = express();app.all('/', (req, res) => {console.log('/');res.send('hello'); });const server = app.listen(8888);const wss = new ws.Server({server, path: '/ws'}); wss.on('connection', conn => {conn.on('message', msg => {console.log(msg);conn.send(new Date().toISOString());}); });

對(duì)應(yīng)的一個(gè)客戶端實(shí)現(xiàn),來(lái)自:?github.com/ilkerkesen/tornado-websocket-client-example/blob/master/client.py

# -*- coding: utf-8 -*-import time from tornado.ioloop import IOLoop, PeriodicCallback from tornado import gen from tornado.websocket import websocket_connectclass Client(object):def __init__(self, url, timeout):self.url = urlself.timeout = timeoutself.ioloop = IOLoop.instance()self.ws = Noneself.connect()PeriodicCallback(self.keep_alive, 2000).start()self.ioloop.start()@gen.coroutinedef connect(self):print("trying to connect")try:self.ws = yield websocket_connect(self.url)except Exception:print("connection error")else:print("connected")self.run()@gen.coroutinedef run(self):while True:msg = yield self.ws.read_message()print('read', msg)if msg is None:print("connection closed")self.ws = Nonebreakdef keep_alive(self):if self.ws is None:self.connect()else:self.ws.write_message(str(time.time()))if __name__ == "__main__":client = Client("ws://localhost:8888/ws", 5)


原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

總結(jié)

以上是生活随笔為你收集整理的Express 的使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。