Почему выбирают Хапи
Возможно, вы использовали веб-фреймворки Node.js, такие как Express, Koa2 и т. д. При создании веб-приложений ваша задача состоит только в создании RESTFUL API или вызове других сетевых интерфейсов через Node. Вам может показаться, что существует более простой способ обработки запросов или среда Node, которая не должна мучиться с поиском промежуточного программного обеспечения для использования на ранних этапах создания проекта. После сравнения нескольких фреймворков я решил использовать Hapi для рефакторинга моего проекта Koa2.
Hapi в настоящее время является звездой Github 10653, последняя версия — 17.5, а релизная версия — 18.x. Количество выпусков — 6, да, вы не ошиблись, однозначные числа. Видно, что внимание и обслуживание Хапи очень хорошие. Вы можете проверить последние разработки Hapi на официальном веб-сайте Hapi, включая представленные материалы, какие проблемы были изменены, учебник, в котором кратко представлены функции, документацию API с примерами, сообщества, плагины и ресурсы, которые используют Hapi. Hapi имеет полный набор плагинов, необходимых для создания WEB-приложений, некоторые из них предоставлены официально, некоторые предоставлены сообществом, и обычно эти плагины можно использовать где угодно, не полагаясь на Hapi, например Boom, Joi, Catbox.
Если вы хотите узнать о Hapi или о том, чем он отличается от других фреймворков, вы можете поискать соответствующую информацию в Google, в этой статье не будет слишком много введения в фреймворк.
Какие читатели
Вам не нужен опыт работы с Node, чтобы следовать этому руководству, вы можете использовать его в качестве вводного курса по Node. Если вы являетесь фронтенд-разработчиком, это руководство даст вам более четкое представление о том, что может сделать Node и как интерфейс и серверная часть выполняют свою работу. Вы также можете быть новичком в других платформах Node, и вы можете использовать это вводное руководство, чтобы сравнить различия между двумя платформами. Если вы уже являетесь опытным разработчиком Node, это руководство не для вас.
В этом руководстве меньше концепций и больше практического опыта, поэтому вы можете начать обучение, даже если у вас нет никакого опыта.
Подготовить
- установить узел
- Создать проект
- Инициализировать package.json
- Рекомендуемый редактор vscode
- Инструменты командной строки — cmder для Windows, iTerm2 для Mac
npm init -y
// or
npm init
// -y 参数 以默认方式初始化 package.json
- Установить Хапи
npm i hapi
// or
npm install hapi -D
// i 为 install 的缩写,不带任何参数时,默认值为 -D
сервис
// server.js
const Hapi = require('hapi')
const server = Hapi.server({
port: 3000,
host: 'localhost'
})
const init = async () => {
await server.start()
console.log(`Server running at: ${server.info.uri}`)
}
init()
выполнить в командной строке
node server.js
# Server running at: http://localhost:3000
# 说明我们的服务已经启动了
# 如果 3000 端口已经被占用了, 你可以修改 port 为其他端口
теперь мы посещаемhttp://localhost:3000, на странице будет отображаться ошибка 404, поскольку мы не настроили никаких路由
.
1. Маршрутизация
// server.js
const init = async () => {
server.route({
path: '/',
method: 'GET',
handler () {
return 'Hapi world'
}
})
await server.start()
console.log(`Server running at: ${server.info.uri}`)
}
Теперь перезапустите службу, и мы увидим, что на странице.
Затем мы создаем интерфейс API, который может возвращатьjson
данные
// server.js
server.route({
path: '/api/welcome',
method: 'GET',
handler () {
return {
code: 200,
success: true,
data: {
msg: 'welcome'
}
}
}
})
Перезапускаем сервис, заходим в гостиhttp://localhost:3000/api/welcome
мы получилиcontent-type
дляapplication/json
данные, мы можем передатьXMLHttpRequest
Библиотеки, такие как (jQuery Ajax, Axios, Fetch), для запроса этого интерфейса и получения данных JSON.
2. Пауза
Подождите, вы заметили, что каждый раз, когда мы модифицируем файл, нам приходится отключать службу и перезапускать ее вручную, что очень плохо, и теперь мы должны решить эту проблему.
npm i onchange
# 增加 onchange 模块
// package.json
"scripts": {
"dev": "node server.js",
"watch": "onchange -i -k '**/*.js' -- npm run dev"
},
Мы добавляем реализацию dev в поле scripts файла package.json. Таким образом, мы выполняемnpm run dev
эквивалентно до выполненияnode server.js
. использоватьonchange
package, отслеживать изменения моего js-файла и перезапускать службу при изменении файла.
попытайся
npm run watch
Затем мы изменяем возвращаемый результат api/welcome
обновите свой браузер
Смотреть! Нет необходимости вручную перезапускать сервис, каждый раз, когда вы вносите изменения, вам нужно только обновить браузер, чтобы увидеть результат.
Теперь нам не нужно слишком рано внедрять Nodemon, хотя он великолепен и хорошо работает.
3. Параметры
Теперь, когда мы можем запрашивать данные с сервера, нам также необходимо передать данные от клиента на сервер.Ниже мы представим несколько форм передачи параметров.
Давайте предположим несколько сценариев, чтобы понять, как получить параметры.
-
/api/welcome
Мы хотим, чтобы он возвращал переданное имя
// 修改路由
server.route({
path: '/api/welcome',
method: 'GET',
handler (request) {
return {
code: 200,
success: true,
data: {
msg: `welcome ${request.query.name}`
}
}
}
})
// 请求 http://localhost:3000/api/welcome?name=kenny
// msg: "welcome kenny"
- Параметр имени немного избыточен, потому что этот интерфейс принимает только этот параметр, поэтому теперь опустите это имя.
// 修改路由
server.route({
path: '/api/welcome/{name}',
method: 'GET',
handler (request) {
return {
code: 200,
success: true,
data: {
msg: `welcome ${request.params.name}`
}
}
}
})
// http://localhost:3000/api/welcome/kenny
// msg: "welcome kenny"
// 结果是一样的
- Предположим, нам нужно время от времени заменять наше приветствие, а не каждый раз модифицировать код, тогда нам нужен интерфейс для замены приветствия, и заменяем приветствие отправкой интерфейса.
let speech = {
value: 'welcome',
set (val) {
this.value = val
}
}
server.route({
path: '/api/welcome/{name}',
method: 'GET',
handler (request) {
return {
code: 200,
success: true,
data: {
msg: `${speech.value} ${request.params.name}`
}
}
}
})
server.route({
path: '/api/speech',
method: 'POST',
handler (request) {
speech.set(request.payload.word)
return {
code: 200,
success: true,
data: {
msg: `speech is *${speech.value}* now`
}
}
}
})
проверять
# 使用 curl 来验证一个 POST 接口,你也可以使用 Ajax,POSTMAN...等等 你所喜欢的方式。
curl --form word=你好 \
http://localhost:3000/api/speech
# {"code":200,"success":true,"data":{"msg":"speech is *你好* now"}}%
curl http://localhost:3000/api/welcome/kenny
# {"code":200,"success":true,"data":{"msg":"你好 kenny"}}%
Здесь следует отметить,content-type
application/x-www-form-urlencodedа такжеmultipart/form-dataизразница.
Подводя итог, вы можете использоватьrequest.query
чтобы получить данные строки запроса URL,request.payload
Получите данные тела запроса интерфейса POST,request.params
Получить пользовательские параметры в URL.
4. Вторая служба
У нас уже есть внутренний API-сервис, который соответствует внешнему сервису. Это может быть одностраничный сервис или традиционная внутренняя страница рендеринга, но обычно он не на том же порту, что и ваш внутренний сервис. Мы создаем еще один сервис для рендеринга страницы интерфейса, чтобы более реалистично имитировать реальную сцену.
+const client = Hapi.server({
+ port: 3002,
+ host: 'localhost'
+})
+
- server.route({
+ client.route({
+ await client.start()
Добавьте новую службу, прослушивайте порт 3002 и измените маршрут предыдущей домашней страницы на домашнюю страницу клиента.
доступhttp://localhost:3002Посмотреть эффект
5. Статические файлы
Раньше способом прямой отрисовки страницы была строка, что не способствовало написанию и модификации, мы изменили способ отдачи HTML на «шаблонную» отрисовку.
# 安装所需依赖包
npm i inert
# 创建 public 文件夹
mkdir public
# 创建 index.html
touch public/index.html
# 创建 about.html
touch public/about.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<title>Document</title>
</head>
<body>
<h1>Hapi world</h1>
</body>
</html>
// ...
const client = Hapi.server({
port: 3002,
host: 'localhost',
routes: {
files: {
relativeTo: Path.join(__dirname, 'public')
}
}
})
// ...
// const init = async () => {
await client.register(Inert)
client.route({
path: '/{param*}',
method: 'GET',
handler: {
directory: {
path: '.',
index: true,
}
}
})
// ...
Посетите, чтобы посмотреть производительность
/index.htmlЭтот путь с расширением не кажется таким уж профессиональным, давайте его модифицируемdirectory
Конфигурация
directory: {
+ defaultExtension: 'html'
доступhttp://localhost:3002/index
6. Запрос между источниками
Мы не будем слишком много говорить о браузерах.Та же политика происхождения, когда существующий клиент (порт 3002) инициирует XHRHttpRequest для запроса интерфейса сервера (порт 3000), он столкнется сCORSПроблема, затем мы должны разрешить запросы от клиентов на стороне сервера, установивAccess-Control-Allow-Origin
и другие заголовки ответа, чтобы разрешить запросы из разных источников.
// index.html
$.ajax({
url: 'http://localhost:3000/api/welcome/kenny'
}).then(function (data) {
console.log(data)
})
доступhttp://localhost:3002/indexjs будет сообщено о междоменной ошибке
Access to XMLHttpRequest at 'http://localhost:3000/api/welcome/kenny' from origin 'http://localhost:3002' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
// server.js
const server = Hapi.server({
port: 3000,
host: 'localhost',
routes: {
cors: {
origin: '*'
}
}
})
После сохранения вы обнаружите, что в терминале будет следующая ошибка
[1] "origin" must be an array
Это еще одно преимущество Hapi, проверка конфигурации, потому что Hapi, как фреймворк, ориентированный на настройку, выполняет множество проверок конфигурации.
origin: ['*']
Затем обновите страницу, вы обнаружите, что междоменная ошибка исчезла.
Что касается междоменного доступа, мы не упомянули:
- Разрешает указанное доменное имя и несколько доменных имен
- Разрешить файлы cookie [Access-Control-Allow-Credentials]
- Разрешить доступ к дополнительной информации заголовка [Access-Control-Expose-Headers]
- Информация о разрешенных заголовках [Access-Control-Allow-Headers]
7. Чего еще не хватает?
В настоящее время у нас есть front-end сервис для веб-рендеринга и back-end сервис, предоставляющий интерфейсы, и они находятся в разных "доменах" (портах).Front-end страницы могут иметь однообразное написание, без картинок и стилей, и нет фавикона.
- Скачайте понравившуюся фавиконку
- Импорт собственного CSS
- импортировать локальное изображение
Поместите их все в каталог /public
...
<head>
...
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="/bulma.min.css">
</head>
...
<html>
<img class="logo" src="/logo.svg" />
...
8. Cookie
Предположим, у нас есть логин/login
интерфейс, после успешного входа в систему установите поле входа в файл cookie, внешний интерфейс может использовать этот вход, чтобы определить, вошли ли вы в систему, и может пройти/logout
Выход.
// ...
server.state('login', {
ttl: null, // 时效
isSecure: false, // https
isHttpOnly: false, // http Only
encoding: 'none', // encode
clearInvalid: false, // 移除不可用的 cookie
strictHeader: true // 不允许违反 RFC 6265
})
// ...
const init = async () => {
// ...
server.route({
path: '/api/login',
method: 'POST',
handler (request, h) {
let body
let code
// 获取 cookie
const isLogin = request.state.login
if (isLogin) {
body = {
msg: '已登录'
}
code = 200
} else if (request.payload && request.payload.email === 'kenny@gmail.com' && request.payload.password === '123456') {
// 设置 cookie
h.state('login', 'true')
body = {
msg: '登录成功'
}
code = 200
} else {
code = 100
body = {
msg: '登录信息有误'
}
}
return {
code,
success: true,
data: body
}
}
})
server.route({
path: '/api/logout',
method: 'POST',
handler (request, h) {
// 取消 cookie
h.unstate('login')
return {
code: 200,
success: true
}
}
})
Этот пример не подходит для реальных бизнес-сценариев, он просто описывает, как проще устанавливать и отменять файлы cookie.
9. Аутентификация и авторизация
Концепция сертификации может быть трудной для понимания при входе на рынок, например, более часто используемаяJWT(JSON Web Token), не тратьте время на объяснение того, как его использовать, если хотите понять, что такое JWT, портал:Learn how to use JSON Web Tokens (JWT) for Authentication. Во фреймворке Hapi мы используем hapi-auth-jwt2
Вот удобство настройки аутентификации в Hapi.
В Express/Koa2 вам нужно
- Внедрить плагины
- Промежуточное ПО обрабатывает 401
- ПО промежуточного слоя сопоставляет маршруты, требующие аутентификации, и исключает маршруты, не требующие аутентификации.
Когда в вашем проекте будет достаточно маршрутов, это правило сопоставления будет становиться все более и более сложным. Или вы можете позаботиться о названии маршрутов, что расстраивает перфекционистов. Вынесение суждений по одному маршруту — это повторяющаяся операция.
Давайте посмотрим на использование Hapi.
// 引入插件
await server.register(require('hapi-auth-jwt2'))
// 自定义一个你的认证方法
const validate = async function (decoded, request) {
return {
isValid: true
}
}
// 设置认证
server.auth.strategy('jwt', 'jwt', {
key: 'your secret key',
validate,
verifyOptions: {
algorithms: ['HS256']
},
cookieKey: 'token'
})
// 一个需要认证的路由
server.route({
path: '/user/info',
method: 'GET',
options: {
auth: 'jwt'
},
// ...
})
// 一个需要认证可选的路由
server.route({
path: '/list/recommond',
method: 'GET',
options: {
auth: {
strategy: 'jwt',
mode: 'optional'
}
},
// ...
})
// 一个需要认证尝试的路由
server.route({
path: '/list/recommond',
method: 'GET',
options: {
auth: {
strategy: 'jwt',
mode: 'try'
}
},
// ...
})
Разница между попыткой и необязательным заключается в возврате после ошибки аутентификации.Правило аутентификации необязательного заключается в том, что вы не можете его иметь, но оно должно быть правильным, если оно у вас есть. try не имеет значения, он не вернет ошибку 401.
Видно, что аутентификация в Hapi настраивается на маршруте, что делает необходимым только настройку соответствующих правил при управлении модулями аутентификации и неаутентификации, не беспокоясь о том, что глобальная конфигурация была изменена по ошибке.
10. Журналы
Когда запрос получен, или сделан запрос на сервисе, нам негде его просмотреть, и теперь добавляется система логов.
npm i hapi-pino
await server.register({
plugin: require('hapi-pino'),
options: {
prettyPrint: true // 格式化输出
}
})
Повторно обслужить и посетить '/api/logout'
Взгляните на явное
[1547736441445] INFO (82164 on MacBook-Pro-3.local): server started
created: 1547736441341
started: 1547736441424
host: "localhost"
port: 3000
protocol: "http"
id: "MacBook-Pro-3.local:82164:jr0qbda5"
uri: "http://localhost:3000"
address: "127.0.0.1"
Server running at: http://localhost:3000
[1547736459475] INFO (82164 on MacBook-Pro-3.local): request completed
req: {
"id": "1547736459459:MacBook-Pro-3.local:82164:jr0qbda5:10000",
"method": "post",
"url": "/api/logout",
"headers": {
"cache-control": "no-cache",
"postman-token": "b4c72a2f-38ab-4c5c-9559-211e0669e6cf",
"user-agent": "PostmanRuntime/7.4.0",
"accept": "*/*",
"host": "localhost:3000",
"accept-encoding": "gzip, deflate",
"content-length": "0",
"connection": "keep-alive"
}
}
res: {
"statusCode": 200,
"headers": {
"content-type": "application/json; charset=utf-8",
"vary": "origin",
"access-control-expose-headers": "WWW-Authenticate,Server-Authorization",
"cache-control": "no-cache",
"set-cookie": [
"login=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict"
],
"content-length": 27
}
}
responseTime: 16
Можно сказать, что очень полный журнал, и с эффектами окраски.
11. Документация
Со временем разработки к вашему проекту добавляется все больше и больше интерфейсов.Когда вы сотрудничаете с другими людьми или хотите найти определение интерфейса, хороший документ заставит вас делать больше с меньшими затратами.
await server.register({
plugin: require('lout')
})
Поскольку Hapi является платформой, ориентированной на конфигурацию, документы также могут быть созданы на основе конфигурации.Пока вы описываете маршрут в определенной степени, будет создан пригодный для использования документ.
доступhttp://localhost:3000/docsПосмотреть эффект
12. Интерфейс переадресации
отменен
Как использовать пример
Содержимое, упомянутое в этой статье, было загруженоgithub
Вы можете просмотреть код после клонирования проекта. Также вы можете переключаться на другие шаги (git checkout HEAD)
# 查看commit
git log --pretty=online
51b2a7eea55817c1b667a34bd2f5c5777bde2601 part 9 api doc
fbb1a43f0f1bf4d1b461c4c59bd93b27aabc3749 Part8 cookies
00a4ca49f733894dafed4d02c5a7b937683ff98c Part7 static
ea2e28f2e3d5ef91baa73443edf1a01a383cc563 Part7 cors
a0caaedbf492f37a4650fdc33d456fa7c6ef46d3 Part6 html render
12fce15043795949e5a1d0d9ceacac8adf0079e8 Part5 client server
79c68c9c6eaa064a0f8c679ae30a8f851117d7e0 Part4 request.payload
e3339ff34d308fd185187a55f599feed1e46753e Part4 request.query
af40fc7ef236135e82128a3f00ec0c5e040d4b12 Part3 restart when file changed
2b4bd9bddfe565fd99c7749224e14cc7752525b1 Part2 route 2
99a8f8426f43fea85f98bc9a3b189e5e3386abfe Part2 route
047c805ca7fe44148bac85255282a4d581b5b8e1 Part1 server
# 切换至 Part5
git checkout 12fce15043795949e5a1d0d9ceacac8adf0079e8
конец
В настоящее время уровень завершения учебника составляет 80%. Из-за ограниченной энергии в настоящее время он временно обновляется здесь. В будущем он будет обновляться до удовлетворительного уровня в соответствии с мнениями и предложениями читателей.
Еще раз спасибо за чтение, и если вы найдете это руководство полезным, пожалуйста, ретвитите комментарии. Конечно, вы также можете наградить.
Если у вас есть лучшие предложения для этого урока, пожалуйста, свяжитесь со мной.