آموزش کار با Middleware در فریمورک Express.js
۱۸ اسفند ۱۳۹۹
در این مقاله قصد داریم به شما نحوهی کار با Middleware در فریمورک Express.js که یک فریمورک برای توسعهی برنامههای Node.js است را آموزش دهیم و همچنین بهسراغ Middlewareهایی میرویم که برای راهاندازی اکثر برنامههای وب ضروری هستند. نحوهی استفاده از هر کدام را بررسی خواهیم کرد و شما میتوانید براساس نیاز خود، آنها را پیکربندی کنید.
Middleware چیست؟
Middleware بخشی از فریمورک Express.js است که در طول request-response cycle یعنی زمان دریافت request در سرور تا زمان ارسال response، اجرا میشود و به آبجکتهای request و response دسترسی دارد.
حال Express middleware در انواع و سطحهای مختلفی ارائه میشود:
- Application-level middleware
- Router-level middleware
- Error-handling middleware
- Built-in middleware
- Third-party middleware
و توسعهدهندگان به دلیل عملکرد محدود Express.js در برنامههای توسعه داده شده با این فریمورک از Middlewareها برای پیادهسازی عملکردهای مختلف استفاده میکنند. شما میتوانید Middleware خود را برای Express.js توسعه دهید اما اکثر توسعهدهندگان ترجیح میدهند از ابزارهای built-in و third-party برای عمدهی کارها استفاده کنند. بههمین منظور نحوهی استفاده از پنج Express middleware محبوب را در ادامهی این مقاله آموزش میدهیم اما قبل از آن بایستی به نحوهی کار Middleware در برنامه بپردازیم.
نحوهی کار Middleware در برنامه
برای درک نحوهی کار Middleware در برنامه فرض کنید که شما لیموناد درست میکنید اما لیموترشهای مورد نیاز برای تهیهی لیموناد را خود مشتریان برای شما میآورند بنابراین شما مسئول ارزیابی طراوت و سلامت لیموها، دور ریختن لیموهای نامناسب و درنهایت تهیه لیموناد هستید.
حال برای اینکه فشار کاری خود را کاهش دهید، محمد را استخدام میکنید و از او میخواهید تا مطمئن شود که لیموهایی تهیه شده توسط مشتریان، ارگانیک و تازه باشد. در این مثال، محمد یک Middleware میان شما و لیموترشهایی است که مشتریان برای شما میآورند.
فرض کنیم که این کار برای شما سودآور بوده است، بنابراین دو کارگر دیگر با نامهای علی و رضا استخدام میکنید. محمد مسئول رسیدگی به ارگانیگ بودن لیموها میشود و لیموهای ارگانیک را به علی میدهد تا لیموهای پوسیده را دور ریخته و لیموهای مناسب به دست رضا برسد تا طراوت آنها را تایید کند. درنهایت شما لیموهای تایید شده توسط رضا را برای درست کردن لیمونادها استفاده میکنید.
در این صورت است که شما میتوانید روی تهیهی لیمونادها و افزایش سود خود تمرکز کنید.
برای تبدیل این مثال به یک مثال در حوزهی فناوری میتوان لیموها را درخواستهای HTTP و لیموناد را Server دانست. قبل از تایید یا رد کردن یک درخواست HTTP بایستی منشا آن بررسی شود زیرا نمیتوانیم به همهی درخواستها حتی اگر از یک منبع معتبر باشند، اعتماد کنیم بنابراین به فیلتر کردن آنها میپردازیم.
اگر صحت یک درخواست HTTP توسط هر کدام از Middlewareها تایید نشد، میتوانیم آن درخواست را از چرخهی request و response خارج کنیم. همچنین درخواستهایی که از Middlewareهای برنامههای شما عبور میکنند، دراختیار Controller برنامه قرار داده میشوند.
البته این یک مثال بسیار ساده بود. مطمئنا در یک سناریو واقعی ممکن است از چندین Middleware برای انجام یک کار واحد مانند ورود کاربر به سیستم استفاده شود.
راهاندازی یک API ساده با Express.js
برای آموزش نحوهی استفاده از Middlewareها به یک Express API ساده نیاز داریم که در ادامهی این بخش به توسعه و راهاندازی آن میپردازیم.
دستورهای زیر را بهترتیب در Terminal سیستمعامل خود وارد کنید:
mkdir express-api
cd express-api
npm init -y
دستور آخر یعنی npm init -y
یک فایل با نام package.json
در مسیر فعلی شما یعنی express-api
ایجاد میکند که محتوای آن بهصورت زیر است:
{
"name": "express-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MIT"
}
حال برای نصب Express بایستی دستور زیر را اجرا کنید:
npm install express
و همانطور که فایل main برنامهی ما index.js
است بایستی یک فایل با همین نام در این مسیر ایجاد کنید:
- Linux:
touch index.js
- Widnows:
.> index.js
پس از ایجاد فایل index.js
، برای ایجاد یک Express API بایستی کدهای زیر را در آن فایل قرار دهید:
const express = require("express");
const app = express();
// Port
const port = 3000;
app.get("/", (req, res) => {
res.json({
message: "Hello Stranger! How are you?",
});
});
// Listen
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
در مرحلهی بعد بایستی با اجرای دستور زیر، ابزار nodemon
را بهعنوان dev dependency پروژه نصب کنید:
npm install -D nodemon
با استفاده از این ابزار دیگر نیازی نیست که هر مرتبه سرور Express.js خود را مجددا راهاندازی کنیم زیرا nodemon
تغییرهای ایجاد شده را شناسایی کرده و بهطور خودکار سرور را راهاندازی میکند.
برای استفاده از این ابزار بایستی بخش "scripts"
را در فایل package.json
خود تغییر دهید:
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
درنهایت پس از انجام همهی این کارها، فایل package.json
شما به شکل زیر خواهد بود:
{
"name": "express-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"nodemon": "^2.0.6"
}
}
حال برای راهاندازی سرور میتوانید دستور زیر را اجرا کنید:
npm run dev
که خروجی اجرای دستور فوق، به شکل زیر است:
[nodemon] 2.0.5
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.js`
Listening on port: 3000
پس از راهاندازی برنامه میتوانید از طریق آدرس localhost:3000، خروجی برنامه را مشاهده کنید:
{
"message": "Hello Stranger! How are you?"
}
نحوهی استفاده از Express middlewareهای محبوب
در مرحلهی قبل یک Express API ساده را راهاندازی کردیم. حال در این بخش قصد داریم تا به بررسی و استفاده از پنج Express middleware محبوب بپردازیم:
۱) Morgan
morgan را میتوان یک logger middleware دانست که برای تمام درخواستهایی که به API ارسال میشوند، logهایی را تولید میکند. بهترین نقطهی قوت این ابزار در این است که شما میتوانید یک فرمت از پیش تعریف شده یا یک فرمت جدید را بر اساس نیاز خود تعریف و استفاده کنید.
برای نصب morgan
، دستور زیر را اجرا کنید:
npm install morgan
morgan
شامل بسیاری فرمتهای از پیش تعریف شده است که شما میتوانید از آنها استفاده کنید. بسیاری از توسعهدهندگان ترجیح میدهند از خروجی استاندارد logهای Apache استفاده کنند.
برای استفاده از morgan
، فایل index.js
برنامهی خود را به شکل زیر تغییر دهید:
const express = require("express");
const morgan = require("morgan")
const app = express();
// Middlewares
app.use(morgan("common"))
// Port
const port = 3000;
app.get("/", (req, res) => {
res.json({
message: "Hello Stranger! How are you?",
});
});
// Listen
app.listen(port, ()=>{
console.log(`Listening on port: ${port}`)
})
پس از ایجاد تغییرهای فوق میتوانید مشاهده کنید که پس از راهاندازی مجدد برنامه توسط nodemon
، خروجی زیر با ارسال هر درخواست به localhost:3000 نمایش داده میشود:
::ffff:127.0.0.1 - - [14/Oct/2020:09:21:16 +0000] "GET / HTTP/1.1" 304 -
قبل از مطالعهی ادامهی مقاله بایستی متذکر شویم که Middlewareها بر اساس ترتیبی که آنها را تعریف میکنید، اجرا میشوند.
۲) Helmet
Helmet یک Middleware است که بهمنظور تامین امنیت برنامههای Express.js با تنظیم Headerهای مختلف استفاده میشود. برای درک بهتر نحوهی کار Helmet
، وارد آدرس localhost:3000 شوید و سپس کلیدهای ترکیبی CTRL + Shift + J
را در مرورگر Chrome یا CTRL + Shift+ K
را در مرورگر Firefox فشار دهید و به تب Network بروید.
اگر تب Network خالی بود، دکمهی F5
را فشار دهید تا صفحه مجددا بارگیری شود.
در تصویر فوق میتوانید درخواست favicon
را نادیده بگیرید زیرا در بخش دیگری به آن میپردازیم اما توجه خود را به بخش درخواستهای GET /
و Response Headerها متمرکز کنید.
ممکن است متوجه آسیبپذیری خاصی نشوید اما مهاجمین میتوانند از تعیین نشدن Headerهای مناسب، برای اجرای حملههای خود استفاده کنند. بهویژه فیلد X-Powered-By: Express
نمایانگر آن است که برنامهی شما توسط Express.js توسعه داده شده و این موضوع خطر را جدیتر میکند.
Helmet
مجموعهای از دوازده Middleware است که از برنامهی شما در برابر آسیبپذیریها و حملههای شناخته شده، محافظت میکند. دستور زیر را برای نصب helmet
اجرا کنید:
npm install --save helmet
پس از نصب این ابزار بایستی تغییرهایی را در فایل index.js
بهمنظور استفاده از helmet
ایجاد کنید:
const express = require("express");
const morgan = require("morgan")
const helmet = require("helmet");
const app = express();
// Middlewares
app.use(morgan("common"))
app.use(helmet());
// Port
const port = 3000;
app.get("/", (req, res) => {
res.json({
message: "Hello Stranger! How are you?",
});
});
// Listen
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
پس از ایجاد این تغییرها، آدرس localhost:3000 را مجددا باز کرده و به تب Network بروید.
همانطور که مشاهده میکنید، موارد جدیدی در Response Headerها تعریف شده است و همچنین فیلد X-Powered-By: Express
پاک شده. علاوهبراینها میتوانید helmet()
را بهگونهای پیکربندی کنید که برخی عملکردهای آن غیرفعال شود:
// This disables the `referrerPolicy` middleware but keeps the rest.
app.use(
helmet({
referrerPolicy: false,
})
);
۳) CORS
CORS مخفف شدهی Cross-Origin Resource Sharing است. حال در این بخش به فعال کردن و پیکربندی CORS در برنامههای Express.js میپردازیم.
تصور کنید که شما یک برنامهنویس full-stack هستید و فرانتاند برنامهی خود را با React در پورت 3000 راهاندازی کردهاید و بکاند برنامه توسط Express در پورت 8000 راهاندازی شده است. با این سناریو درخواستها که از سمت کاربر یا همان فرانتاند برنامه به بکاند ارسال میشود اما اگر توجه کنید، تمام درخواستها با خطا روبرو خواهند شد زیرا درخواستها از یک مبدا متفاوت از سرور Express، به برنامه ارسال شدهاند.
بنابراین شما بایستی سرور را بهگونهای پیکربندی کنید که حتی درخواستهای مبداهای دیگر، پذیرفته شود. دستور زیر را برای نصب cors
اجرا کنید:
npm install --save cors
و پس از آن فایل index.js
را به شکل زیر تغییر دهید:
const express = require("express");
const morgan = require("morgan")
const helmet = require("helmet");
const cors = require("cors");
const app = express();
// Middlewares
app.use(morgan("common"))
app.use(helmet());
app.use(cors())
// Port
const port = 3000;
app.get("/", (req, res) => {
res.json({
message: "Hello Stranger! How are you?",
});
});
// Listen
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
با اضافه کردن کد app.use(cors())
تمام درخواستها از هر مبدایی پذیرفته میشوند که در بعضی موارد این موضوع میتواند برنامهی شما را آسیبپذیر کند. بیایید مثال قبلی که دربارههای برنامهای با فرانتاند React و بکاند Express.js بود را مجددا در نظر بگیریم. بهجای قبول کردن همهی درخواستها از مبداهای مختلف میتوانیم یک white list از مبداهای مورد اعتماد تهیه کرده و فقط درخواستهایی که از آن مبداها ارسال میشوند را پاسخ دهیم:
// whitelist
const whitelist = ['http://localhost:3000', 'http://localhost:3001']
const corsOptions = {
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
}
}
app.use(cors(corsOptions));
برای کسب اطلاعات بیشتر در رابطه با CORS میتوانید به مستندات موجود در سامانه MDN مراجعه کنید.
۴) Express Rate Limit
Express Rate Limit یک Middleware برای محدود کردن تعداد درخواستها در Express.js است. یعنی به کمک این Middleware میتوانیم تعداد درخواستهای مکرر از یک آدرس IP را محدود کنیم.
دستور زیر را برای نصب express-rate-limit
اجرا کنید:
npm install --save express-rate-limit
برای استفاده از این Middleware بایستی در فایل index.js
یک متغیر با نام limiter
ایجاد کرده و در ادامه از آن برای پیکربندی express-rate-limit
استفاده کنیم:
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
با استفاده از کد فوق، هر آدرس IP به ۱۰۰ درخواست در مدت زمان ۱۵ دقیقه محدود میشود.
برای اعمال محدودیت فوق بایستی فایل index.js
را بهصورت زیر بهروزرسانی کنید:
const express = require("express");
const morgan = require("morgan")
const helmet = require("helmet");
const cors = require("cors");
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
const app = express();
// Middlewares
app.use(morgan("common"))
app.use(helmet());
app.use(cors())
app.use(limiter); // apply to all requests
// Port
const port = 3000;
app.get("/", (req, res) => {
res.json({
message: "Hello Stranger! How are you?",
});
});
// Listen
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
برای درک بهتر این موضوع میتوانید محدودیت زمانی و تعداد درخواستها را کمتر کنید:
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 2, // limit each IP to 2 requests per windowMs
message: "Too many accounts created from this IP, please try again after a minute"
});
حال اگر وارد آدرس localhost:3000 شوید و صفحهها را سه یا چهار مرتبه رفرش کنید، پیغام Too many accounts created from this IP, please try again after a minute
به شما نمایش داده میشود.
از آنجا که morgan
هنوز کار میکند، میتوانید گزارشها را در Terminal مشاهده کنید:
::1 - - [14/Nov/2020:08:15:58 +0000] "GET / HTTP/1.1" 304 -
::1 - - [14/Nov/2020:08:15:59 +0000] "GET / HTTP/1.1" 304 -
::1 - - [14/Nov/2020:08:15:59 +0000] "GET / HTTP/1.1" 429 71
کد ۴۲۹ نشان میدهد که کاربر درخواستهای زیادی را بهنسبت مدت زمان مشخص شده، ارسال کرده است. علاوهبراینها شما میتوانید express-rate-limit
را بهگونهای پیکربندی کنید که فقط درخواست به Routeهای خاصی را محدود کند:
// apply to all requests
app.use(limiter);
// only apply to requests that begin with /api/
app.use("/api/", limiter);
همچنین در لیست زیر میتوانید برخی دیگر از Middlewareهایی که برای محدود کردن تعداد درخواستها استفاده میشوند را مشاهده کنید:
۵) serve-favicon
serve-favicon
یک Middleware برای serve کردن favicon در Express.js است. احتمالا بهخاطر دارید که تب Network را در بخشهای قبلی باز کرده بودیم و یک درخواست ناموفق برای favicon در تب Network وجود داشت.
favicon یک آیکون کوچک است که اغلب در سمت چپ عنوان صفحه در نوار آدرس نمایش داده میشود. حال برای نصب serve-favicon
بایستی دستور زیر را اجرا کنید:
npm install serve-favicon
همچنین به یک favicon در مسیر پروژه نیاز خواهید داشت. پس از قراردادن favicon در مسیر پروژه میتوانید فایل index.js
را به شکل زیر بهروزرسانی کنید:
const express = require("express");
const morgan = require("morgan")
const helmet = require("helmet");
const cors = require("cors");
const rateLimit = require("express-rate-limit");
var favicon = require('serve-favicon')
const limiter = rateLimit({
windowMs: 15 *60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: "Too many accounts created from this IP, please try again after a minute"
});
const app = express();
// Serve Favicon
app.use(favicon('favicon.ico'))
// Middlewares
app.use(morgan("common"))
app.use(helmet());
app.use(cors())
app.use(limiter); // apply to all requests
// Port
const port = 3000;
app.get("/", (req, res) => {
res.json({
message: "Hello Stranger! How are you?",
});
});
// Listen
app.listen(port, () => {
console.log(`Listening on port: ${port}`);
});
اگر favicon شما در پوشهی public
قرار داشته باشد میتوانید از path
استفاده کنید:
var path = require('path')
...
// Serve Favicon
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))
...
حال اگر آدرس localhost:3000 را مجددا بارگیری کنید، favicon انتخابی شما نمایش داده میشود.
تب Network را باز کرده و صفحه را reload کنید.
با استفاده از serve-favicon
میتوانید برای بهینهسازی عملکرد برنامهی وب خود، favicon را در حافظهی دیسک کاربر، cache کنید. بهطور پیشفرض با استفاده از serve-favicon
، آیکون مورد نظر بهمدت یک سال cache میشود:
Cache-Control: public, max-age=31536000
اما برای تغییر این مدت زمان میتوانید از مقدار maxAge
استفاده کنید:
// Serve Favicon
app.use(
favicon("favicon.ico", {
maxAge: 500 * 60 * 60 * 24 * 1000,
})
);
منبع: https://blog.logrocket.com/express-middleware-a-complete-guide