node.js hapi_使用Hapi使用JavaScript异步构建安全的Node.js应用程序

node.js hapi

At the core of the JavaScript language is its asynchronous programming model. Unfortunately, dealing with callback functions has long been a source of frustration for many developers. JavaScript Promises helped make writing complex asynchronous code more manageable, but brought its own set of challenges. With the introduction of async functions in ES2017 (and the async and await keywords), writing asynchronous JavaScript is now much easier.

JavaScript语言的核心是其异步编程模型。 不幸的是,处理回调函数长期以来一直使许多开发人员感到沮丧。 JavaScript Promises帮助使编写复杂的异步代码更易于管理,但也带来了一系列挑战。 随着ES2017中引入异步函数(以及asyncawait关键字),编写异步JavaScript现在变得更加容易。

Hapi is one of many frameworks available for Node.js designed for building scalable web application and services. With the release of version 17, hapi has been completely overhauled to use JavaScript async functions. The result is a modern framework for Node.js that is a pleasure to use.

Hapi是可用于Node.js的众多框架之一,这些框架旨在构建可伸缩的Web应用程序和服务。 随着版本17的发布,已经彻底修改了hapi以使用JavaScript异步功能。 结果是使用Node.js的一种现代框架。

Most Node.js tutorials available today were written for older versions of Node.js using older ES5 syntax. In this tutorial, you will learn the basics of creating a Node.js web application with hapi using more modern JavaScript.

今天可用的大多数Node.js教程都是使用较旧的ES5语法为较旧版本的Node.js编写的。 在本教程中,您将学习使用更现代JavaScript使用hapi创建Node.js Web应用程序的基础知识。

创建您的第一个hapi Web服务器 ( Create Your First hapi Web Server )

Open up a terminal (Mac/Linux) or a command prompt (Windows) and type the following command:

打开终端(Mac / Linux)或命令提示符(Windows)并键入以下命令:

node --version

If you get an error, or the version of Node.js you have is less than version 8, you'll need to install Node.js. On Mac or Linux, I recommend you first install nvm and use nvm to install Node.js. On Windows, I recommend you use Chocolatey.

如果出现错误,或者您拥有的Node.js版本低于版本8,则需要安装Node.js。 在Mac或Linux上,建议您首先安装nvm,然后使用nvm安装Node.js。 在Windows上,建议您使用Chocolatey

After ensuring you have a recent version of Node.js installed, create a folder for your project.

确保安装了最新版本的Node.js之后,为您的项目创建一个文件夹。

mkdir learning-hapi
cd learning-hapi

A package.json file is required for your Node.js project and includes things like project information, scripts, and project dependencies. Use the npm command to create a package.json file in the project folder.

您的Node.js项目需要package.json文件,其中包括项目信息,脚本和项目依赖项。 使用npm命令在项目文件夹中创建package.json文件。

npm init -y

Next, install hapi as a dependency.

接下来,安装hapi作为依赖项。

npm install hapi

Now open the project in your editor of choice.

现在,在您选择的编辑器中打开项目。

If you don't already have a favorite code editor, I recommend installing Visual Studio Code. VS Code has exceptional support for JavaScript and Node.js, such as smart code completion and debugging. There's also a vast library of free extensions contributed by the community.

如果您还没有喜欢的代码编辑器,建议安装Visual Studio Code 。 VS Code对JavaScript和Node.js具有出色的支持,例如智能代码完成和调试。 还有一个由社区提供的庞大的免费扩展库。

Next, create a folder named src. In this folder, add a new file named index.js. Open the file and add the following JavaScript.

接下来,创建一个名为src的文件夹。 在此文件夹中,添加一个名为index.js的新文件。 打开文件并添加以下JavaScript。

"use strict";

const Hapi = require( "hapi" );

const port = 8080;
const server = Hapi.server( { port } );

// Define a route for the URL http://localhost:8080/
server.route( {
    method: "GET",
    path: "/",
    handler: () => {
        // a handler can return text, HTML, JSON,
        // a file, or just about anything.
        return "My first hapi server!";
    }
} );

const start = async () => {
    try {
        // start the server
        await server.start();
        console.log( `Server running at http://localhost:${ port }` );
    } catch ( err ) {
        console.log( err );
        process.exit( 1 );
    }
};

start();

As you can see in the previous code, the line const start = async () => { declares an asynchronous function named start using the arrow function syntax. server.start() is itself an asynchronous function, which requires the await keyword. The await keyword instructs the application to pause execution until the async function completes before moving on to the next line of code.

如您在前面的代码中所见, const start = async () => {使用箭头函数语法声明了一个名为start的异步函数。 server.start()本身是一个异步函数,需要await关键字。 关键字await指示应用程序暂停执行,直到异步功能完成为止,然后再继续执行下一行代码。

Dealing with errors in asynchronous code before async/await was tricky at best. Another advantage of using async/await is the ability to use straight-forward try/catch blocks to catch any errors that may occur.

async/await之前处理异步代码中的错误充其量是棘手的。 使用async / await另一个优点是可以使用直接try / catch块来捕获可能发生的任何错误。

Next, edit the package.json file and change the "main" property value to "src/index.js". This property points Node to a file to execute when the application starts.

接下来,编辑package.json文件,并将"main"属性值更改为"src/index.js" 。 该属性将Node指向要在应用程序启动时执行的文件。

"main": "src/index.js",

Now you can start the application. Go back to the terminal window and type in the following command.

现在您可以启动应用程序。 返回终端窗口并输入以下命令。

node.

You should see the message Server running at http://localhost:8080. Open your browser and navigate to http://localhost:8080. Your browser should display something like the following.

您应该看到消息Server running at http://localhost:8080 。 打开浏览器并导航到http://localhost:8080 。 您的浏览器应显示类似以下内容。

Success! You are on your way to hapi-ness!

哈皮项目结构 ( Hapi Project Structure )

As a Node.js project grows beyond a simple "hello world" example, it's essential to set up a good project structure. There are countless opinions on how you might organize a project, but a good starting point for a web application might look something like the following.

随着Node.js项目的发展超越了一个简单的“ hello world”示例,建立一个良好的项目结构至关重要。 关于如何组织项目的观点不计其数,但是Web应用程序的一个好的起点可能类似于以下内容。

├── package.json
├── client
│   ├── index.html
│   ├── css
│   └── js
├── src
│   ├── app.js
│   ├── index.js
│   ├── plugins
│   │   └── index.js
│   ├── routes
│   │   └── index.js
│   └── views
└── test
    └── index.js

Hapi can serve static files, such as HTML, CSS, and front-end JavaScript, using the inert plugin (more on plugins later). The client folder is where you might store these static assets. Of course, the contents and structure inside the client folder may differ depending on your front-end framework of choice.

Hapi可以使用inert插件(稍后将在更多插件中介绍)提供静态文件,例如HTML,CSS和前端JavaScript。 您可以在client文件夹中存储这些静态资产。 当然, client文件夹中的内容和结构可能会有所不同,具体取决于您选择的前端框架。

Under the src folder, you might organize your server-side code into the following files and folders:

src文件夹下,您可以将服务器端代码组织到以下文件和文件夹中:

  • app.js to configure the hapi server,

    app.js来配置hapi服务器,
  • index.js to start the server,

    index.js启动服务器,
  • plugins for registering external and custom hapi plugins,

    plugins用于注册外部和定制HAPI插件,
  • routes for defining the resources, or URIs, of your application,

    用于定义应用程序资源或URI的 routes
  • and views for any back-end dynamically-rendered content.

    以及任何后端动态呈现内容的views

Hapi can render server-side content using the vision plugin combined with a template engine such as ejs, handlebars, or pug. It is up to you whether you want your application to serve static content using inert, server-rendered content using vision, or a mixture of both.

哈皮可以使使用服务器侧内容视力插件与模板发动机组合如EJS车把 ,或哈巴狗 。 由您决定是否要让应用程序使用inert内容提供静态内容,使用vision服务器渲染内容或同时使用两者。

Note: If you are building an application that only acts as a service or only exposes an API, you may not have a need for the inert and vision plugins, or a need to have folders for client and views.

注意:如果要构建仅充当服务或仅公开API的应用程序,则可能不需要惰性和视觉插件,也不需要具有用于客户端和视图的文件夹。

重构Hapi项目 (Refactor the Hapi Project)

Before continuing further, refactor your project with the following steps.

在继续之前,请按照以下步骤重构项目。

Create a folder under src named plugins. Create a new file under src/plugins named index.js. Add the following code to this file.

src下创建一个名为plugins的文件夹。 在src/plugins下创建一个名为index.js的新文件。 将以下代码添加到该文件。

"use strict";

module.exports.register = async server => {
    // more to come later
};

Create a new folder under src named routes. Create a new file under src/routes named index.js. Add the following code to this file.

src下创建名为routes的新文件夹。 在src/routes下创建一个名为index.js的新文件。 将以下代码添加到该文件。

"use strict";

module.exports.register = async server => {
    server.route( {
        method: "GET",
        path: "/",
        handler: () => {
            // a handler can return text, HTML, JSON,
            // a file, or just about anything
            return "My first hapi server!";
        }
    } );
};

Create a new file under src named app.js. Add the following code to this file.

src下创建一个名为app.js的新文件。 将以下代码添加到该文件。

"use strict";

const Hapi = require( "hapi" );
const plugins = require( "./plugins" );
const routes = require( "./routes" );

module.exports.createServer = async config => {
    const server = Hapi.server( config );
    // register plugins
    await plugins.register( server );

    // register routes
    await routes.register( server );

    return server;
};

Last, modify src/index.js to match the following code.

最后,修改src/index.js以匹配以下代码。

"use strict";

const app = require( "./app" );

const port = 8080;
const config = { port };

const start = async () => {
    try {
        // create the server
        const server = await app.createServer( config );

        // start the server
        await server.start();

        console.log( `Server running at  http://localhost:${ port }` );
    } catch ( err ) {
        console.log( err );
        process.exit( 1 );
    }
}

start();

使用插件和库扩展Hapi ( Extend Hapi with Plugins and Libraries )

By design, the core hapi service focuses on basic server functionality. Plugins add additional features and capabilities to hapi. Your application may use a mix of official plugins, third-party plugins, and custom plugins you write. Here are just a sample of the more commonly used plugins.

通过设计,核心hapi服务专注于基本服务器功能。 插件为hapi添加了其他功能。 您的应用程序可能混合使用官方插件,第三方插件和您编写的自定义插件。 这只是更常用插件的一个示例。

PluginDescription
inertUse to serve static files and directories.
visionRender templates.
blippDisplays all the defined routes on startup.
hapi-pinoFast application logger that logs information in JSON format.
bellThird-party authentication.
插入 描述
惰性的 用于提供静态文件和目录。
视力 渲染模板。
Blipp 在启动时显示所有定义的路由。
匹皮诺 快速的应用程序记录器,以JSON格式记录信息。
第三方认证。

Here are a few useful libraries commonly found in hapi projects.

以下是在hapi项目中常见的一些有用的库。

LibraryDescription
joiJSON object schema validation.
boomUse to generate and return HTTP error messages.
bounceSelectively catch and rethrow errors.
wreckCollection of HTTP client utilities.
labTesting framework with code coverage analysis.
codeTest assertion library to use with lab.
图书馆 描述
i JSON对象架构验证。
繁荣 用于生成和返回HTTP错误消息。
弹跳 有选择地捕获并重新抛出错误。
破坏 HTTP客户端实用程序的集合。
实验室 具有代码覆盖率分析的测试框架。
测试断言库以与lab一起使用。

安装并注册hapi插件 (Install and Register hapi Plugins)

In this next step, install two hapi plugins and configure them. From the command line, install blipp and hapi-pino.

在下一步中,安装两个hapi插件并进行配置。 在命令行中,安装blipphapi-pino

npm install blipp hapi-pino

Next, modify src/plugins/index.js and replace the contents of this file with the following code.

接下来,修改src/plugins/index.js并将该文件的内容替换为以下代码。

"use strict";

const blipp = require( "blipp" );
const pino = require( "hapi-pino" );

const isDev = process.env.NODE_ENV !== "production";

module.exports.register = async server => {
    await server.register( [ blipp, {
        plugin: pino,
        options: {
            prettyPrint: isDev,
            logEvents: [ "response", "onPostStart" ]
        }
    } ] );
};

server.register() can take a single plugin or an array of plugins. A plugin can be registered using an instance of the plugin itself, such as the case with registering blipp. A plugin can also be registered using the plugin configuration object syntax, demonstrated by registering hapi-pino with its configuration options.

server.register()可以使用单个插件或插件数组。 可以使用插件本身的实例来注册插件,例如注册blipp的情况。 也可以使用插件配置对象语法来注册插件,这通过使用其配置选项注册hapi-pino演示。

创建一个自定义的hapi插件 (Create a Custom hapi Plugin)

You can create your custom plugins for hapi to do all sorts of things, such as modify server configuration, add routes, or listen for server events. In this step, create a plugin that listens for when a server starts and logs a message.

您可以为hapi创建自定义插件,以执行各种操作,例如修改服务器配置,添加路由或监听服务器事件 。 在此步骤中,创建一个侦听服务器启动并记录消息的插件。

Create a new file under src/plugins named serverStart.js. In this file, add the following code.

src/plugins下创建一个名为serverStart.js的新文件。 在此文件中,添加以下代码。

"use strict";

module.exports = {
    name: "serverStart",
    version: "1.0.0",
    register: async ( server, { message } ) => {
        server.events.on( "start", () => {
            const msg = message || `Server running at ${ server.info.uri }`;
            server.log( [ "info", "server" ], msg );
        } );
    }
};

A hapi plugin is a JavaScript object with a name property, a version property, and a register function with two arguments: server and options. Your serverStart plugin has an asynchronous register function that takes a server argument and uses object destructuring to take a message passed in the options. If the code does not specify a message, the plugin generates a default message.

hapi插件是一个JavaScript对象,具有name属性, version属性和具有两个参数的register函数: serveroptions 。 您的serverStart插件具有异步register函数,该函数带有server参数,并使用对象 serverStart来获取在选项中传递的message 。 如果代码未指定消息,则插件会生成默认消息。

To use this new plugin, modify src/plugins/index.js with the following code.

要使用此新插件,请使用以下代码修改src/plugins/index.js

"use strict";

const Blipp = require( "blipp" );
const HapiPino = require( "hapi-pino" );
const serverStart = require( "./serverStart" );

const isDev = process.env.NODE_ENV !== "production";

module.exports.register = async server => {
    await server.register( [ Blipp, {
        plugin: HapiPino,
        options: {
            prettyPrint: isDev,
            logEvents: [ "response" ]
        }
    }, {
        plugin: serverStart,
        options: {
            message: `My hapi server is running at ${ server.info.uri }`
        }
    } ] );
};

Now run your application using:

现在使用以下命令运行您的应用程序:

node.

Your console output should look similar to the following.

您的控制台输出应类似于以下内容。

method  path                          description
------  ----------------------------  -----------
GET     /[1544478627595] INFO  (7408 on mycomputer):
    tags: [
      "info",
      "server"
    ]
    data: "My hapi server is running at http://mycomputer:8080"

添加服务器渲染的模板以获得更好的UI ( Add Server Rendered Templates for a Better UI )

So far the application has only returned plain text. A hapi application can respond to requests with text, static files, content dynamically generated from templates, or other types of media, such as JSON. In this step, add support for Embedded JavaScript templates, or EJS.

到目前为止,该应用程序仅返回纯文本。 hapi应用程序可以使用文本,静态文件,从模板动态生成的内容或其他类型的媒体(例如JSON)来响应请求。 在此步骤中,添加对嵌入式JavaScript模板或EJS的支持。

First, install the required dependencies using npm.

首先,使用npm安装所需的依赖项。

npm install vision ejs

Create a new file in the src/views folder named layout.ejs. Add the following HTML to this file.

src/views文件夹中创建一个名为layout.ejs的新文件。 将以下HTML添加到此文件。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title><%= title %></title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body>
    <%- content %>
</body>
</html>

Create a new file in the src/views folder named index.ejs. Add the following HTML to this file.

src/views文件夹中创建一个名为index.ejs的新文件。 将以下HTML添加到此文件。

<div class="container">
    <h1 class="header"><%= title %></h1>
    <p><%= message %></p>
</div>

Next, update src/app.js to configure the vision plugin and ejs template engine.

接下来,更新src/app.js以配置vision插件和ejs模板引擎。

"use strict";

const Hapi = require( "hapi" );
const vision = require( "vision" );
const ejs = require( "ejs" );
const plugins = require( "./plugins" );
const routes = require( "./routes" );

module.exports.createServer = async config => {
    const server = Hapi.server( config );

    // add the vision plugin and 
    // register EJS template view support
    await server.register( vision );
    server.views( {
        engines: { ejs },
        relativeTo: __dirname,
        path: "views",
        layout: true
    } );

    // register plugins
    await plugins.register( server );

    // register routes
    await routes.register( server );

    return server;
};

Now, update src/routes/index.js to return the rendered view instead of text.

现在,更新src/routes/index.js以返回渲染的视图而不是文本。

"use strict";

module.exports.register = async server => {
    server.route( {
        method: "GET",
        path: "/",
        handler: async ( request, h ) => {
            try {
                const message = "My first hapi server!";
                return h.view( "index", {
                    title: "Home",
                    message
                } );
            } catch ( err ) {
                server.log( [ "error", "home" ], err );
            }
        }
    } );
};

Note: In the previous handler function, the request contains information about the incoming request, and h is the response toolkit that includes properties and utilities for creating responses.

注意:在上一个处理程序函数中, 请求包含有关传入请求的信息,h是响应工具包 ,其中包括用于创建响应的属性和实用程序。

使用Bell和Okta保护您的hapi服务器 ( Secure Your hapi Server with Bell and Okta )

Unless you are building a static website, chances are your application needs a way to allow visitors to register for an account, log in, reset their password, and so forth. Add to the mix the ever-changing landscape of security concerns, and you can easily see implementing and maintaining a sound security strategy is far from a trivial task.

除非您建立一个静态网站,否则您的应用程序很可能需要一种允许访问者注册帐户,登录,重设密码等的方法。 加上不断变化的安全问题,您可以轻松地看到实施和维护合理的安全策略绝非易事。

The good news is Okta's developer platform, built on the latest open security standards, makes this step very easy.

好消息是Okta的开发人员平台建立在最新的开放安全性标准之上,使这一步骤非常容易。

To begin, create a free developer account with Okta at developer.okta.com. Click the Create Free Account button, or click the Sign Up button.

首先,请在developer.okta.com上使用Okta创建一个免费的开发人员帐户。 单击创建免费帐户按钮,或单击注册按钮。

After creating your account, click the Applications link at the top, and then click Add Application.

创建帐户后,单击顶部的“ 应用程序”链接,然后单击“ 添加应用程序”

Next, choose a Web Application and click Next.

接下来,选择一个Web应用程序 ,然后单击Next

Enter a name for your application, such as My Hapi Server. Verify the port number is the same as configured for your local web application. Then, click Done to finish creating the application.

输入您的应用程序的名称,例如My Hapi Server 。 验证端口号与为本地Web应用程序配置的端口号相同。 然后,单击“完成”以完成应用程序的创建。

启用自助服务注册 (Enable self-service registration)

One of the great features of Okta is allowing users of your application to sign up for an account. By default, this feature is disabled, but you can easily enable it. First, click on the Users menu and select Registration.

Okta的一大功能是允许您的应用程序用户注册一个帐户。 默认情况下,此功能是禁用的,但是您可以轻松地启用它。 首先,单击“ 用户”菜单,然后选择“ 注册”

  1. Click on the Edit button.

    单击编辑按钮。
  2. Change Self-service registration to Enabled.

    自助 服务注册更改为“ 启用”
  3. Click the Save button at the bottom of the form.

    单击表单底部的“ 保存”按钮。

使用DotEnv管理配置 (Manage Configuration with DotEnv)

Node.js applications typically use environment variables for configuration. However, managing environment variables can be a chore. A popular module for managing application configuration data is dotenv.

Node.js应用程序通常使用环境变量进行配置。 但是,管理环境变量可能很麻烦。 dotenv是用于管理应用程序配置数据的流行模块。

Install dotenv as a project dependency.

安装dotenv作为项目依赖项。

npm install dotenv

Create a file named .env in the root folder of the project, and add the following configuration.

在项目的根文件夹中创建一个名为.env的文件,并添加以下配置。

Note: When using a source control system such as git, do not add the .env file to source control. Each environment requires a custom .env file. It is recommended you document the values expected in the .env file in the project README or a separate .env.sample file.

注意:使用git等源代码管理系统时,请勿将.env文件添加到源代码管理中。 每个环境都需要一个自定义的.env文件。 建议您在项目README的.env文件或单独的.env.sample文件中记录所需的值。

# Server configuration
NODE_ENV=production
PORT=8080
HOST_URL=http://localhost:8080
COOKIE_ENCRYPT_PWD=superAwesomePasswordStringThatIsAtLeast32CharactersLong!

# Okta configuration
OKTA_ORG_URL=https://{yourOktaDomain}
OKTA_CLIENT_ID={yourClientId}
OKTA_CLIENT_SECRET={yourClientSecret}

Now, update src/index.js to use the dotenv module.

现在,更新src/index.js以使用dotenv模块。

"use strict";

// Load in environment configuration
require( "dotenv" ).config();
const app = require( "./app" );

const DEFAULT_PORT = 8080;
const port = process.env.PORT || DEFAULT_PORT;
const config = { port };

const start = async () => {
    try {
        // create the server
        const server = await app.createServer( config );

        // start the server
        await server.start();
    } catch ( err ) {
        console.log( err );
        process.exit( 1 );
    }
};

start();

Go to your Okta account and click on the Dashboard link. On the right side of the page, you should find your Org URL. Copy and paste this value into your .env file to replace the value for OKTA_ORG_URL.

转到您的Okta帐户,然后单击“仪表板”链接。 在页面右侧,您应该找到您的组织网址 。 将此值复制并粘贴到您的.env文件中,以替换OKTA_ORG_URL的值。

Click on the Applications link, and then click on the name of your new application. Click on the General tab, and find near the bottom of the page a section titled Client Credentials. Copy the Client ID and Client secret values and paste them into your .env file to replace {yourClientId} and {yourClientSecret}, respectively.

单击“ 应用程序”链接,然后单击新应用程序的名称。 点击常规标签,然后在页面底部附近找到标题为客户端凭据的部分。 复制客户ID客户机密值,然后将其粘贴到您的.env文件中,分别替换{yourClientId}{yourClientSecret}

创建一个安全的Web应用程序 (Create A Secure Web Application)

First, install the bell and hapi-auth-cookie plugins. Bell is an authentication plugin, and hapi-auth-cookie is for cookie-based session management.

首先,安装bellhapi-auth-cookie插件。 Bell是身份验证插件,而hapi-auth-cookie用于基于cookie的会话管理。

npm install bell hapi-auth-cookie

Under src/plugins create a new file named auth.js and add the following code.

src/plugins创建一个名为auth.js的新文件,并添加以下代码。

"use strict";

const bell = require( "bell" );
const authCookie = require( "hapi-auth-cookie" );

const isSecure = process.env.NODE_ENV === "production";

module.exports.register = async server => {
    // register plugins
    await server.register( [ authCookie, bell ] );

    // configure cookie authorization strategy
    server.auth.strategy( "session", "cookie", {
        password: process.env.COOKIE_ENCRYPT_PWD,
        redirectTo: "/authorization-code/callback", // If there is no session, redirect here
        isSecure // Should be set to true (which is the default) in production
    } );

    // configure bell to use your Okta authorization server
    server.auth.strategy( "okta", "bell", {
        provider: "okta",
        config: { uri: process.env.OKTA_ORG_URL },
        password: process.env.COOKIE_ENCRYPT_PWD,
        isSecure,
        location: process.env.HOST_URL,
        clientId: process.env.OKTA_CLIENT_ID,
        clientSecret: process.env.OKTA_CLIENT_SECRET
    } );
};

Next, update src/plugins/index.js to register the new module.

接下来,更新src/plugins/index.js来注册新模块。

"use strict";

const blipp = require( "blipp" );
const pino = require( "hapi-pino" );
const serverStart = require( "./serverStart" );
const auth = require( "./auth" );

const isDev = process.env.NODE_ENV !== "production";

module.exports.register = async server => {
    await server.register( [ blipp, {
        plugin: pino,
        options: {
            prettyPrint: isDev,
            logEvents: [ "response" ]
        }
    }, {
        plugin: serverStart,
        options: {
            message: `My hapi server is running at ${ server.info.uri }`
        }
    } ] );
    await auth.register( server );
};

Now, modify src/routes/index.js to the following code.

现在,将src/routes/index.js修改为以下代码。

"use strict";

const boom = require( "boom" );

module.exports.register = async server => {
    server.route( {
        method: "GET",
        path: "/",
        config: {
            auth: {
                strategy: "session",
                mode: "optional"
            }
        },
        handler: async ( request, h ) => {
            try {
                const message = request.auth.isAuthenticated ? `Hello, ${ request.auth.credentials.profile.firstName }!` : "My first hapi server!";
                return h.view( "index", {
                    title: "Home",
                    message,
                    isAuthenticated: request.auth.isAuthenticated
                } );
            } catch ( err ) {
                server.log( [ "error", "home" ], err );
            }
        }
    } );

    server.route( {
        method: "GET",
        path: "/login",
        options: {
            auth: "session",
            handler: async request => {
                return `Hello, ${ request.auth.credentials.profile.email }!`;
            }
        }
    } );

    server.route( {
        method: "GET",
        path: "/authorization-code/callback",
        options: {
            auth: "okta",
            handler: ( request, h ) => {
                if ( !request.auth.isAuthenticated ) {
                    throw boom.unauthorized( `Authentication failed: ${ request.auth.error.message }` );
                }
                request.cookieAuth.set( request.auth.credentials );
                return h.redirect( "/" );
            }
        }
    } );

    server.route( {
        method: "GET",
        path: "/logout",
        options: {
            auth: {
                strategy: "session",
                mode: "try"
            },
            handler: ( request, h ) => {
                try {
                    if ( request.auth.isAuthenticated ) {
                        // clear the local session
                        request.cookieAuth.clear();
                    }

                    return h.redirect( "/" );
                } catch ( err ) {
                    request.log( [ "error", "logout" ], err );
                }
            }
        }
    } );
};

Create a new folder under src/views named partials. Create a new file in the partials folder named navigation.ejs. Add the following HTML to this file.

src/views下创建一个名为partials的新文件夹。 在partials文件夹中创建一个名为navigation.ejs的新文件。 将以下HTML添加到此文件。

<nav>
    <div class="nav-wrapper">
    <% if ( isAuthenticated ) { %>
    <a href="/logout">Logout</a>
    <% } else { %> 
    <a href="/login">Login</a>
    <% } %>
</nav>

Update src/views/layout.ejs to include the navigation.ejs file when it renders.

更新src/views/layout.ejs以使其在呈现时包含navigation.ejs文件。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title><%= title %></title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
</head>
<body>
    <% include partials/navigation %>
    <%- content %>
</body>
</html>

Now, you are ready to start your application again.

现在,您可以重新启动应用程序了。

node.

In your browser, navigate to http://localhost:8080/. Click on the Login button at the top. You should see a prompt to log in to your Okta account.

在浏览器中,导航到http://localhost:8080/ 。 单击顶部的“ 登录”按钮。 您应该看到提示登录到您的Okta帐户。

Note: To verify authentication is working as expected, you may need to open a new browser or use a private/incognito browser window.

注意:要验证身份验证是否按预期工作,您可能需要打开新的浏览器或使用专用/隐身浏览器窗口。

After logging in, you should be redirected back to the home page and see something like the following.

登录后,应将您重定向回主页,并看到类似以下的内容。

After authentication, the following profile information is available on every request as part of request.auth.

认证后,以下配置文件信息可作为request.auth一部分用于每个请求。

// request.auth (Example)
{
    "isAuthenticated": true,
    "credentials": {
        "provider": "okta",
        "token": "...",
        "expiresIn": 3600,
        "profile": {
            "id": "0012345",
            "username": "john.henry@company.com",
            "firstName": "John",
            "lastName": "Henry",
            "email": "john.henry@company.com",
            "raw": {
                "sub": "0012345",
                "name": "John Henry",
                "locale": "en-US",
                "email": "john.henry@company.com",
                "preferred_username": "john.henry@company.com",
                "given_name": "John",
                "family_name": "Henry",
                "zoneinfo": "America/Los_Angeles",
                "updated_at": 1544212558,
                "email_verified": true
            }
        }
    }
}

将测试添加到您的Hapi项目 ( Add Tests to Your Hapi Project )

A hapi project would not be complete without tests. Lab and code are the preferred test libraries for hapi projects. TestDouble is also a handy utility for replacing, mocking dependencies, and verifying behavior.

没有测试,hapi项目将无法完成。 实验室代码是hapi项目的首选测试库。 TestDouble还是一个方便的实用程序,用于替换, 模拟依赖关系和验证行为。

From the command line, install the following developer dependencies required for testing.

在命令行中,安装测试所需的以下开发人员依赖项。

npm install --save-dev code lab testdouble

Create a new folder in the root of the project named test. Add a file to this folder named app.js. In this file, add the following code.

在名为test的项目的根目录中创建一个新文件夹。 将一个文件添加到名为app.js的文件夹中。 在此文件中,添加以下代码。

"use strict";

const td = require( "testdouble" );
td.replace( "hapi-pino" );
require( "dotenv" ).config();
const { expect } = require( "code" );
const Lab = require( "lab" );

const app = require( "../src/app" );

const lab = exports.lab = Lab.script();
const { describe, it } = lab;

describe( "App", () => {
    it( "home page returns valid response", async () => {
        const server = await app.createServer( { port: 12345 } );
        await server.initialize();
        const res = await server.inject( {
            url: "/",
            method: "GET"
        } );
        expect( res.statusCode ).to.equal( 200 );
        expect( res.result ).to.exist();
        expect( res.result ).to.contain( "My first hapi server!" );
    } );
} );

Modify your package.json file, and change the test scripts property to the following.

修改您的package.json文件,并将test脚本属性更改为以下内容。

"test": "lab -c"

Now, run the test from the command line using the following command.

现在,使用以下命令从命令行运行测试。

npm run test

The output from this first test should look similar to the following.

第一个测试的输出应类似于以下内容。

.

1 tests complete
Test duration: 67 ms
No global variable leaks detected
Coverage: 88.73% (16/142)
src/plugins/serverStart.js missing coverage on line(s): 8, 9
src/routes/index.js missing coverage on line(s): 17, 24, 35, 46-50, 64, 65, 69, 75, 77, 78

One of the great features of lab is the ability to analyze your tests for code coverage. The code analysis report includes lines of your source code currently missing test coverage.

lab一大特色是能够分析测试的代码覆盖率。 代码分析报告包含当前缺少测试覆盖范围的源代码行。

去得到哈皮! ( Go Get Hapi! )

In this tutorial, you have learned the basics of creating a modern web application with hapi and some of the tools that are part of the hapi ecosystem. Below are more resources to explore.

在本教程中,您学习了使用hapi和hapi生态系统一部分的一些工具创建现代Web应用程序的基础。 以下是更多探索资源。

You can find the complete project source code on GitHub.

您可以在GitHub上找到完整的项目源代码

Thanks for reading, and happy... er... hapi coding!

感谢您的阅读,并高兴...呃... hapi编码!

翻译自: https://scotch.io/tutorials/build-a-secure-nodejs-application-with-javascript-async-await-using-hapi

node.js hapi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值