├── .eslintrc.js ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── master.yml ├── .gitignore ├── .workshopper-test.config.js ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── makemehapi ├── credits.js ├── credits.txt ├── exercises ├── auth │ ├── exercise.js │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── cookies │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── directories │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ ├── public │ │ │ └── file.html │ │ └── solution.js │ └── solution_fr │ │ ├── public │ │ └── file.html │ │ └── solution.js ├── extend │ ├── exercise.js │ ├── problem.md │ └── solution │ │ └── solution.js ├── handling │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ ├── index.html │ │ └── solution.js │ └── solution_fr │ │ ├── index.html │ │ └── solution.js ├── hello_hapi │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── solution_fr │ │ └── solution.js ├── helping │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ ├── helpers │ │ │ └── helper.js │ │ ├── solution.js │ │ └── templates │ │ │ └── template.html │ └── solution_fr │ │ ├── helpers │ │ └── helper.js │ │ ├── solution.js │ │ └── templates │ │ └── template.html ├── menu.json ├── proxies │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ └── solution.js ├── routes │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ ├── solution_fr │ │ └── solution.js │ ├── solution_ja │ │ └── solution.js │ └── solution_ko │ │ └── solution.js ├── static_serving │ ├── exercise.js │ ├── problem.md │ └── solution │ │ └── solution.js ├── streams │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ └── solution │ │ ├── input.txt │ │ └── solution.js ├── uploads │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ ├── input.txt │ │ └── solution.js │ └── solution_fr │ │ ├── input.txt │ │ └── solution.js ├── validation │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── solution_fr │ │ └── solution.js ├── validation_using_joi_object │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ │ └── solution.js │ └── solution_fr │ │ └── solution.js └── views │ ├── exercise.js │ ├── problem.fr.md │ ├── problem.ja.md │ ├── problem.ko.md │ ├── problem.md │ ├── solution │ ├── solution.js │ └── templates │ │ └── index.html │ └── solution_fr │ ├── solution.js │ └── templates │ └── index.html ├── i18n ├── br.json ├── credits │ ├── br.txt │ ├── en.txt │ ├── fr.txt │ ├── ja.txt │ └── ko.txt ├── en.json ├── fr.json ├── ja.json └── ko.json ├── images └── makemehapi.png ├── index.js ├── lib ├── exercise.js ├── utils.js └── verifyStatusProcessor.js └── package.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true 6 | }, 7 | extends: [ 8 | 'standard', 9 | 'plugin:json/recommended' 10 | ], 11 | globals: { 12 | Atomics: 'readonly', 13 | SharedArrayBuffer: 'readonly' 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018 17 | }, 18 | rules: { 19 | indent: ['error', 2], 20 | semi: 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.js text eol=lf 7 | 8 | # Denote all files that are truly binary and should not be modified. 9 | *.png binary 10 | *.jpg binary 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: master 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [19.x, 18.x, 16.x] 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Node ${{ matrix.node_version }} 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node_version }} 22 | - run: npm install 23 | - run: npm run lint 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | npm-debug.log 4 | dump.rdb 5 | node_modules 6 | results.tap 7 | results.xml 8 | npm-shrinkwrap.json 9 | .DS_Store 10 | */.DS_Store 11 | */*/.DS_Store 12 | ._* 13 | */._* 14 | */*/._* 15 | package-lock.json 16 | -------------------------------------------------------------------------------- /.workshopper-test.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exercisesFolder: 'exercises', 3 | files: 'solution/*.*', 4 | testFileRegex: 'solution.js' 5 | } 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ben Acker 2 | Lin Clark (http://lin-clark.com) 3 | Chetan Dhembre 4 | Van Nguyen 5 | Wyatt Preul (http://jsgeek.com) 6 | Christophe Porteneuve (http://tddsworld.com) 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [7.1.4](https://github.com/ccarruitero/makemehapi/compare/v7.1.3...v7.1.4) (2020-11-23) 6 | 7 | ### [7.1.3](https://github.com/ccarruitero/makemehapi/compare/v7.1.2...v7.1.3) (2020-05-20) 8 | 9 | 10 | ### Bug Fixes 11 | 12 | * **handling:** add info about setup files base path in server ([f8bae05](https://github.com/ccarruitero/makemehapi/commit/f8bae05cb69c11fb255ffb07a848fb3890e934c4)) 13 | * cookies exercise solution ([1b2ec73](https://github.com/ccarruitero/makemehapi/commit/1b2ec73104a9f354dbba59b92acb71350b5d478c)) 14 | * kill child process when hyperquest fail fix [#186](https://github.com/ccarruitero/makemehapi/issues/186) ([a014b3a](https://github.com/ccarruitero/makemehapi/commit/a014b3aa877e4e3f711bcf52e3d57c4868c0d0ea)) 15 | * linter ([170fdc1](https://github.com/ccarruitero/makemehapi/commit/170fdc1e76bae8ee738962d8cbc604a155f99855)) 16 | 17 | ### [7.1.2](https://github.com/ccarruitero/makemehapi/compare/v7.1.1...v7.1.2) (2020-01-13) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * license in package.json ([c078e8f](https://github.com/ccarruitero/makemehapi/commit/c078e8ff454ac8faccaf98e58c5db4644e354687)) 23 | 24 | ### [7.1.1](https://github.com/ccarruitero/makemehapi/compare/v7.1.0...v7.1.1) (2019-11-06) 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | We welcome contributions from the community and are pleased to have them. Please follow this guide when logging issues or making code changes. 3 | 4 | ## Logging Issues 5 | All issues should be created using the [new issue form](https://github.com/hapijs/makemehapi/issues/new). Clearly describe the issue including steps 6 | to reproduce if there are any. Also, make sure to indicate the earliest version that has the issue being reported. 7 | 8 | ## Patching Code 9 | 10 | Code changes are welcome and should follow the guidelines below. 11 | 12 | * Fork the repository on GitHub. 13 | * Fix the issue ensuring that your code follows the [style guide](https://github.com/hapijs/contrib/blob/master/Style.md). 14 | * [Pull requests](http://help.github.com/send-pull-requests/) should be made to the [master branch](https://github.com/hapijs/makemehapi/tree/master). 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014, Walmart and other contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * The names of any contributors may not be used to endorse or promote 12 | products derived from this software without specific prior written 13 | permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | * * * 27 | 28 | The complete list of contributors can be found at: https://github.com/hapijs/makemehapi/graphs/contributors 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Make Me Hapi 2 | 3 | [![NPM](https://nodei.co/npm/makemehapi.png?downloads=true&stars=true)](https://nodei.co/npm/makemehapi/) 4 | 5 | Learn all about [hapi](http://hapijs.com) through a series of self-guided 6 | challenges. 7 | 8 | ![makemehapi](https://raw.github.com/hapijs/makemehapi/master/images/makemehapi.png) 9 | 10 | ## Getting Started 11 | 12 | 1. Install **makemehapi** by running `npm install -g makemehapi` 13 | 2. Run `makemehapi` 14 | 15 | **makemehapi** will run you through a series of challenges ranging from a basic 16 | "hello world" server then move on to more advanced exercises dealing with 17 | rendering views, handling uploads, and managing cookies. 18 | 19 | ## Getting Help 20 | 21 | Run into a problem or want to discuss the exercises? 22 | [Open an issue](https://github.com/hapijs/makemehapi/issues) on this 23 | repository! 24 | -------------------------------------------------------------------------------- /bin/makemehapi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../index').execute(process.argv.slice(2)) 4 | -------------------------------------------------------------------------------- /credits.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const colorsTmpl = require('colors-tmpl'); 4 | const combinedStream = require('combined-stream'); 5 | 6 | function read (file) { 7 | return fs.createReadStream(path.join(__dirname, file), { encoding: 'utf8' }); 8 | } 9 | 10 | function credits (workshopper) { 11 | combinedStream 12 | .create() 13 | .append(read(`./i18n/credits/${workshopper.lang}.txt`)) 14 | .append(read('./credits.txt')) 15 | .on('error', (err) => { 16 | console.log(err); 17 | throw err; 18 | }) 19 | .on('data', (data) => { 20 | console.log(colorsTmpl(data)); 21 | }) 22 | .resume(); 23 | } 24 | 25 | module.exports = credits; 26 | -------------------------------------------------------------------------------- /credits.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------ 2 | Ben Acker @nvcexploder 3 | Lloyd Benson @lloydbenson 4 | Lin Clark @linclark 5 | Chetan Dhembre @chetandhembre 6 | Van Nguyen @thegoleffect 7 | Wyatt Lyon Preul @geek 8 | Christophe Porteneuve @tdd 9 | -------------------------------------------------------------------------------- /exercises/auth/exercise.js: -------------------------------------------------------------------------------- 1 | const hyperquest = require('hyperquest'); 2 | const exercise = require('../../lib/exercise'); 3 | 4 | exercise.queryUrl = (port) => `http://hapi:auth@localhost:${port}/`; 5 | 6 | exercise.handleInput = (request, stream, port) => { 7 | const failUrl = `http://hapi:fail@localhost:${port}/`; 8 | 9 | request.on('response', (res) => { 10 | stream.write(`${res.statusCode}\n`); 11 | 12 | hyperquest.get(failUrl).pipe(stream); 13 | }) 14 | }; 15 | 16 | module.exports = exercise; 17 | -------------------------------------------------------------------------------- /exercises/auth/problem.ja.md: -------------------------------------------------------------------------------- 1 | Basic認証はあなたのアプリケーションを保護するシンプルな方法で、ユーザー名と 2 | パスワードのみを使用します。クッキーやセッションは必要とせず、標準の 3 | HTTPヘッダーのみを使用します。 4 | 5 | コマンドラインから渡されるポート番号をlistenする、Basic認証で保護された 6 | サーバーを作成します。認証のユーザー名は"hapi"、パスワードは"auth"です。 7 | 認証に失敗した場合はHTTPステータスコードの401を返して下さい。 8 | 9 | -------------------- 10 | 11 | ## ヒント 12 | 13 | Basic認証を扱うhapiのプラグインがあります。以下のコマンドでインストール 14 | して下さい。 15 | 16 | ```sh 17 | npm install @hapi/basic 18 | ``` 19 | 20 | '`@hapi/basic`'プラグインを登録し、`basic`認証ストラテジを名前を 21 | 付けて設定して下さい。設定が完了すれば、その認証ストラテジの名前に対し、 22 | '`auth`'プロパティをルートコンフィギュレーション中で設定して下さい。 23 | 24 | ```js 25 | server.auth.strategy('simple', 'basic', { validateFunc: validate }); 26 | server.auth.default('simple'); 27 | 28 | server.route({ 29 | method: 'GET', 30 | path: '/', 31 | handler: function (request, h) { 32 | 33 | return 'welcome'; 34 | } 35 | }); 36 | ``` 37 | 38 | 39 | Hapi-auth-basicの情報は以下にあります。 40 | 41 | {rootdir:/node_modules/hapi-auth-basic/README.md} 42 | 43 | -------------------------------------------------------------------------------- /exercises/auth/problem.ko.md: -------------------------------------------------------------------------------- 1 | Basic 인증은 유저명과 비밀번호만으로 앱으로의 접근을 보호할 수 있는 간단한 인증 방법입니다. 쿠키와 세션은 필요 없으며 표준 HTTP 헤더만 있으면 됩니다. 2 | 3 | hapi 서버를 만들어 봅시다. 커맨드 라인으로 포트 번호를 받아 설정하고, Basic 인증을 사용해 봅시다. 인증에 사용되는 유저명은 "hapi"로, 비밀번호는 "auth"로 설정해주세요. 인증에 실패한 경우는 서버가 HTTP 401 상태 코드를 반환하도록 해주세요. 4 | 5 | -------------------- 6 | 7 | ## 힌트 8 | 9 | 다음 명령어를 실행해서, Basic 인증을 다루는 플러그인을 설치해주세요. 10 | 11 | ```sh 12 | npm install @hapi/basic 13 | ``` 14 | 15 | 설치한 `@hapi/basic` 플러그인을 등록하고, 다음과 같이 임의의 이름(simple)을 지정한 Basic 인증을 서버의 strategy로 설정해주세요. 인증 설정이 끝났다면, 서버의 라우트 설정의 `auth` 프로퍼티에 strategy를 설정할 때 사용한 이름을 지정해야 합니다. 16 | 17 | ```js 18 | server.auth.strategy('simple', 'basic', { validateFunc: validate }); 19 | server.auth.default('simple'); 20 | 21 | server.route({ 22 | method: 'GET', 23 | path: '/', 24 | handler: function (request, h) { 25 | 26 | return 'welcome'; 27 | } 28 | }); 29 | ``` 30 | 31 | Hapi-auth-basic에 관한 자세한 정보는 아래 URL에서 확인하세요. 32 | 33 | {rootdir:/node_modules/hapi-auth-basic/README.md} 34 | 35 | -------------------------------------------------------------------------------- /exercises/auth/problem.md: -------------------------------------------------------------------------------- 1 | Basic Authentication is a simple way to protect access to your application using 2 | only a username and password. There is no need for cookies or sessions, only a 3 | standard HTTP header. 4 | 5 | Create a hapi server that listens on a port passed from the command line and is 6 | protected with Basic Authentication. The authentication username should be 7 | "hapi" and the password "auth" and the server should respond with an HTTP 401 8 | status code when authentication fails. 9 | 10 | -------------------- 11 | 12 | ##HINTS 13 | 14 | There is a hapi plugin for handling basic authentication. Install it by running: 15 | 16 | ```sh 17 | npm install @hapi/basic 18 | ``` 19 | 20 | You'll need to register the `@hapi/basic` plugin then configure a named 21 | authentication strategy for `basic`. Once authentication is configured, you'll 22 | need to set the `auth` property in the route configuration to the name of the 23 | strategy you configured. 24 | 25 | ```js 26 | server.auth.strategy('simple', 'basic', { validateFunc: validate }); 27 | server.auth.default('simple'); 28 | 29 | server.route({ 30 | method: 'GET', 31 | path: '/', 32 | handler: function (request, h) { 33 | 34 | return 'welcome'; 35 | } 36 | }); 37 | ``` 38 | 39 | Hapi-auth-basic information can be found here: 40 | 41 | {rootdir:/node_modules/@hapi/basic/README.md} 42 | 43 | -------------------------------------------------------------------------------- /exercises/auth/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const Auth = require('@hapi/basic'); 3 | 4 | const user = { name: 'hapi', password: 'auth' }; 5 | 6 | const validate = async (request, username, password, h) => { 7 | const isValid = username === user.name && password === user.password; 8 | 9 | return { isValid, credentials: { name: user.name } }; 10 | }; 11 | 12 | (async () => { 13 | try { 14 | const server = Hapi.Server({ 15 | host: 'localhost', 16 | port: Number(process.argv[2] || 8080) 17 | }); 18 | 19 | await server.register(Auth); 20 | 21 | server.auth.strategy('simple', 'basic', { validate }); 22 | server.auth.default('simple'); 23 | 24 | server.route({ 25 | method: 'GET', 26 | path: '/', 27 | handler (request, h) { 28 | return 'welcome'; 29 | } 30 | }); 31 | 32 | await server.start(); 33 | } catch (error) { 34 | console.log(error); 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /exercises/cookies/exercise.js: -------------------------------------------------------------------------------- 1 | const hyperquest = require('hyperquest'); 2 | const exercise = require('../../lib/exercise'); 3 | 4 | exercise.queryUrl = (port) => `http://localhost:${port}/set-cookie`; 5 | 6 | exercise.handleInput = (request, stream, port) => { 7 | const checkCookiesUrl = `http://localhost:${port}/check-cookie`; 8 | 9 | request.on('response', (res) => { 10 | stream.write(`${JSON.stringify(res.headers['set-cookie'])}\n`); 11 | hyperquest.get(checkCookiesUrl, { 12 | headers: { Cookie: res.headers['set-cookie'] } 13 | }).pipe(stream); 14 | }) 15 | }; 16 | 17 | module.exports = exercise; 18 | -------------------------------------------------------------------------------- /exercises/cookies/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur qui a une configuration de route exposant des points d’accès 2 | `set-cookie` et `check-cookie`, accessibles via des requêtes GET, décrits 3 | ci-après. 4 | 5 | ``` 6 | /set-cookie 7 | ``` 8 | 9 | Le point d’accès `set-cookie` définit un cookie avec une clé `'session'` et une 10 | valeur `{ key: 'makemehapi' }`. Le cookie doit être encodé au format 11 | `base64json`, expirer au bout de 10ms et être associé au domaine `localhost`. 12 | La réponse à la requête est sans importance pour cette exercice : mettez ce 13 | que vous voulez. 14 | 15 | ``` 16 | /check-cookie 17 | ``` 18 | 19 | Le point d’accès `check-cookie` doit pouvoir recevoir les cookies définis par 20 | `/set-cookie`. Si la clé `'session'` est présente, renvoyez simplement comme 21 | réponse `{ user: 'hapi' }`, sinon renvoyez une erreur d’accès HTTP 22 | `unauthorized`. 23 | 24 | -------------------- 25 | 26 | ## Conseils 27 | 28 | Dans votre appel à `server.route()`, vous utiliserez sans doute les options 29 | suivantes : 30 | 31 | ```js 32 | config: { 33 | state: { 34 | parse: true, 35 | failAction: 'log' 36 | } 37 | } 38 | ``` 39 | 40 | Grâce à celles-ci, vous pourrez configurer votre serveur pour traiter les 41 | cookies de la façon qui vous convient. 42 | 43 | Hapi permet une gestion des cookies par chemin d’URL spécifique, par exemple : 44 | 45 | ```js 46 | server.state('session', { 47 | path: '/', 48 | }); 49 | ``` 50 | 51 | Vous pouvez définir des cookies lors de la réponse, comme ceci : 52 | 53 | ```js 54 | reply('success').state('session', 'session') 55 | ``` 56 | 57 | Les valeurs de cookies sont stockées dans l’état serveur, et accessibles avec 58 | du code comme celui-ci : 59 | 60 | ```js 61 | var session = request.state.session; 62 | ``` 63 | 64 | Vous trouverez davantage d’informations sur la gestion des cookies dans la 65 | documentation de l’API de Hapi en ligne: 66 | 67 | [Documentation API](http://hapijs.com/api) 68 | -------------------------------------------------------------------------------- /exercises/cookies/problem.ja.md: -------------------------------------------------------------------------------- 1 | `set-cookie`と`check-cookie`の2つのエンドポイントを提供するサーバーを 2 | 作成します。これらのエンドポイントは`GET`リクエストによりアクセス可能で、 3 | 以下のようにアクセスします。 4 | 5 | ``` 6 | /set-cookie 7 | ``` 8 | 9 | `set-cookie`エンドポイントは'session'というキーに対し、値として 10 | `{ key: 'makemehapi' }`をクッキーに設定します。クッキーの条件は 11 | 以下の通りです。 12 | 13 | * `base64json`としてエンコードする 14 | * 有効期間は`10 ms` 15 | * スコープのドメインは`localhost` 16 | 17 | レスポンスはこのエクササイズでは特に重要ではありませんので、何でも 18 | 構いません。 19 | 20 | ``` 21 | /check-cookie 22 | ``` 23 | 24 | `check-cookie`エンドポイントは`/set-cookie`エンドポイントで受け取った 25 | クッキーを使用します。`session`キーがクッキーに存在していれば、 26 | `{ user: 'hapi' }`を返し、それ以外は`unauthorized`のアクセス 27 | エラーを返します。 28 | 29 | -------------------- 30 | 31 | ## ヒント 32 | 33 | '`server.route()`'関数では以下のようなオプションが必要となるでしょう。 34 | 35 | ```js 36 | config: { 37 | state: { 38 | parse: true, 39 | failAction: 'log' 40 | } 41 | } 42 | ``` 43 | 44 | このオプションを使用することにより、サーバーはクッキーを様々な方法で取り扱う 45 | ことが出来ます。 46 | 47 | `hapi`は特定のURLに対するクッキーを管理する機能を提供します。 48 | 49 | ```js 50 | server.state('session', { 51 | path: '/', 52 | }); 53 | ``` 54 | 55 | リクエストにリプライする際、以下のようなコードでクッキーをセットする 56 | ことが出来ます。 57 | 58 | ```js 59 | reply('success').state('session', 'session') 60 | ``` 61 | 62 | クッキーの値はサーバーのstateに保存され、以下のようなコードでアクセス 63 | することが出来ます。 64 | 65 | ```js 66 | var session = request.state.session; 67 | ``` 68 | 69 | クッキーの取り扱いに関しての詳細な情報は以下にあります。 70 | 71 | [API](http://hapijs.com/api). 72 | 73 | このエクササイズでは必要ありませんが、[Boom](https://www.npmjs.com/package/boom) 74 | を使用することにより、より簡単に`unauthorized`エラーを正しいHTTPステータスコードで 75 | 返すことが出来ます。 76 | 77 | ```js 78 | var Boom = require('boom'); 79 | ``` 80 | 81 | ```js 82 | reply(Boom.unauthorized('Missing authentication')); 83 | ``` 84 | -------------------------------------------------------------------------------- /exercises/cookies/problem.ko.md: -------------------------------------------------------------------------------- 1 | 서버를 만들어봅시다. 'GET' request로 접근 가능한 `set-cookie`와 `check-cookie` 두 개의 endpoint를 라우트 설정에 추가해주세요. 2 | 3 | ``` 4 | /set-cookie 5 | ``` 6 | 7 | `set-cookie` endpoint는 'session'이라는 키로 `{ key: 'makemehapi' }` 값을 쿠키에 저장할 것입니다. 쿠키는 `base64json`으로 암호화하고, `10 ms`가 지나면 소멸해야 하며, 도메인 범위는 `localhost`여야 합니다. response는 이번 과제에서 별로 중요하지 않으므로, 여러분에게 맡기겠습니다. 8 | 9 | ``` 10 | /check-cookie 11 | ``` 12 | 13 | `check-cookie` endpoint는 `/set-cookie` endpoint로부터 쿠키를 전달받습니다. 쿠키에 `session` 키가 있다면 `{ user: 'hapi' }`를 반환하고, 없다면 `unauthorized` 접근 오류를 반환하도록 해주세요. 14 | 15 | -------------------- 16 | 17 | ## 힌트 18 | 19 | `server.route()` 함수에 다음과 같은 옵션을 추가하세요. 20 | 21 | ```js 22 | config: { 23 | state: { 24 | parse: true, 25 | failAction: 'log' 26 | } 27 | } 28 | ``` 29 | 30 | 이 옵션을 사용해서, 서버가 쿠키를 다양한 방법으로 다룰 수 있게 설정할 수 있습니다. 31 | 32 | `hapi`는 특정 URL에 대해 쿠키를 관리하는 기능을 제공합니다. 33 | 34 | ```js 35 | server.state('session', { 36 | path: '/', 37 | }); 38 | ``` 39 | 40 | request에 응답할 때, 다음과 같이 쿠키를 설정할 수 있습니다. 41 | 42 | ```js 43 | reply('success').state('session', 'session') 44 | ``` 45 | 46 | 쿠키값은 서버의 state에 보존되어, 다음과 같은 코드로 접근할 수 있습니다. 47 | 48 | ```js 49 | var session = request.state.session; 50 | ``` 51 | 52 | `hapi`에서 쿠키를 다루는 법에 대해 더 알고 싶으시다면, 다음 URL에서 확인하세요. [API](http://hapijs.com/api). 53 | 54 | 이번 과제에서는 필요하지 않지만, Boom 모듈([Boom](https://www.npmjs.com/package/boom))을 사용하면 간단하게 `unauthorized` 오류를 올바른 HTTP 상태 코드와 함께 반환할 수 있습니다. 다음과 같이 말이죠. 55 | 56 | ```js 57 | var Boom = require('boom'); 58 | ``` 59 | 60 | ```js 61 | reply(Boom.unauthorized('Missing authentication')); 62 | ``` 63 | -------------------------------------------------------------------------------- /exercises/cookies/problem.md: -------------------------------------------------------------------------------- 1 | Create a server that has a route configuration exposing an endpoint ``set- 2 | cookie`` and ``check-cookie`` which can be accessed using a `'GET'` request. 3 | Specifically: 4 | 5 | ``` 6 | /set-cookie 7 | ``` 8 | 9 | The `set-cookie` endpoint will set a cookie with the key 'session' and the value 10 | `{ key: 'makemehapi' }`. The cookie should be `base64json` encoded, should 11 | expire in `10 ms`, and have a domain scope of `localhost`. The response is 12 | unimportant for this exercise, and may be anything you like. 13 | 14 | ``` 15 | /check-cookie 16 | ``` 17 | 18 | The `check-cookie` endpoint will have cookies received from the `/set-cookie` 19 | endpoint. If the `session` key is present in cookies then simply return 20 | `{ user: 'hapi' }`, otherwise return an `unauthorized` access error. 21 | 22 | -------------------- 23 | 24 | ##HINTS 25 | 26 | In your `server.route()` function, you may add the following option: 27 | 28 | ```js 29 | config: { 30 | state: { 31 | parse: true, 32 | failAction: 'log' 33 | } 34 | } 35 | ``` 36 | 37 | By using this option, we can configure the server to handle cookies in various ways. 38 | 39 | `hapi` provides a way to manage cookies for a specific url path. 40 | 41 | ```js 42 | server.state('session', { 43 | path: '/', 44 | }); 45 | ``` 46 | 47 | We can set cookies while replying to request as follows: 48 | 49 | ```js 50 | reply('success').state('session', 'session') 51 | ``` 52 | 53 | Cookie values are stored in server state, accessible using following code: 54 | 55 | ```js 56 | var session = request.state.session; 57 | ``` 58 | 59 | More information about handling of cookies in `hapi` can be found in the Hapi 60 | documentation here [API](http://hapijs.com/api). 61 | 62 | While not required for this exercise, you may use [Boom](https://www.npmjs.com/package/boom) 63 | to more easily return an `unauthorized` error along with the correct HTTP status 64 | code: 65 | 66 | ```js 67 | var Boom = require('boom'); 68 | ``` 69 | 70 | ```js 71 | reply(Boom.unauthorized('Missing authentication')); 72 | ``` 73 | -------------------------------------------------------------------------------- /exercises/cookies/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const Boom = require('@hapi/boom'); 3 | 4 | (async () => { 5 | try { 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: Number(process.argv[2] || 8080) 9 | }); 10 | 11 | server.state('session', { 12 | path: '/', 13 | encoding: 'base64json', 14 | ttl: 10, 15 | domain: 'localhost', 16 | isSameSite: false, 17 | isSecure: false, 18 | isHttpOnly: false 19 | }); 20 | 21 | server.route({ 22 | method: 'GET', 23 | path: '/set-cookie', 24 | handler: (request, h) => h.response({ 25 | message: 'success' 26 | }).state('session', { key: 'makemehapi' }), 27 | options: { 28 | state: { 29 | parse: true, 30 | failAction: 'log' 31 | } 32 | } 33 | }); 34 | 35 | server.route({ 36 | method: 'GET', 37 | path: '/check-cookie', 38 | handler: (request, h) => { 39 | const { session } = request.state; 40 | let result; 41 | 42 | if (session) { 43 | result = { user: 'hapi' }; 44 | } else { 45 | result = Boom.unauthorized('Missing authentication'); 46 | } 47 | 48 | return result; 49 | }, 50 | options: { 51 | state: { 52 | parse: true, 53 | failAction: 'log' 54 | } 55 | } 56 | }); 57 | 58 | await server.start(); 59 | } catch (error) { 60 | console.log(error); 61 | } 62 | })(); 63 | -------------------------------------------------------------------------------- /exercises/directories/exercise.js: -------------------------------------------------------------------------------- 1 | const exercise = require('../../lib/exercise') 2 | 3 | exercise.queryUrl = (port) => `http://localhost:${port}/foo/bar/baz/file.html`; 4 | 5 | module.exports = exercise; 6 | -------------------------------------------------------------------------------- /exercises/directories/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur qui route toute requête vers le chemin 2 | `/foo/bar/baz/file.html` vers un fichier présent dans un répertoire, par 3 | exemple `public/file.html`. Ce fichier contiendrait : 4 | 5 | ```html 6 | 7 | Salut les répertoires 8 | 9 | Salut les répertoires 10 | 11 | 12 | ``` 13 | 14 | ----------------------------------------------------------------- 15 | 16 | ## Conseils 17 | 18 | Les gestionnaires peuvent être un objet avec un chemin vers un répertoire : 19 | 20 | ```js 21 | handler: { 22 | directory: { path: './public' } 23 | } 24 | ``` 25 | 26 | Les routes reposant sur un gestionnaire de type `directory` doivent inclure un 27 | paramètre à la fin de la définition de leur chemin d’accès. Ce chemin d’accès 28 | associé à la route n’a pas besoin de correspondre à un quelconque répertoire 29 | présent sur le disque. De même, le nom du paramètre à la fin du chemin n’a pas 30 | d’importance particulière. 31 | 32 | ```js 33 | path: "/chemin/vers/quelquepart/{param}" 34 | ``` 35 | 36 | Attention, dans la pratique, vous devrez fournir un chemin absolu pointant 37 | vers un répertoire `public` situé dans le répertoire courant. Vous aurez 38 | sans doute besoin du module noyau `path`, de sa fonction `join()`, et de 39 | la variable globale `__dirname` pour y arriver. 40 | -------------------------------------------------------------------------------- /exercises/directories/problem.ja.md: -------------------------------------------------------------------------------- 1 | '`/foo/bar/baz/file.html`'へのリクエストを、あるディレクトリに存在する 2 | ファイルへルートするサーバーを作成しましょう。例えば、'`public/file.html`' 3 | にルートします。このファイルは以下の内容を持ちます。 4 | 5 | ```html 6 | 7 | Hello Directories 8 | 9 | Hello Directories 10 | 11 | 12 | ``` 13 | 14 | ----------------------------------------------------------------- 15 | ## ヒント 16 | 17 | サーバーが静的なコンテンツを提供するために、前回のエクササイズと同様、 18 | `inert`モジュールを`require`し、`register`する必要があるでしょう。 19 | 20 | ハンドラは以下のように、ディレクトリパスをもつオブジェクトとして宣言 21 | することが出来ます。 22 | 23 | ```js 24 | handler: { 25 | directory: { path: './public' } 26 | } 27 | ``` 28 | 29 | ディレクトリハンドラを使用するルートは、パス(URL)文字列の最後にパス 30 | パラメータを含めなければなりません。 31 | 32 | ルートに設定すパスは、実際のファイルシステムのディレクトリ構造に 33 | 一致している必要はありません。また、パラメータ名は何でも構いません。 34 | 35 | ```js 36 | path: "/path/to/somewhere/{param}" 37 | ``` 38 | 39 | [注意] 40 | 実際にはプログラムディレクトリの中にある`public`ディレクトリへの 41 | 絶対パスを指定する必要があるでしょう。このパスを取得するために、 42 | `path`コアモジュールの`join()`関数、及びグローバル変数の`__dirname` 43 | が必要となるでしょう。 44 | -------------------------------------------------------------------------------- /exercises/directories/problem.ko.md: -------------------------------------------------------------------------------- 1 | 서버를 만들어봅시다. `/foo/bar/baz/file.html` 경로의 요청에 대해 특정 디렉터리에 있는 파일(예: public/file.html)을 반환해주도록 설정해 주세요. 파일에는 다음 내용이 포함되도록 해주세요. 2 | 3 | ```html 4 | 5 | Hello Directories 6 | 7 | Hello Directories 8 | 9 | 10 | ``` 11 | 12 | ----------------------------------------------------------------- 13 | ## 힌트 14 | 15 | 이번 과제에서는 이전 과제와 마찬가지로, 정적인 디렉터리의 내용을 제공하려면 `inert` 플러그인을 `require` 해서 `register` 해야 합니다. 16 | 17 | `handler`는 다음과 같이 디렉터리 경로를 포함한 객체로 선언될 수 있습니다. 18 | 19 | ```js 20 | handler: { 21 | directory: { path: './public' } 22 | } 23 | ``` 24 | 25 | 디렉터리 `handler`에서 사용하는 경로는, 반드시 path 문자열 뒤에 path 매개변수를 붙여야 합니다. 경로 지정에 사용되는 path는 파일 시스템 디렉터리 구조와 일치하지 않아도 됩니다. 매개변수 이름은 아무거나 괜찮습니다. 26 | 27 | 28 | ```js 29 | path: "/path/to/somewhere/{param}" 30 | ``` 31 | 32 | 실제 사용 시에는, 프로그램 디렉터리 안에 있는 `public` 디렉터리에 절대 경로를 지정해야 하는 것에 유의하세요. 이를 위해선 `path` 모듈의 `join()` 함수와 `__dirname`이라는 글로벌 변수가 필요할 것입니다. 33 | -------------------------------------------------------------------------------- /exercises/directories/problem.md: -------------------------------------------------------------------------------- 1 | Create a server which routes requests to the path `/foo/bar/baz/file.html` to a 2 | file in a directory, e.g. `public/file.html`, which contains the following: 3 | 4 | ```html 5 | 6 | Hello Directories 7 | 8 | Hello Directories 9 | 10 | 11 | ``` 12 | 13 | ----------------------------------------------------------------- 14 | ##HINTS 15 | 16 | You'll need to `require` and `register` the `inert` plugin in this exercise the 17 | same as in the previous exercise in order to serve the static directory's 18 | contents. 19 | 20 | Handlers can be declared as an object with a directory path: 21 | 22 | ```js 23 | handler: { 24 | directory: { path: './public' } 25 | } 26 | ``` 27 | 28 | Routes using the directory handler must include a path parameter at the end of 29 | the path string. The path defined for the route does not need to correspond to 30 | the file system directory structure, and the parameter name does not matter. 31 | 32 | ```js 33 | path: "/path/to/somewhere/{param}" 34 | ``` 35 | 36 | Be careful: in practice, you'll need to provide an absolute path to a 37 | `public` directory in your program's directory. To achieve this, you'll 38 | probably need the `path` core module, its `join()` function, and the 39 | `__dirname` global variable. 40 | -------------------------------------------------------------------------------- /exercises/directories/solution/public/file.html: -------------------------------------------------------------------------------- 1 | 2 | Hello Directories 3 | 4 | Hello Directories 5 | 6 | 7 | -------------------------------------------------------------------------------- /exercises/directories/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Hapi = require('@hapi/hapi'); 3 | const Inert = require('@hapi/inert'); 4 | 5 | (async () => { 6 | try { 7 | const server = Hapi.Server({ 8 | host: 'localhost', 9 | port: process.argv[2] || 8080 10 | }); 11 | 12 | await server.register(Inert); 13 | 14 | server.route({ 15 | path: '/foo/bar/baz/{filename}', 16 | method: 'GET', 17 | handler: { 18 | directory: { 19 | path: Path.join(__dirname, 'public') 20 | } 21 | } 22 | }); 23 | 24 | await server.start(); 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /exercises/directories/solution_fr/public/file.html: -------------------------------------------------------------------------------- 1 | 2 | Salut les répertoires 3 | 4 | Salut les répertoires 5 | 6 | 7 | -------------------------------------------------------------------------------- /exercises/directories/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Hapi = require('@hapi/hapi'); 3 | const Inert = require('@hapi/inert'); 4 | 5 | (async () => { 6 | try { 7 | const server = Hapi.Server({ 8 | host: 'localhost', 9 | port: process.argv[2] || 8080 10 | }); 11 | 12 | await server.register(Inert); 13 | 14 | server.route({ 15 | path: '/foo/bar/baz/{filename}', 16 | method: 'GET', 17 | handler: { 18 | directory: { 19 | path: Path.join(__dirname, 'public') 20 | } 21 | } 22 | }); 23 | 24 | await server.start(); 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /exercises/extend/exercise.js: -------------------------------------------------------------------------------- 1 | // extend 2 | -------------------------------------------------------------------------------- /exercises/extend/problem.md: -------------------------------------------------------------------------------- 1 | Hapi's request lifecycle gives multiple places to add functions to modify an 2 | inbound request. Functions can be tied into the extension points by using the 3 | following syntax: 4 | 5 | ```javascript 6 | server.ext('onRequest', function (request, next) { 7 | 8 | //do some things 9 | 10 | next(); 11 | }); 12 | ``` 13 | 14 | The extension points are listed below: 15 | 16 | * onRequest - initial request, before routing 17 | * onPreAuth - by now, Hapi has: 18 | * looked up the route 19 | * parsed cookies 20 | * onPostAuth - by now, Hapi has: 21 | * authenticated request 22 | * parsed payload 23 | * authenticated payload 24 | * onPreHandler - by now, Hapi has: 25 | * validated path parameters 26 | * validated query 27 | * validated payload 28 | * onPostHandler - by now Hapi has: 29 | * handled route prerequisites 30 | * called the handler 31 | * onPreResponse 32 | * Validates payload 33 | 34 | For this exercise, create a function that will be added to a server on the 35 | 'onRequest' extension point. Have the function log the requested URL path to the 36 | console. 37 | 38 | The workshop will execute requests against the server and verify the output. 39 | 40 | ----------------------------------------------------------------- 41 | ##HINTS 42 | 43 | After creating a server, add an extension point by calling the server's 44 | ```ext``` function. 45 | 46 | ```js 47 | server.ext(extensionPoint, function (request, next) { ... 48 | ``` 49 | 50 | The path will be part of the inbound request. Ensure to call ```next();``` 51 | within the extension handler! 52 | ----------------------------------------------------------------- 53 | -------------------------------------------------------------------------------- /exercises/extend/solution/solution.js: -------------------------------------------------------------------------------- 1 | // extend 2 | -------------------------------------------------------------------------------- /exercises/handling/exercise.js: -------------------------------------------------------------------------------- 1 | const exercise = require('../../lib/exercise') 2 | 3 | exercise.queryUrl = (port) => `http://localhost:${port}/`; 4 | 5 | module.exports = exercise; 6 | -------------------------------------------------------------------------------- /exercises/handling/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur qui répond aux requêtes sur `/` avec un fichier HTML 2 | statique nommé `index.html` qui contiendra le code suivant : 3 | 4 | ```html 5 | 6 | Coucou du gestionnaire 7 | 8 | Salut, je suis servi grâce au gestionnaire 9 | 10 | 11 | ``` 12 | 13 | ----------------------------------------------------------------- 14 | 15 | ## Conseils 16 | 17 | Vous pouvez déclarer comme gestionnaires des objets plutôt que des fonctions. 18 | Un tel objet doit contenir une des clés suivantes : `file`, `directory`, 19 | `proxy` ou `view`. 20 | 21 | Par exemple, `handler` peut recevoir comme valeur un objet avec la clé `file` : 22 | 23 | ```js 24 | handler: { 25 | file: "index.html" 26 | } 27 | ``` 28 | 29 | Attention, dans la pratique, vous devrez fournir un chemin absolu pointant 30 | vers un fichier `index.html` situé dans le répertoire courant. Vous aurez 31 | sans doute besoin du module noyau `path`, de sa fonction `join()`, et de 32 | la variable globale `__dirname` pour y arriver. 33 | -------------------------------------------------------------------------------- /exercises/handling/problem.ja.md: -------------------------------------------------------------------------------- 1 | パス"`/`"へのリクエストに対し、静的なHTMLファイル`index.html`の内容を返す 2 | サーバーを作成して下さい。`index.html`の内容は以下のとおりです。 3 | 4 | ```html 5 | 6 | Hello Handling 7 | 8 | Hello Handling 9 | 10 | 11 | ``` 12 | 13 | ----------------------------------------------------------------- 14 | ## ヒント 15 | 16 | このエクササイズをするために、`@hapi/inert`モジュールをインストールする 17 | 必要があります。これはhapiのプラグインで、静的なファイル、あるいは 18 | ディレクトリを扱うためのものです。このモジュールを使用するためには、 19 | コードの中でプラグインをhapiに登録する必要があります。 20 | 21 | ```js 22 | var Inert = require('@hapi/inert'); 23 | 24 | await server.register(Inert); 25 | ``` 26 | 27 | ハンドラは関数ではなく、オブジェクトとして宣言することが出来ます。 28 | オブジェクトは次の何れかを持ちます。 29 | 30 | * `file` (要`@hapi/inert`プラグイン) 31 | * `directory` (要`@hapi/inert`プラグイン) 32 | * `proxy` (要`@hapi/h2o2`プラグイン) 33 | * `view` (要`@hapi/vision`プラグイン) 34 | 35 | 以下は`file`キーを持つオブジェクトをハンドラとして設定する例です。 36 | 37 | ```js 38 | handler: { 39 | file: "index.html" 40 | } 41 | ``` 42 | 43 | [注意] 44 | 実際にはプログラムディレクトリの中にある`index.html`への絶対パスを 45 | 指定する必要があるでしょう。このパスを取得するために、`path`コア 46 | モジュールの`join()`関数、及びグローバル変数の`__dirname`が必要と 47 | なるかもしれません。 48 | -------------------------------------------------------------------------------- /exercises/handling/problem.ko.md: -------------------------------------------------------------------------------- 1 | 서버를 만들어 봅시다. `/` request에 정적 HTML 파일인 `index.html`을 반환하도록 해주세요. `index.html`의 내용은 다음과 같습니다. 2 | 3 | ```html 4 | 5 | Hello Handling 6 | 7 | Hello Handling 8 | 9 | 10 | ``` 11 | 12 | ----------------------------------------------------------------- 13 | ## 힌트 14 | 15 | 이번 과제에는 `inert` 모듈이 필요합니다. 이 모듈은 hapi 플러그인으로, 정적 파일과 디렉터리를 다룹니다. 정적 파일을 서비스하려면 플러그인을 코드에 등록해야 합니다. 16 | 17 | ```js 18 | var Inert = require('@hapi/inert'); 19 | 20 | await server.register(Inert); 21 | ``` 22 | 23 | 함수 대신 객체로 `handler`를 선언할 수도 있습니다. 객체는 다음 항목 중 반드시 하나는 가지고 있어야 합니다. `file`(`@hapi/inert` 플러그인 필요), `directory`(`@hapi/inert` 플러그인 필요), `proxy`(`@hapi/h2o2` 플러그인 필요), `view`(`@hapi/vision` 플러그인 필요) 24 | 25 | 다음과 같이 `handler`에 `file` 키를 갖는 객체를 설정할 수 있습니다. 26 | 27 | ```js 28 | handler: { 29 | file: "index.html" 30 | } 31 | ``` 32 | 33 | 실제 사용 시에는, 프로그램 디렉터리 안에 있는 `index.html`에 절대 경로를 지정해야 하는 것에 유의하세요. 이를 위해선 `path` 모듈의 `join()` 함수와 `__dirname`이라는 글로벌 변수가 필요할 것입니다. 34 | 35 | -------------------------------------------------------------------------------- /exercises/handling/problem.md: -------------------------------------------------------------------------------- 1 | Create a server which responds to requests to `/` with a static HTML file named 2 | `index.html` containing the following: 3 | 4 | ```html 5 | 6 | Hello Handling 7 | 8 | Hello Handling 9 | 10 | 11 | ``` 12 | 13 | ----------------------------------------------------------------- 14 | ##HINTS 15 | 16 | This exercise requires you to install the `@hapi/inert` module, which is a hapi 17 | plugin for serving static files and directories. You'll need to register the 18 | plugin in your code in order to serve static files: 19 | 20 | ```js 21 | var Inert = require('@hapi/inert'); 22 | 23 | await server.register(Inert); 24 | ``` 25 | 26 | You can declare handlers as objects instead of functions. The object must 27 | contain one of the following: `file` (requires `@hapi/inert` plugin), `directory` 28 | (requires `@hapi/inert` plugin), `proxy` (requires `@hapi/h2o2` plugin), or 29 | `view` (requires `@hapi/vision` plugin). 30 | 31 | For example, `handler` can be assigned an object with the `file` key: 32 | 33 | ```js 34 | handler: { 35 | file: "index.html" 36 | } 37 | ``` 38 | 39 | Be careful: in practice, you'll need to provide an absolute path to an 40 | `index.html` file in your program's directory. To achieve this, you'll probably 41 | need the `path` core module, its `join()` function, and the `__dirname` global 42 | variable. 43 | 44 | You can also configure a files base path in your server and just pass relative 45 | paths in the route. This is specially useful if you have multiple routes that 46 | respond with files. 47 | 48 | ## Docs 49 | 50 | - Hapi - Route options: https://hapi.dev/api#route-options 51 | - Hapi - Serving Static Content Tutorial: https://hapi.dev/tutorials/servingfiles 52 | -------------------------------------------------------------------------------- /exercises/handling/solution/index.html: -------------------------------------------------------------------------------- 1 | 2 | Hello Handling 3 | 4 | Hello Handling 5 | 6 | 7 | -------------------------------------------------------------------------------- /exercises/handling/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const Inert = require('@hapi/inert'); 3 | 4 | (async () => { 5 | try { 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: process.argv[2] || 8080, 9 | routes: { 10 | files: { 11 | relativeTo: __dirname 12 | } 13 | } 14 | }); 15 | 16 | await server.register(Inert); 17 | 18 | server.route({ 19 | path: '/', 20 | method: 'GET', 21 | handler: { 22 | file: 'index.html' 23 | } 24 | }); 25 | 26 | await server.start(); 27 | } catch (error) { 28 | console.log(error); 29 | } 30 | })(); 31 | -------------------------------------------------------------------------------- /exercises/handling/solution_fr/index.html: -------------------------------------------------------------------------------- 1 | 2 | Coucou du gestionnaire 3 | 4 | Salut, je suis servi grâce au gestionnaire 5 | 6 | 7 | -------------------------------------------------------------------------------- /exercises/handling/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const Inert = require('@hapi/inert'); 3 | 4 | (async () => { 5 | try { 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: process.argv[2] || 8080, 9 | routes: { 10 | files: { 11 | relativeTo: __dirname 12 | } 13 | } 14 | }); 15 | 16 | await server.register(Inert); 17 | 18 | server.route({ 19 | path: '/', 20 | method: 'GET', 21 | handler: { 22 | file: 'index.html' 23 | } 24 | }); 25 | 26 | await server.start(); 27 | } catch (error) { 28 | console.log(error); 29 | } 30 | })(); 31 | -------------------------------------------------------------------------------- /exercises/hello_hapi/exercise.js: -------------------------------------------------------------------------------- 1 | const exercise = require('../../lib/exercise') 2 | 3 | exercise.queryUrl = (port) => `http://localhost:${port}/`; 4 | 5 | module.exports = exercise; 6 | -------------------------------------------------------------------------------- /exercises/hello_hapi/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur hapi qui écoute sur le numéro de port passé dans 2 | la ligne de commande, et répond par « Bonjour hapi » aux requêtes 3 | HTTP GET envoyées sur `/`. 4 | 5 | L’exercice enverra des requêtes à votre serveur et vérifiera leurs réponses. 6 | 7 | ----------------------------------------------------------------- 8 | 9 | ## Conseils 10 | 11 | Créez pour commencer un serveur qui écoute par défaut sur le port 8080, 12 | sauf si on lui passe un numéro de port explicite via la ligne de commande, 13 | à l’aide du code suivant : 14 | 15 | ```js 16 | var Hapi = require('@hapi/hapi'); 17 | var server = Hapi.Server({ 18 | host: 'localhost', 19 | port: Number(process.argv[2] || 8080) 20 | }); 21 | ``` 22 | 23 | On ajoute les routes (les chemins des points d’accès) à l’aide de la 24 | fonction `route()` : 25 | 26 | ```js 27 | server.route({ path: '/', method: 'GET', handler: anonOrYourFunction }); 28 | ``` 29 | 30 | Les gestionnaires de requête peuvent être des fonctions anonymes, ou des 31 | fonctions déclarées ailleurs (c’est du JavaScript, quoi :P), mais tous 32 | doivent avoir la même signature : 33 | 34 | ```js 35 | function handler(request, reply) { 36 | 37 | // `request` a toutes les informations 38 | // `reply` gère la réponse au client 39 | 40 | reply({ mustFlow: true }); 41 | } 42 | ``` 43 | 44 | Pour que le serveur commence à écouter sur le port défini au préalable, 45 | appelez la fonction `start()` : 46 | 47 | ```js 48 | await server.start(); 49 | 50 | console.log(`Serveur fonctionnant à: ${server.info.uri}`); 51 | ``` 52 | ----------------------------------------------------------------- 53 | -------------------------------------------------------------------------------- /exercises/hello_hapi/problem.ja.md: -------------------------------------------------------------------------------- 1 | コマンドラインから渡されたポート番号をlistenするhapiサーバーを作ります。 2 | '/'に対するHTTP GETリクエストに対し、"Hello hapi"を返しましょう。 3 | 4 | このワークショッパーがあなたのサーバーにリクエストを発行し、結果を検証します。 5 | 6 | ----------------------------------------------------------------- 7 | ## ヒント 8 | 9 | 以下のコードでは、コマンドラインから何も渡されなかった場合ポート`8080`をlistenします。 10 | 11 | ```js 12 | var Hapi = require('@hapi/hapi'); 13 | var server = Hapi.Server({ 14 | host: 'localhost', 15 | port: Number(process.argv[2] || 8080) 16 | }); 17 | ``` 18 | 19 | ルートは`route`関数で追加することが出来ます。 20 | 21 | ```js 22 | server.route({path: '/', method:'GET', handler: anonOrYourFunction}); 23 | ``` 24 | 25 | ハンドラには無名関数、あるいは別に定義された関数を設定することが出来ます。 26 | (Javascriptではよくあることですね!) 但し、全てのハンドラは以下の形式 27 | でなければなりません。 28 | 29 | ```js 30 | function handler(request, reply) { 31 | 32 | // Requestが全ての情報を持ちます 33 | // Replyがクライアントへのレスポンスを扱います 34 | 35 | reply(); 36 | } 37 | ``` 38 | 39 | `start`関数をコールすることにより、サーバーは指定されたポートのlistenを始めます。 40 | `start`関数にはコールバックが必要となることに気をつけて下さい。 41 | 42 | ```js 43 | await server.start(); 44 | 45 | console.log('Server running at:', server.info.uri); 46 | ``` 47 | ----------------------------------------------------------------- 48 | -------------------------------------------------------------------------------- /exercises/hello_hapi/problem.ko.md: -------------------------------------------------------------------------------- 1 | hapi 서버를 만들어 봅시다. 커맨드 라인으로 포트 번호를 받아 설정하고, `/`로 들어오는 HTTP GET request에 "Hello hapi"라고 응답하도록 만들어 봅시다. 2 | 3 | 이 워크숍은 서버에 request를 보내고, 출력을 확인합니다. 4 | 5 | ----------------------------------------------------------------- 6 | ## 힌트 7 | 8 | `8080` 포트를 사용하는 서버를 만드세요. 커맨드 라인에서 아무것도 전달되지 않는다면 다음 코드를 사용하세요. 9 | 10 | ```js 11 | var Hapi = require('@hapi/hapi'); 12 | var server = Hapi.Server({ 13 | host: 'localhost', 14 | port: Number(process.argv[2] || 8080) 15 | }); 16 | ``` 17 | 18 | 경로는 `route` 함수를 통해 추가하세요. 19 | 20 | ```js 21 | server.route({path: '/', method:'GET', handler: anonOrYourFunction}); 22 | ``` 23 | 24 | 핸들러는 익명 함수나 별도로 선언할 수 있습니다. (자바스크립트처럼요! :P) 단, 반드시 아래와 같이 작성해야 합니다. 25 | 26 | ```js 27 | function handler(request, reply) { 28 | 29 | // Request has all information 30 | // Reply handles client response 31 | 32 | reply(); 33 | } 34 | ``` 35 | 36 | `start` 함수를 호출해서 지정된 포트를 사용하는 서버를 가져오세요. `start` 함수를 호출할 때 콜백을 작성해야 하는 것도 잊지 마세요. 37 | 38 | ```js 39 | await server.start(); 40 | 41 | console.log('Server running at:', server.info.uri); 42 | ``` 43 | ----------------------------------------------------------------- 44 | -------------------------------------------------------------------------------- /exercises/hello_hapi/problem.md: -------------------------------------------------------------------------------- 1 | Create a hapi server that listens on a port passed from the command line and 2 | replies with "Hello hapi" when an HTTP GET request is sent to `/` . 3 | 4 | 5 | The workshop will execute requests against the server and verify the output. 6 | 7 | ----------------------------------------------------------------- 8 | ##HINTS 9 | 10 | Create a server that listens on port `8080` , if none is passed from the command 11 | line, with the following code: 12 | 13 | ```js 14 | var Hapi = require('@hapi/hapi'); 15 | var server = Hapi.Server({ 16 | host: 'localhost', 17 | port: Number(process.argv[2] || 8080) 18 | }); 19 | 20 | ``` 21 | 22 | Routes are added via the `route` function: 23 | 24 | ```js 25 | server.route({path: '/', method:'GET', handler: anonOrYourFunction}); 26 | ``` 27 | 28 | Handlers can be anonymous functions or separately declared (just like in 29 | javascript :P), but all of them should have this signature: 30 | 31 | ```js 32 | function handler(request, h) { 33 | 34 | // Request has all information 35 | // a string can be returned 36 | 37 | return 'a string in the response'; 38 | } 39 | ``` 40 | 41 | Calling the `start` function gets a server listening on the assigned port. Note 42 | that await is required when calling `start`: 43 | 44 | ```js 45 | await server.start(); 46 | 47 | console.log('Server running at:', server.info.uri); 48 | ``` 49 | ----------------------------------------------------------------- 50 | -------------------------------------------------------------------------------- /exercises/hello_hapi/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | 3 | (async () => { 4 | try { 5 | const server = Hapi.Server({ 6 | host: 'localhost', 7 | port: Number(process.argv[2] || 8080) 8 | }); 9 | 10 | server.route({ 11 | path: '/', 12 | method: 'GET', 13 | handler: (request, h) => 'Hello hapi' 14 | }); 15 | 16 | await server.start(); 17 | 18 | console.log(`Server running at: ${server.info.uri}`); 19 | } catch (error) { 20 | console.log(error); 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /exercises/hello_hapi/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | 3 | (async () => { 4 | try { 5 | const server = Hapi.Server({ 6 | host: 'localhost', 7 | port: Number(process.argv[2] || 8080) 8 | }); 9 | 10 | server.route({ 11 | path: '/', 12 | method: 'GET', 13 | handler: (request, h) => { 14 | return 'Bonjour hapi!'; 15 | } 16 | }); 17 | 18 | await server.start(); 19 | 20 | console.log(`Serveur fonctionnant à: ${server.info.uri}`); 21 | } catch (error) { 22 | console.log(error); 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /exercises/helping/exercise.js: -------------------------------------------------------------------------------- 1 | const exercise = require('../../lib/exercise') 2 | 3 | exercise.queryUrl = (port) => { 4 | const nameParam = encodeURIComponent(exercise.__('name_param')); 5 | return `http://localhost:${port}/?name=${nameParam}`; 6 | }; 7 | 8 | module.exports = exercise; 9 | -------------------------------------------------------------------------------- /exercises/helping/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur qui répond aux requêtes sur `/?name=assistant&suffix=!` en 2 | utilisant le gabarit de l’exercice VUES. 3 | 4 | Au lieu de placer le paramètre de la *query string* directement dans le template, 5 | créez un helper dans `helpers/helper.js` et utilisez-le dans le template pour 6 | afficher le paramètre de *query string* `name`. 7 | 8 | ```html 9 | 10 | Bonjour assistant! 11 | 12 | Bonjour assistant! 13 | 14 | 15 | ``` 16 | 17 | Le helper doit concaténer les paramètres de *query string* `name` et `suffix`. 18 | 19 | ----------------------------------------------------------------- 20 | 21 | ## Conseils 22 | 23 | Les helpers sont des fonctions utilisées à l’intérieur des templates pour 24 | effectuer des transformations et des manipulations de données en utilisant le 25 | contexte du template et d’autres sources. 26 | 27 | Vous pouvez définir un chemin de helpers dans les options du serveur. Tous les 28 | fichiers `.js` de ce répertoire seront chargés et le nom de chaque fichier sera 29 | utilisé comme nom du helper fourni. 30 | 31 | ```js 32 | var options = { 33 | views: { 34 | … 35 | helpersPath: 'helpers' 36 | } 37 | }; 38 | ``` 39 | 40 | Chaque fichier doit exporter une unique fonction avec comme signature 41 | `function(context)`, qui renverra une `String`. La requête que vous 42 | manipuleriez d’habitude dans un gestionnaire de requête est accessible 43 | via `context.data.root`. 44 | 45 | ``` 46 | module.exports = function(context) { 47 | return context.data.root.query.foo; 48 | } 49 | ``` 50 | 51 | Votre fonction helper peut alors être utilisée dans le template : 52 | 53 | ```html 54 |
{{helper}}
55 | ``` 56 | -------------------------------------------------------------------------------- /exercises/helping/problem.ja.md: -------------------------------------------------------------------------------- 1 | ビューエクササイズのテンプレートを使用し、'`/?name=Helping&suffix=!`'への 2 | リクエストに応答するサーバーを作成します。 3 | 4 | 今回はクエリパラメータを直接テンプレートで使用するのではなく、'`helpers/helper.js`' 5 | にヘルパーを作成し、このヘルパーをテンプレート中で使用してクエリパラメータ'`name`' 6 | を出力します。 7 | 8 | ```html 9 | 10 | Hello Helping! 11 | 12 | Hello Helping! 13 | 14 | 15 | ``` 16 | 17 | ヘルパーでクエリパラメータ'`name`'と'`suffix`'を結合して下さい。 18 | 19 | ----------------------------------------------------------------- 20 | ## ヒント 21 | 22 | テンプレートを使用するので、'`@hapi/vision`'プラグインを登録していることを 23 | 確認して下さい。 24 | 25 | ヘルパーはテンプレートコンテキストやその他の入力を使用し、テンプレート中で 26 | 変換・その他のデータ操作を行う関数です。 27 | 28 | ヘルパーのパスはサーバーオプションで指定することが出来ます。指定された 29 | ディレクトリ中にある全ての'`.js'`ファイルがロードされます、ファイル名が 30 | ヘルパー名となります。 31 | 32 | ```js 33 | var options = { 34 | views: { 35 | ... 36 | helpersPath: 'helpers' 37 | } 38 | }; 39 | ``` 40 | 41 | 各ファイルは'`function(context)`'の形式の関数を1つだけエクスポートする 42 | 必要があります。この関数の返り値は文字列です。 43 | 44 | ``` 45 | module.exports = function(context) { 46 | return context.data.root.query.foo; 47 | } 48 | ``` 49 | 50 | このヘルパーファンクションはテンプレート中で以下のように使用します。 51 | 52 | ```html 53 |
{{helper}}
54 | ``` 55 | -------------------------------------------------------------------------------- /exercises/helping/problem.ko.md: -------------------------------------------------------------------------------- 1 | 서버를 만들어봅시다. VIEWS 과제에서 만든 템플릿을 이용해서 `/?name=Helping&suffix=!`로의 requests에 응답하도록 해주세요. 2 | 3 | 템플릿에 쿼리 매개변수를 직접 넣는 대신, `helpers/helper.js`에 helper를 만들고 템플릿에서 helper를 이용하여 쿼리 파라미터 `name`을 출력하세요. 4 | 5 | ```html 6 | 7 | Hello Helping! 8 | 9 | Hello Helping! 10 | 11 | 12 | ``` 13 | 14 | helper를 사용해서 쿼리 매개변수 `name`과 `suffix`를 결합해주세요. 15 | 16 | ----------------------------------------------------------------- 17 | ## 힌트 18 | 19 | 템플릿을 렌더링할 때 `@hapi/vision` 플러그인을 등록하는 걸 잊지 마세요. 20 | 21 | helper는 템플릿 내에서, 템플릿 컨텍스트와 기타 입력을 통해 변환과 기타 데이터 조작을 수행하는 함수입니다. 22 | 23 | helper의 경로는 서버의 옵션에서 지정할 수 있습니다. 지정된 디렉터리에 있는 모든 `.js` 파일이 로드되고 파일명은 helper의 이름이 됩니다. 24 | 25 | ```js 26 | var options = { 27 | views: { 28 | ... 29 | helpersPath: 'helpers' 30 | } 31 | }; 32 | ``` 33 | 34 | 각 파일은 `function(context)` 형식의 메서드를 export 해야 하며, 문자열을 반환해야 합니다. 35 | 36 | ``` 37 | module.exports = function(context) { 38 | return context.data.root.query.foo; 39 | } 40 | ``` 41 | 42 | 완성된 helper 함수는 다음과 같이 템플릿 안에서 사용됩니다. 43 | 44 | ```html 45 |
{{helper}}
46 | ``` 47 | -------------------------------------------------------------------------------- /exercises/helping/problem.md: -------------------------------------------------------------------------------- 1 | Create a server which responds to requests to `/?name=Helping&suffix=!` using 2 | the template from the VIEWS exercise. 3 | 4 | Instead of placing the query parameter directly in the template, create a helper 5 | at `helpers/helper.js` and use this helper in the template to output the `name` 6 | query parameter. 7 | 8 | ```html 9 | 10 | Hello Helping! 11 | 12 | Hello Helping! 13 | 14 | 15 | ``` 16 | 17 | The helper should concatenate the `name` and `suffix` query parameters. 18 | 19 | ----------------------------------------------------------------- 20 | ##HINTS 21 | 22 | Be sure to register the `@hapi/vision` plugin when attempting to render the template. 23 | 24 | Helpers are functions used within templates to perform transformations and other 25 | data manipulations using the template context or other inputs. 26 | 27 | You can define a helpers path in the server options. All `.js` files in this 28 | directory will be loaded and the file name will be used as the helper name. 29 | 30 | ```js 31 | var options = { 32 | views: { 33 | ... 34 | helpersPath: 'helpers' 35 | } 36 | }; 37 | ``` 38 | 39 | Each file must export a single method with the signature `function(context)` and 40 | return a string. 41 | 42 | ``` 43 | module.exports = function(context) { 44 | return context.data.root.query.foo; 45 | } 46 | ``` 47 | 48 | The helper function can then be used in the template. 49 | 50 | ```html 51 |
{{helper}}
52 | ``` 53 | -------------------------------------------------------------------------------- /exercises/helping/solution/helpers/helper.js: -------------------------------------------------------------------------------- 1 | module.exports = function (context) { 2 | const { query } = context.data.root; 3 | return query.name + query.suffix; 4 | }; 5 | -------------------------------------------------------------------------------- /exercises/helping/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Hapi = require('@hapi/hapi'); 3 | const Vision = require('@hapi/vision'); 4 | const Handlebars = require('handlebars'); 5 | 6 | (async () => { 7 | try { 8 | const server = Hapi.Server({ 9 | host: 'localhost', 10 | port: Number(process.argv[2] || 8080) 11 | }); 12 | 13 | await server.register(Vision); 14 | 15 | server.views({ 16 | engines: { 17 | html: Handlebars 18 | }, 19 | path: Path.join(__dirname, 'templates'), 20 | helpersPath: Path.join(__dirname, 'helpers') 21 | }); 22 | 23 | server.route({ 24 | path: '/', 25 | method: 'GET', 26 | handler: { 27 | view: 'template.html' 28 | } 29 | }); 30 | 31 | await server.start(); 32 | 33 | console.log(`Server running at: ${server.info.uri}`); 34 | } catch (error) { 35 | console.log(error); 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /exercises/helping/solution/templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | Hello {{helper}} 3 | 4 | Hello {{helper}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /exercises/helping/solution_fr/helpers/helper.js: -------------------------------------------------------------------------------- 1 | module.exports = function (context) { 2 | const { query } = context.data.root; 3 | return query.name + query.suffix; 4 | }; 5 | -------------------------------------------------------------------------------- /exercises/helping/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Hapi = require('@hapi/hapi'); 3 | const Vision = require('@hapi/vision'); 4 | const Handlebars = require('handlebars'); 5 | 6 | (async () => { 7 | try { 8 | const server = Hapi.Server({ 9 | host: 'localhost', 10 | port: Number(process.argv[2] || 8080) 11 | }); 12 | 13 | await server.register(Vision); 14 | 15 | server.views({ 16 | engines: { 17 | html: Handlebars 18 | }, 19 | path: Path.join(__dirname, 'templates'), 20 | helpersPath: Path.join(__dirname, 'helpers') 21 | }); 22 | 23 | server.route({ 24 | path: '/', 25 | method: 'GET', 26 | handler: { 27 | view: 'template.html' 28 | } 29 | }); 30 | 31 | await server.start(); 32 | } catch (error) { 33 | console.log(error); 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /exercises/helping/solution_fr/templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | Bonjour {{helper}} 3 | 4 | Bonjour {{helper}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /exercises/menu.json: -------------------------------------------------------------------------------- 1 | [ 2 | "HELLO_HAPI", 3 | "ROUTES", 4 | "HANDLING", 5 | "DIRECTORIES", 6 | "VIEWS", 7 | "PROXIES", 8 | "HELPING", 9 | "STREAMS", 10 | "VALIDATION", 11 | "VALIDATION USING JOI OBJECT", 12 | "UPLOADS", 13 | "COOKIES", 14 | "AUTH" 15 | ] 16 | -------------------------------------------------------------------------------- /exercises/proxies/exercise.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const exercise = require('../../lib/exercise') 3 | 4 | exercise.queryUrl = (port) => `http://localhost:${port}/proxy`; 5 | 6 | exercise.addSetup(function (mode, callback) { 7 | const self = this; 8 | 9 | (async () => { 10 | try { 11 | // start the server being proxied to 12 | self.server = new Hapi.Server({ 13 | host: 'localhost', 14 | port: 65535 15 | }); 16 | 17 | self.server.route({ 18 | method: 'GET', 19 | path: '/proxy', 20 | handler: request => exercise.__('greeting') 21 | }); 22 | 23 | await self.server.start(); 24 | } catch (error) { 25 | console.log(error); 26 | } 27 | })(); 28 | 29 | process.nextTick(callback); 30 | }); 31 | 32 | exercise.addCleanup(function (mode, passed, callback) { 33 | if (this.server) { 34 | this.server.stop(); 35 | } 36 | 37 | process.nextTick(callback) 38 | }); 39 | 40 | module.exports = exercise; 41 | -------------------------------------------------------------------------------- /exercises/proxies/problem.fr.md: -------------------------------------------------------------------------------- 1 | Un proxy vous permet de relayer des requêtes d’un serveur/service à un autre. 2 | 3 | Créez un serveur qui écoute sur le numéro de port passé en argument de ligne 4 | de commande, qui accepte toute requête sur le chemin `/proxy` et les relaie 5 | à `http://localhost:65535/proxy`. 6 | 7 | ----------------------------------------------------------------- 8 | 9 | ## Conseils 10 | 11 | La clé `proxy` sert à générer un gestionnaire de proxy inverse. 12 | 13 | ```js 14 | handler: { 15 | proxy: { 16 | host: '127.0.0.1', 17 | port: 65535 18 | } 19 | } 20 | ``` 21 | 22 | ----------------------------------------------------------------- 23 | 24 | En savoir plus sur le fond : http://fr.wikipedia.org/wiki/Proxy_inverse 25 | -------------------------------------------------------------------------------- /exercises/proxies/problem.ja.md: -------------------------------------------------------------------------------- 1 | プロキシを使用することにより、リクエストを他のサーバー/サービスへ 2 | 中継することが出来ます。 3 | 4 | コマンドラインで指定されるポート番号をlistenするサーバーを作成 5 | します。このサーバーはパス'`/proxy`'へのリクエストを全て 6 | '`http://localhost:65535/proxy`'に転送します。 7 | 8 | ----------------------------------------------------------------- 9 | ## ヒント 10 | 11 | このエクササイズでは、`h2o2`モジュールが必要となります。これはhapiの 12 | プラグインで、プロキシを使用することが出来ます。このモジュールを使用 13 | するためには、下記のようにコード中でプラグインを登録する必要があります。 14 | 15 | ```js 16 | var H2o2 = require('h2o2'); 17 | 18 | await server.register(H2o2); 19 | ``` 20 | 21 | ハンドラの`proxy`キーを使用し、リバースプロキシを作ることが出来ます。 22 | 23 | ```js 24 | handler: { 25 | proxy: { 26 | host: '127.0.0.1', 27 | port: 65535 28 | } 29 | } 30 | ``` 31 | 32 | ----------------------------------------------------------------- 33 | [背景知識] 34 | https://en.wikipedia.org/wiki/Proxy_server 35 | -------------------------------------------------------------------------------- /exercises/proxies/problem.ko.md: -------------------------------------------------------------------------------- 1 | 프락시를 사용하면, 서버/서비스 간에 request를 넘겨주는 것이 가능합니다. 2 | 3 | 서버를 만들어봅시다. 커맨드 라인으로 포트 번호를 받아 설정하고 `/proxy`로 들어오는 request를 전부 `http://localhost:65535/proxy`로 넘겨줍시다. 4 | 5 | ----------------------------------------------------------------- 6 | ## 힌트 7 | 8 | 이번 과제는 `h2o2` 모듈이 필요합니다. 이 모듈은 프락시를 다룰 수 있는 hapi 플러그인입니다. `proxy` 설정을 사용하려면 이 모듈을 다음과 같이 등록해야 합니다. 9 | 10 | ```js 11 | var H2o2 = require('h2o2'); 12 | 13 | await server.register(H2o2); 14 | ``` 15 | 16 | `proxy` 키를 사용하면 리버스 프락시 handler를 만들 수도 있습니다. 17 | 18 | ```js 19 | handler: { 20 | proxy: { 21 | host: '127.0.0.1', 22 | port: 65535 23 | } 24 | } 25 | ``` 26 | 27 | ----------------------------------------------------------------- 28 | 참고자료: en.wikipedia.org/wiki/Proxy_server 29 | -------------------------------------------------------------------------------- /exercises/proxies/problem.md: -------------------------------------------------------------------------------- 1 | A proxy lets you relay requests from one server/service to another. 2 | 3 | Create a server which listens on a port passed from the command line, takes any 4 | requests to the path `/proxy` and proxies them to `http://localhost:65535/proxy`. 5 | 6 | ----------------------------------------------------------------- 7 | ##HINTS 8 | 9 | This exercise requires you to install the `h2o2` module, which is a hapi plugin 10 | for handling proxies. You'll need to register the plugin in your code in 11 | order to use the `proxy` configuration: 12 | 13 | ```js 14 | var H2o2 = require('h2o2'); 15 | 16 | await server.register(H2o2); 17 | ``` 18 | 19 | The `proxy` key can be used to generate a reverse proxy handler. 20 | 21 | ```js 22 | handler: { 23 | proxy: { 24 | host: '127.0.0.1', 25 | port: 65535 26 | } 27 | } 28 | ``` 29 | 30 | ----------------------------------------------------------------- 31 | Background info: en.wikipedia.org/wiki/Proxy_server 32 | -------------------------------------------------------------------------------- /exercises/proxies/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const H2o2 = require('@hapi/h2o2'); 3 | 4 | (async () => { 5 | try { 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: process.argv[2] || 8080 9 | }); 10 | 11 | await server.register(H2o2); 12 | 13 | server.route({ 14 | method: 'GET', 15 | path: '/proxy', 16 | handler: { 17 | proxy: { 18 | host: '127.0.0.1', 19 | port: 65535 20 | } 21 | } 22 | }); 23 | 24 | await server.start(); 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /exercises/routes/exercise.js: -------------------------------------------------------------------------------- 1 | const exercise = require('../../lib/exercise') 2 | 3 | exercise.queryUrl = (port) => `http://localhost:${port}/world`; 4 | 5 | module.exports = exercise; 6 | -------------------------------------------------------------------------------- /exercises/routes/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur hapi qui écoute sur le numéro de port passé en 2 | ligne de commande, et affiche « Bonjour [nom] », sachant que `[nom]` 3 | est remplacé par un paramètre de chemin fourni par uner requête GET 4 | sur `/{name}`. 5 | 6 | Quand vous aurez terminé le serveur, vous pouvez l’exécuter dans 7 | l’environnement de test avec : 8 | 9 | ```sh 10 | {appname} run program.js 11 | ``` 12 | 13 | Et quand vous serez prêt-e à valider votre exercice, faites : 14 | 15 | ```sh 16 | {appname} verify program.js 17 | ``` 18 | 19 | ----------------------------------------------------------------- 20 | 21 | ## Conseils 22 | 23 | Créez pour commencer un serveur qui écoute par défaut sur le port 8080, 24 | sauf si on lui passe un numéro de port explicite via la ligne de commande, 25 | à l’aide du code suivant : 26 | 27 | ```js 28 | var Hapi = require('@hapi/hapi'); 29 | var server = Hapi.Server({ 30 | host: 'localhost', 31 | port: Number(process.argv[2] || 8080) 32 | }); 33 | ``` 34 | 35 | Ajoutez un gestionnaire de route similaire à celui-ci : 36 | 37 | ```js 38 | function handler (request, h) { 39 | return `Bonjour ${request.params.name}`; 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /exercises/routes/problem.ja.md: -------------------------------------------------------------------------------- 1 | コマンドラインから渡されたポート番号をlistenするhapiサーバーを作ります。 2 | HTTP GETリクエストに対し、"Hello [name]"を返します。 3 | `/{name}`の形式で渡されるパスパラメータを取得して[name]の部分を置き換えて下さい。 4 | 5 | サーバープログラムが完成すれば、以下のコマンドでテスト環境での実行が出来ます。 6 | 7 | `{appname} run program.js` 8 | 9 | 期待した結果が得られれば、以下のコマンドで結果の検証を行って下さい。 10 | 11 | `{appname} verify program.js` 12 | 13 | ----------------------------------------------------------------- 14 | ## ヒント 15 | 16 | 以下のコードでは、コマンドラインから何も渡されなかった場合ポート`8080`をlistenします。 17 | 18 | ```js 19 | var Hapi = require('@hapi/hapi'); 20 | var server = Hapi.Server({ 21 | host: 'localhost', 22 | port: Number(process.argv[2] || 8080) 23 | }); 24 | ``` 25 | 26 | 以下のようなコードをルートのハンドラとして追加して下さい。 27 | 28 | ```js 29 | function handler (request, h) { 30 | return `Hello ${request.params.name}`; 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /exercises/routes/problem.ko.md: -------------------------------------------------------------------------------- 1 | hapi 서버를 만들어봅시다. 커맨드 라인으로 포트 번호를 받아 설정하고, `/{name}`으로 들어오는 HTTP GET request에 "Hello [name]"을 출력하도록 하세요. [name]에는 GET request의 `{name}`이 들어갑니다. 2 | 3 | 서버를 만들었다면, 다음 명령어로 테스트 환경에서 실행 가능합니다. 4 | 5 | `{appname} run program.js` 6 | 7 | 테스트 결과가 괜찮다면 다음 커맨드로 정답을 확인해보세요. 8 | 9 | `{appname} verify program.js` 10 | 11 | ----------------------------------------------------------------- 12 | ## 힌트 13 | 14 | `8080` 포트를 사용하는 서버를 만드세요. 커맨드 라인으로부터 아무것도 전달되지 않는다면 다음 코드를 사용하세요. 15 | 16 | ```js 17 | var Hapi = require('@hapi/hapi'); 18 | var server = Hapi.Server({ 19 | host: 'localhost', 20 | port: Number(process.argv[2] || 8080) 21 | }); 22 | ``` 23 | 24 | 경로 handler를 다음과 같은 코드로 추가해주세요. 25 | 26 | ```js 27 | function handler (request, h) { 28 | return `Hello ${request.params.name}`; 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /exercises/routes/problem.md: -------------------------------------------------------------------------------- 1 | Create a hapi server that listens on a port passed from the command line and 2 | outputs "Hello [name]" where [name] is replaced with the path parameter supplied 3 | to GET `/{name}` 4 | 5 | 6 | When you have completed your server, you can run it in the test environment 7 | with: 8 | 9 | `{appname} run program.js` 10 | 11 | And once you are ready to verify it then run: 12 | 13 | `{appname} verify program.js` 14 | 15 | ----------------------------------------------------------------- 16 | ##HINTS 17 | 18 | Create a server that listens on port `8080`, if none is passed from the command 19 | line, with the following code: 20 | 21 | ```js 22 | var Hapi = require('@hapi/hapi'); 23 | var server = Hapi.Server({ 24 | host: 'localhost', 25 | port: Number(process.argv[2] || 8080) 26 | }); 27 | ``` 28 | 29 | Add a route handler similar to the following: 30 | 31 | ```js 32 | function handler (request, h) { 33 | return `Hello ${request.params.name}`; 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /exercises/routes/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | 3 | (async () => { 4 | try { 5 | const server = Hapi.Server({ 6 | host: 'localhost', 7 | port: Number(process.argv[2] || 8080) 8 | }); 9 | 10 | server.route({ 11 | path: '/{name}', 12 | method: 'GET', 13 | handler (request, h) { 14 | return `Hello ${request.params.name}`; 15 | 16 | // a more secure alternative is this: 17 | // 18 | // return `Hello ${encodeURIComponent(request.params.name)}`; 19 | // 20 | // encodeURIComponent escapes all characters except the following: alphabetic, decimal digits, - _ . ! ~ * ' ( ) 21 | // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent 22 | // for more details why you should call encodeURIComponent on any user-entered parameter 23 | } 24 | }); 25 | 26 | await server.start(); 27 | 28 | console.log(`Server running at: ${server.info.uri}`); 29 | } catch (error) { 30 | console.log(error); 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /exercises/routes/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | 3 | (async () => { 4 | try { 5 | const server = Hapi.Server({ 6 | host: 'localhost', 7 | port: Number(process.argv[2] || 8080) 8 | }); 9 | 10 | server.route({ 11 | path: '/{name}', 12 | method: 'GET', 13 | handler (request, h) { 14 | return `Bonjour ${request.params.name}`; 15 | // Une alternative plus sécurisée serait: 16 | // 17 | // return `Bonjour ${encodeURIComponent(request.params.name)}`; 18 | // 19 | // `encodeURIComponent` échape tous les caractères spéciaux à l’exception 20 | // des suivants: alphabet simple, chiffres décimaux, - _ . ! ~ * ' ( ) 21 | // Voir https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent 22 | // pour de plus amples détails sur les raisons qui devraient vous faire utiliser 23 | // `encodeURIComponent` pour toute donnée saisie par l’utilisateur. 24 | } 25 | }); 26 | 27 | await server.start(); 28 | 29 | console.log(`Serveur fonctionnant à: ${server.info.uri}`); 30 | } catch (error) { 31 | console.log(error); 32 | } 33 | })(); 34 | -------------------------------------------------------------------------------- /exercises/routes/solution_ja/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | 3 | (async () => { 4 | try { 5 | const server = Hapi.Server({ 6 | host: 'localhost', 7 | port: Number(process.argv[2] || 8080) 8 | }); 9 | 10 | server.route({ 11 | path: '/{name}', 12 | method: 'GET', 13 | handler (request, h) { 14 | return `Hello ${request.params.name} `; 15 | // より安全な方法は以下です: 16 | // 17 | // return `Hello ${encodeURIComponent(request.params.name)}`; 18 | // 19 | // 'encodeURIComponent'は以下の文字以外、全ての文字をエスケープします。 20 | // * 英数字 21 | // * 右の記号 - _ . ! ~ * ' ( ) 22 | // ユーザーの入力パラメータに対し、'encodeURIComponent'を使用すべき理由は 23 | // 以下を参照して下さい。 24 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent 25 | } 26 | }); 27 | 28 | await server.start(); 29 | } catch (error) { 30 | console.log(error); 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /exercises/routes/solution_ko/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | 3 | (async () => { 4 | try { 5 | const server = Hapi.Server({ 6 | host: 'localhost', 7 | port: Number(process.argv[2] || 8080) 8 | }); 9 | 10 | server.route({ 11 | path: '/{name}', 12 | method: 'GET', 13 | handler (request, h) { 14 | return `Hello ${request.params.name} `; 15 | // 다음 코드가 더 안전합니다 16 | // 17 | // return `Hello ${encodeURIComponent(request.params.name)}`; 18 | // 19 | // encodeURIComponent는 이하의 문자 이외에는 전부 escape 시킵니다. 20 | // 영숫자, - _ . ! ~ * ' ( ) 21 | // 유저가 입력한 매개변수에 대해 왜 encodeURIComponent를 적용해야 하는지에 대한 이유는 아래 URL을 참고하세요. 22 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent 23 | } 24 | }); 25 | 26 | await server.start(); 27 | } catch (error) { 28 | console.log(error); 29 | } 30 | })(); 31 | -------------------------------------------------------------------------------- /exercises/static_serving/exercise.js: -------------------------------------------------------------------------------- 1 | // static server 2 | -------------------------------------------------------------------------------- /exercises/static_serving/problem.md: -------------------------------------------------------------------------------- 1 | static directories, using depth on a configuration -------------------------------------------------------------------------------- /exercises/static_serving/solution/solution.js: -------------------------------------------------------------------------------- 1 | // setup a static server 2 | -------------------------------------------------------------------------------- /exercises/streams/exercise.js: -------------------------------------------------------------------------------- 1 | const exercise = require('../../lib/exercise') 2 | 3 | exercise.queryUrl = (port) => `http://localhost:${port}/`; 4 | 5 | exercise.addVerifyProcessor(function (callback) { 6 | const { response, url } = this.submissionResult; 7 | if (response.statusCode === 404) { 8 | const msg = exercise.__('fail.page_not_found', { url }); 9 | exercise.emit('fail', msg); 10 | 11 | callback(null, false); 12 | } 13 | }); 14 | 15 | module.exports = exercise; 16 | -------------------------------------------------------------------------------- /exercises/streams/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur Hapi qui répond aux requêtes GET sur `/`. La réponse doit être 2 | un flux issu d’un fichier auquel vous aurez appliqué une transformation ROT13. 3 | Le fichier d’origine contient le texte suivant : 4 | 5 | ``` 6 | The Pursuit of Hapi-ness 7 | ``` 8 | 9 | Du coup la sortie devrait être : 10 | 11 | ``` 12 | Gur Chefhvg bs Uncv-arff 13 | ``` 14 | 15 | ----------------------------------------------------------------- 16 | 17 | ## Conseils 18 | 19 | ### Flux 20 | 21 | La fonction de traitement de requête `reply()` dans Hapi accepte aussi un flux 22 | comme argument. 23 | 24 | ### Fichier 25 | 26 | Le module noyau `fs` fournit une fonction `createReadStream(cheminDuFichier)` 27 | qui vous sera probablement utile. À vous de créer le fichier et de fournir 28 | son chemin complet de façon dynamique. 29 | 30 | ### ROT13 facile 31 | 32 | Pour cet exercice, vous aurez besoin du module tiers `rot13-transform`. Pour 33 | l’installez, faites simplement : 34 | 35 | ```sh 36 | npm install rot13-transform 37 | ``` 38 | 39 | Sa documentation est ici : 40 | 41 | https://www.npmjs.com/package/rot13-transform 42 | -------------------------------------------------------------------------------- /exercises/streams/problem.ja.md: -------------------------------------------------------------------------------- 1 | '`/`'への'GET'リクエストに対し、ストリームでROT13変換(シーザー暗号の1種)を 2 | 行ったファイルの内容を返すサーバーを作ります。変換するファイルの内容は下記です。 3 | 4 | ``` 5 | The Pursuit of Hapi-ness 6 | ``` 7 | 8 | 出力は以下のようになるはずです。 9 | 10 | ``` 11 | Gur Chefhvg bs Uncv-arff 12 | ``` 13 | 14 | ----------------------------------------------------------------- 15 | ## ヒント 16 | 17 | ### ストリーム 18 | 19 | '`reply`'というhapiのハンドラは引数としてストリームを取ることが出来ます。 20 | 21 | ### ファイル 22 | 23 | '`fs`'モジュールは'`createReadStream(pathToFile)`'を持っていますが、 24 | この関数が役に立つはずです。 25 | 26 | ### シンプル ROT13 27 | 28 | このエクササイズでは、'`rot13-transform`'を使用します。以下のコマンドで 29 | インストールして下さい。 30 | 31 | ```sh 32 | npm install rot13-transform 33 | ``` 34 | -------------------------------------------------------------------------------- /exercises/streams/problem.ko.md: -------------------------------------------------------------------------------- 1 | hapi 서버를 만들어봅시다. `/`로 들어오는 HTTP GET request에 스트림을 이용하여 ROT13 2 | 변환을 거친 파일 내용을 돌려주도록 해주세요. 변환할 파일의 내용은 다음과 같습니다. 3 | 4 | ``` 5 | The Pursuit of Hapi-ness 6 | ``` 7 | 8 | 출력되는 내용은 다음과 같습니다. 9 | 10 | ``` 11 | Gur Chefhvg bs Uncv-arff 12 | ``` 13 | 14 | ----------------------------------------------------------------- 15 | ##힌트 16 | 17 | ### 스트림 18 | 19 | `reply` hapi 핸들러는 스트림을 인수로 받을 수 있습니다. 20 | 21 | ### 파일 22 | 23 | `fs` 모듈은 `createReadStream(pathToFile)`이라는 유용한 함수가 있습니다. 24 | 25 | ### 간단한 ROT13 26 | 27 | 이번 과제에서 `rot13-transform`을 사용할 것입니다. 다음 명령어로 rot13-transform을 설치하세요. 28 | 29 | ```sh 30 | npm install rot13-transform 31 | ``` 32 | -------------------------------------------------------------------------------- /exercises/streams/problem.md: -------------------------------------------------------------------------------- 1 | Create a hapi server which responds to `GET` requests to `/` by streaming a 2 | ROT13'd version of a file that contains: 3 | 4 | ``` 5 | The Pursuit of Hapi-ness 6 | ``` 7 | 8 | Output should look like: 9 | 10 | ``` 11 | Gur Chefhvg bs Uncv-arff 12 | ``` 13 | 14 | ----------------------------------------------------------------- 15 | ##HINTS 16 | 17 | ### Stream 18 | 19 | The hapi handler return a stream. 20 | 21 | ### File 22 | 23 | The `fs` module has a `createReadStream(pathToFile)` function that would be useful. 24 | 25 | ### Simple ROT13 26 | 27 | In this exercise, we'll be using `rot13-transform`. To install rot13-transform: 28 | 29 | ```sh 30 | npm install rot13-transform 31 | ``` 32 | -------------------------------------------------------------------------------- /exercises/streams/solution/input.txt: -------------------------------------------------------------------------------- 1 | The Pursuit of Hapi-ness 2 | -------------------------------------------------------------------------------- /exercises/streams/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Fs = require('fs'); 2 | const Hapi = require('@hapi/hapi'); 3 | const Path = require('path'); 4 | const Rot13 = require('rot13-transform'); 5 | 6 | (async () => { 7 | try { 8 | const server = Hapi.Server({ 9 | host: 'localhost', 10 | port: Number(process.argv[2] || 8080) 11 | }); 12 | 13 | server.route({ 14 | path: '/', 15 | method: 'GET', 16 | config: { 17 | handler: (request, h) => { 18 | const thisfile = Fs.createReadStream(Path.join(__dirname, 'input.txt')); 19 | return thisfile.pipe(Rot13()); 20 | } 21 | } 22 | }); 23 | 24 | await server.start(); 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /exercises/uploads/exercise.js: -------------------------------------------------------------------------------- 1 | const FormData = require('form-data'); 2 | const fs = require('fs'); 3 | const exercise = require('../../lib/exercise') 4 | 5 | exercise.queryUrl = (port) => `http://localhost:${port}/upload`; 6 | 7 | exercise.requestOpts = { method: 'POST' }; 8 | 9 | exercise.handleInput = (request, stream, port) => { 10 | const form = new FormData(); 11 | form.append('description', 'makemehapi'); 12 | form.append('file', fs.createReadStream(`${__dirname}/solution/input.txt`)); 13 | const headers = form.getHeaders(); 14 | request.setHeader('content-type', headers['content-type']); 15 | form.pipe(request).pipe(stream); 16 | }; 17 | 18 | module.exports = exercise; 19 | -------------------------------------------------------------------------------- /exercises/uploads/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur avec un point d’accès qui accepte un téléversement (*upload*) 2 | de fichier sur le chemin suivant : 3 | 4 | ``` 5 | /upload 6 | ``` 7 | 8 | Le point d’accès doit accepter les clés suivantes : `description` et `file`. Le 9 | champ `description` doit contenir une `String` avec le texte que vous voulez, 10 | et `file` est le fichier à envoyer. La réponse doit être un objet JSON avec le 11 | motif suivant : 12 | 13 | ```json 14 | { 15 | description : // La description issue du formulaire 16 | file : { 17 | data : // Le contenu du fichier envoyé 18 | filename: // Le nom du fichier envoyé 19 | headers : // L’en-tête de fichier défini par Hapi 20 | } 21 | } 22 | ``` 23 | 24 | ----------------------------------------------------------------- 25 | 26 | ## Conseils 27 | 28 | Pour accepter un fichier en entrée, votre requête doit utiliser le type de 29 | contenu `multipart/form-data`, et votre gestionnaire doit décoder le contenu 30 | en fonction. 31 | 32 | On peut consulter ce fichier sous la forme d’un flux en lecture en ajoutant les 33 | réglages suivants à la configuration de notre route : 34 | 35 | ```js 36 | 37 | payload: { 38 | output : 'stream', 39 | parse : true 40 | } 41 | ``` 42 | 43 | Si nous avons envoyé un fichier via le paramètre `file`, alors nous pourrons 44 | y accéder au sein du gestionnaire de la façon suivante : 45 | 46 | ```js 47 | handler: function (request, reply) { 48 | var body = ''; 49 | 50 | request.payload.file.on('data', function (data){ 51 | body += data 52 | }); 53 | 54 | request.payload.file.on('end', function (){ 55 | console.log(body); 56 | }); 57 | } 58 | ``` 59 | 60 | Vous trouverez plus d’information sur le téléversement de fichiers dans la 61 | documentation API pour l’interface de réponse: 62 | 63 | [la doc API en ligne](http://hapijs.com/api#reply-interface) 64 | -------------------------------------------------------------------------------- /exercises/uploads/problem.ja.md: -------------------------------------------------------------------------------- 1 | 以下のパスでファイルのアップロードを受け付けるサーバーを作成します。 2 | 3 | ``` 4 | /upload 5 | ``` 6 | 7 | このエンドポイントは次のキーを受け付けます。 8 | 9 | description : ファイルの説明文となる文字列。内容は何でも構いません。 10 | 11 | file : アップロードするファイル。 12 | 13 | エンドポイントからは、以下のパターンに従ったJSONを返します。 14 | 15 | ```json 16 | { 17 | description : //フォームから送られてきたdescription 18 | file : { 19 | data : //アップロードされたファイルの内容 20 | filename: //アップロードされたファイル名 21 | headers : //hapiから得られるファイルヘッダー 22 | } 23 | } 24 | ``` 25 | 26 | ----------------------------------------------------------------- 27 | ## ヒント 28 | 29 | ファイルを入力として受け付けるためには、リクエストは```multipart/form-data``` 30 | のヘッダーを使用する必要があります。 31 | 32 | 以下をルートのコンフィギュレーションで設定することにより、ファイルを読み取り用 33 | ストリームとして取得することが出来ます。 34 | 35 | ```js 36 | 37 | payload: { 38 | output : 'stream', 39 | parse : true 40 | } 41 | ``` 42 | 43 | ファイルを'`file`'パラメータとしてアップロードした場合、ハンドラ関数中では 44 | 以下のようなコードを使用してファイルにアクセスすることが可能です。 45 | 46 | ```js 47 | handler: function (request, reply) { 48 | var body = ''; 49 | request.payload.file.on('data', function (data){ 50 | 51 | body += data 52 | }); 53 | 54 | request.payload.file.on('end', function (){ 55 | 56 | console.log(body); 57 | }); 58 | } 59 | ``` 60 | 61 | ファイルのアップロードに関しては、hapiのreplyインターフェースのドキュメントに 62 | 詳しい情報があります。 63 | 64 | [API docs](http://hapijs.com/api#reply-interface). 65 | 66 | -------------------------------------------------------------------------------- /exercises/uploads/problem.ko.md: -------------------------------------------------------------------------------- 1 | 서버를 만들어봅시다. 다음 경로로 업로드된 파일을 받아들이는 endpoint를 설정하세요. 2 | 3 | ``` 4 | /upload 5 | ``` 6 | 7 | endpoint는 description과 file이라는 두 개의 키를 필수로 받아야 합니다. `description` 필드에는 문자열이 들어가야 하며 아무 내용이나 적어도 됩니다. `file` 필드는 업로드된 파일이어야 합니다. endpoint는 아래와 같은 형식의 JSON 객체를 반환해야 합니다. 8 | 9 | ```json 10 | { 11 | description : //form으로부터의 description 12 | file : { 13 | data : //업로드된 파일의 내용 14 | filename: //업로드된 파일의 이름 15 | headers : //hapi가 제공하는 파일 헤더 16 | } 17 | } 18 | ``` 19 | 20 | ----------------------------------------------------------------- 21 | ## 힌트 22 | 23 | 파일이 정상적으로 업로드되려면, request에 `multipart/form-data` 헤더를 사용해야 합니다. 24 | 25 | 경로 구성에 다음 코드를 추가하여, 파일을 읽기 가능한 스트림으로 만들 수 있습니다. 26 | 27 | ```js 28 | 29 | payload: { 30 | output : 'stream', 31 | parse : true 32 | } 33 | ``` 34 | 35 | `file`이라는 매개변수와 함께 파일을 업로드 하면 handler 함수 내에서 다음과 같은 코드로 접근할 수 있습니다. 36 | 37 | ```js 38 | handler: function (request, reply) { 39 | var body = ''; 40 | request.payload.file.on('data', function (data){ 41 | 42 | body += data; 43 | }); 44 | 45 | request.payload.file.on('end', function (){ 46 | 47 | console.log(body); 48 | }); 49 | } 50 | ``` 51 | 52 | 파일 업로드에 관한 자세한 정보는 hapi의 reply 인터페이스에 관한 api를 참조하세요. [API docs](http://hapijs.com/api#reply-interface). 53 | 54 | -------------------------------------------------------------------------------- /exercises/uploads/problem.md: -------------------------------------------------------------------------------- 1 | Create a server with an endpoint that accepts an uploaded file to the following 2 | path: 3 | 4 | ``` 5 | /upload 6 | ``` 7 | 8 | The endpoint should accept the following keys: description and file. The 9 | ```description``` field should be a string describing whatever you want, and 10 | ```file``` should be an uploaded file. The endpoint should return a JSON object 11 | that follows the following pattern: 12 | 13 | ```json 14 | { 15 | description : //description from form 16 | file : { 17 | data : //content of file uploaded 18 | filename: //name of file uploaded 19 | headers : //file header provided by hapi 20 | } 21 | } 22 | ``` 23 | 24 | ----------------------------------------------------------------- 25 | ##HINTS 26 | 27 | To accept a file as input, your request should use the ```multipart/form-data``` 28 | header. 29 | 30 | We can get a file as readable stream by adding the following in the route 31 | configuration: 32 | 33 | ```js 34 | 35 | payload: { 36 | output : 'stream', 37 | parse : true 38 | } 39 | ``` 40 | 41 | If we've uploaded the file with the parameter ```file```, then we can access it 42 | in the handler function using following code: 43 | 44 | ```js 45 | handler: function (request, reply) { 46 | var body = ''; 47 | request.payload.file.on('data', function (data){ 48 | 49 | body += data 50 | }); 51 | 52 | request.payload.file.on('end', function (){ 53 | 54 | console.log(body); 55 | }); 56 | } 57 | ``` 58 | 59 | More information about file uploading can be found in the reply interface of the 60 | hapi [API docs](http://hapijs.com/api#reply-interface). 61 | 62 | -------------------------------------------------------------------------------- /exercises/uploads/solution/input.txt: -------------------------------------------------------------------------------- 1 | We like 'makemehapi'. -------------------------------------------------------------------------------- /exercises/uploads/solution/solution.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | try { 3 | const Hapi = require('@hapi/hapi'); 4 | const server = new Hapi.Server({ 5 | host: 'localhost', 6 | port: Number(process.argv[2] || 8080) 7 | }); 8 | 9 | server.route({ 10 | method: 'POST', 11 | path: '/upload', 12 | handler: (request, h) => new Promise((resolve, reject) => { 13 | let body = ''; 14 | 15 | request.payload.file.on('data', (data) => { 16 | body += data; 17 | }); 18 | 19 | request.payload.file.on('end', () => { 20 | const result = { 21 | description: request.payload.description, 22 | file: { 23 | data: body, 24 | filename: request.payload.file.hapi.filename, 25 | headers: request.payload.file.hapi.headers 26 | } 27 | }; 28 | 29 | return resolve(JSON.stringify(result)); 30 | }); 31 | 32 | request.payload.file.on('error', err => reject(err)); 33 | }), 34 | options: { 35 | payload: { 36 | output: 'stream', 37 | parse: true, 38 | multipart: true 39 | } 40 | } 41 | }); 42 | 43 | await server.start(); 44 | } catch (error) { 45 | console.log(error); 46 | } 47 | })(); 48 | -------------------------------------------------------------------------------- /exercises/uploads/solution_fr/input.txt: -------------------------------------------------------------------------------- 1 | On adore « makemehapi ». 2 | -------------------------------------------------------------------------------- /exercises/uploads/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | try { 3 | const Hapi = require('@hapi/hapi'); 4 | const server = new Hapi.Server({ 5 | host: 'localhost', 6 | port: Number(process.argv[2] || 8080) 7 | }); 8 | 9 | server.route({ 10 | method: 'POST', 11 | path: '/upload', 12 | config: { 13 | handler: (request, h) => new Promise((resolve, reject) => { 14 | let body = ''; 15 | 16 | request.payload.file.on('data', (data) => { 17 | body += data; 18 | }); 19 | 20 | request.payload.file.on('end', () => { 21 | const result = { 22 | description: request.payload.description, 23 | file: { 24 | data: body, 25 | filename: request.payload.file.hapi.filename, 26 | headers: request.payload.file.hapi.headers 27 | } 28 | }; 29 | 30 | return resolve(JSON.stringify(result)); 31 | }); 32 | 33 | request.payload.file.on('error', err => reject(err)); 34 | }), 35 | payload: { 36 | output: 'stream', 37 | parse: true, 38 | allow: 'multipart/form-data' 39 | } 40 | } 41 | }); 42 | 43 | await server.start(); 44 | } catch (error) { 45 | console.log(error); 46 | } 47 | })(); 48 | -------------------------------------------------------------------------------- /exercises/validation/exercise.js: -------------------------------------------------------------------------------- 1 | const exercise = require('../../lib/exercise') 2 | 3 | exercise.queryUrl = (port) => `http://localhost:${port}/chickens/foo`; 4 | 5 | module.exports = exercise; 6 | -------------------------------------------------------------------------------- /exercises/validation/problem.fr.md: -------------------------------------------------------------------------------- 1 | La configuration des routes nous offre de nombreuses manières de personnaliser 2 | chaque point d’accès de notre application Hapi. Un de ces aspects concerne 3 | la validation des requêtes. 4 | 5 | La validation peut s’intéresser aux paramètres de chemin, au corps de la requête, 6 | ou à la réponse envoyée. Les objets qui pilotent la validation sont définis au 7 | moyen du framework de validation Joi. 8 | 9 | Créez un serveur qui configure une route exposant un point d’accès pour des 10 | poulets. Spécifiquement : 11 | 12 | ``` 13 | /chickens 14 | ``` 15 | 16 | Au sein de cette route, ajoutez un paramètre de chemin nommé `breed` (race), 17 | lequel dispose d’une validation dédiée dans la configuration de la route. Le 18 | vérificateur s’assurera simplement que l’objet de validation existe dans la 19 | configuration de route pour le paramètre `breed`, sans chercher plus loin. 20 | 21 | ----------------------------------------------------------------- 22 | 23 | ## Conseils 24 | 25 | Le code suivant illustre à quoi peut ressembler une telle configuration : 26 | 27 | ```js 28 | var routeConfig = { 29 | path: '/un/chemin/{avec}/{des}/{parametres}', 30 | method: 'GET', 31 | handler: myHandler, 32 | config: { 33 | validate: { 34 | params: { 35 | with: Joi.string().required(), 36 | parameters: Joi.string().required() 37 | } 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | Toutes les façons de configurer une route sont consultables [ici](https://hapijs.com/api). 44 | 45 | Les informations sur les objets de validation Joi sont consutlables ici : 46 | 47 | {rootdir:/node_modules/joi/README.md} 48 | -------------------------------------------------------------------------------- /exercises/validation/problem.ja.md: -------------------------------------------------------------------------------- 1 | あなたの作るhapiアプリケーションの各エンドポイントに対し、ルート 2 | コンフィギュレーションは様々なカスタマイズ手段を提供します。それらの 3 | 手段の内の一つがバリデーション(検証)です。 4 | 5 | このバリデーションは以下のものが対象となります。 6 | 7 | * パスで渡されるパラメータ 8 | * リクエストのデータ本体 9 | * クライアントに返すレスポンス 10 | 11 | バリデーションオブジェクトは検証フレームワークの'`Joi`'で定義されます。 12 | 13 | 'chikens'というエンドポイントを提供するルートコンフィギュレーションを 14 | 持ったサーバーを作ります。このエンドポイントは具体的には以下です。 15 | 16 | ``` 17 | /chickens 18 | ``` 19 | 20 | ルートコンフィギュレーションに'`breed`'という名前のパスパラメータを 21 | 追加し、'`breed`'に対しそのコンフィギュレーション中でバリデーションの 22 | 設定も行います。このエクササイズでは、'`breed`'がパス中で提供される 23 | 必要がある、ということ設定するのみで、内容に対する検証は行いません。 24 | 25 | ----------------------------------------------------------------- 26 | ## ヒント 27 | 28 | コマンドラインから渡されたポート番号をlistenするサーバーを作ります。 29 | ルートのコンフィギュレーションは以下のようなものになるでしょう。 30 | 31 | ```js 32 | var routeConfig = { 33 | path: '/a/path/{with}/{parameters}', 34 | method: 'GET', 35 | handler: myHandler, 36 | config: { 37 | validate: { 38 | params: { 39 | with: Joi.string().required(), 40 | parameters: Joi.string().required() 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | ルートに関する情報は以下を参照して下さい。 48 | 49 | https://hapijs.com/api 50 | 51 | Joiの情報は以下にあります。 52 | 53 | {rootdir:/node_modules/joi/README.md} 54 | 55 | Joiのインストールは下記のコマンドです。 56 | 57 | ```sh 58 | npm install joi 59 | ``` 60 | -------------------------------------------------------------------------------- /exercises/validation/problem.ko.md: -------------------------------------------------------------------------------- 1 | hapi 앱의 각 endpoint는 route 설정을 통해 다양하게 커스터마이징 할 수 있습니다. 그중 하나가 유효성 검사입니다. 2 | 3 | 유효성 검사는 경로의 매개변수, request, response에서 설정할 수 있습니다. 유효성 검사를 위한 객체는 `Joi`라는 유효성 검사 프레임워크에 정의되어 있습니다. 4 | 5 | `chickens`라는 endpoint를 제공하는 route 설정을 가진 서버를 만들어 보세요. 구체적으로는 다음과 같습니다. 6 | 7 | ``` 8 | /chickens 9 | ``` 10 | 11 | route 설정 내에 `breed`라는 유효성 검사가 추가된 경로 매개변수를 추가하세요. 이번 과제에서는 `breed`가 설정되어 있는지만 확인하며, 유효성 검사의 내용은 확인하지 않습니다. 12 | 13 | ----------------------------------------------------------------- 14 | ## 힌트 15 | 16 | 다음 코드를 사용해서 `8080` 포트를 사용하는 서버를 만드세요. 17 | 18 | ```js 19 | var routeConfig = { 20 | path: '/a/path/{with}/{parameters}', 21 | method: 'GET', 22 | handler: myHandler, 23 | config: { 24 | validate: { 25 | params: { 26 | with: Joi.string().required(), 27 | parameters: Joi.string().required() 28 | } 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | route에 관한 정보는 여기서 확인하세요. 35 | 36 | https://hapijs.com/api 37 | 38 | Joi에 관한 정보는 여기서 확인하세요. 39 | 40 | {rootdir:/node_modules/joi/README.md} 41 | 42 | Joi를 설치하는 명령어는 다음과 같습니다. 43 | 44 | ```sh 45 | npm install joi 46 | ``` 47 | -------------------------------------------------------------------------------- /exercises/validation/problem.md: -------------------------------------------------------------------------------- 1 | Route configuration offers lots of ways to customize each endpoint offered by 2 | your hapi application. One of those ways is through validation. 3 | 4 | Validation can happen in parameters in the path, in inbound payload validation, 5 | and outbound response. Objects for validation are defined in the `Joi` 6 | validation framework. 7 | 8 | Create a server that has a route configuration exposing an endpoint for 9 | chickens. Specifically: 10 | 11 | ``` 12 | /chickens 13 | ``` 14 | 15 | Within the route, add a path parameter named `breed` which has an attached 16 | validation within the route's configuration. The solution will just check that a 17 | Validation object exists within the configuration for `breed`, not any specific 18 | validation. 19 | 20 | ----------------------------------------------------------------- 21 | ##HINTS 22 | 23 | Create a server that listens on port `8080` with the following code: 24 | 25 | ```js 26 | var routeConfig = { 27 | path: '/a/path/{with}/{parameters}', 28 | method: 'GET', 29 | handler: myHandler, 30 | config: { 31 | validate: { 32 | params: { 33 | with: Joi.string().required(), 34 | parameters: Joi.string().required() 35 | } 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | All route information can be found [here](https://hapijs.com/api). 42 | 43 | Joi 44 | 45 | Information can be found here: 46 | 47 | {rootdir:/node_modules/joi/README.md} 48 | 49 | To install joi: 50 | 51 | ```sh 52 | npm install joi 53 | ``` 54 | -------------------------------------------------------------------------------- /exercises/validation/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const Joi = require('@hapi/joi'); 3 | 4 | (async () => { 5 | try { 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: Number(process.argv[2] || 8080) 9 | }); 10 | 11 | server.route({ 12 | method: 'GET', 13 | path: '/chickens/{breed?}', 14 | config: { 15 | handler: (request, h) => `You asked for the chicken ${request.params.breed}`, 16 | validate: { 17 | params: Joi.object({ 18 | breed: Joi.string().required() 19 | }) 20 | } 21 | } 22 | }); 23 | 24 | await server.start(); 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /exercises/validation/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const Joi = require('joi'); 3 | 4 | (async () => { 5 | try { 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: Number(process.argv[2] || 8080) 9 | }); 10 | 11 | server.route({ 12 | method: 'GET', 13 | path: '/chickens/{breed?}', 14 | config: { 15 | handler: (request, h) => `Vous avez demandé les poulets ${request.params.breed}`, 16 | validate: { 17 | params: { 18 | breed: Joi.string().required() 19 | } 20 | } 21 | } 22 | }); 23 | 24 | await server.start(); 25 | } catch (error) { 26 | console.log(error); 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /exercises/validation_using_joi_object/exercise.js: -------------------------------------------------------------------------------- 1 | const { Readable } = require('stream'); 2 | const exercise = require('../../lib/exercise') 3 | 4 | exercise.queryUrl = (port) => `http://localhost:${port}/login`; 5 | 6 | exercise.requestOpts = { method: 'POST' }; 7 | 8 | exercise.handleInput = (request, stream, port) => { 9 | const message = { 10 | isGuest: false, 11 | username: 'hapi', 12 | password: 'makemehapi' 13 | }; 14 | const input = new Readable({ read(size) {} }); 15 | input.pipe(request).pipe(stream); 16 | input.push(JSON.stringify(message)); 17 | input.push(null); 18 | }; 19 | 20 | module.exports = exercise; 21 | -------------------------------------------------------------------------------- /exercises/validation_using_joi_object/problem.fr.md: -------------------------------------------------------------------------------- 1 | En utilisant un objet Joi nous pouvons spécifier des règles de validation 2 | hautement personnalisables pour les chemins, le contenu des requêtes, ou même 3 | les réponses. 4 | 5 | Créez un serveur exposant un point d’accès d’authentification qui répond par 6 | « authentification réussie » quand une requête HTTP POST est envoyée sur 7 | le chemin `/login`. 8 | 9 | Le point d’accès accepte les variables de requête (formulaire) suivantes : 10 | 11 | ```isGuest``` (booléen) 12 | ```username``` (chaîne de caractères) 13 | ```accessToken``` (alphanumérique) 14 | ```password``` (alphanumérique) 15 | 16 | La validation doit exprimer les conditions suivantes : 17 | 18 | 1) Si `isGuest` est `false`, un `username` est requis. 19 | 2) `password` ne peut pas être fourni en même temps que `accessToken`. 20 | 3) Si d’autres paramètres que ceux spécifiés ci-dessus sont fournis, ils sont 21 | acceptés d’office. 22 | 23 | ----------------------------------------------------------------- 24 | 25 | ## Conseils 26 | 27 | Le code suivant illustre une validation complexe : 28 | 29 | ```js 30 | var routeConfig = { 31 | path: '/a/path/', 32 | method: 'POST', 33 | handler: myHandler, 34 | config: { 35 | validate: { 36 | payload: Joi.object({ 37 | username: Joi.string(), 38 | password: Joi.string().alphanum(), 39 | accessToken: Joi.string().alphanum(), 40 | birthyear: Joi.number().integer().min(1900).max(2013), 41 | email: Joi.string().email() 42 | }) 43 | .options({ allowUnknown: true }) 44 | .with('username', 'birthyear') 45 | .without('password', 'accessToken') 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | Toutes les façons de configurer une route sont consultables [ici](https://hapijs.com/api). 52 | 53 | Les informations sur les objets de validation Joi sont consutlables ici : 54 | 55 | {rootdir:/node_modules/joi/README.md} 56 | -------------------------------------------------------------------------------- /exercises/validation_using_joi_object/problem.ja.md: -------------------------------------------------------------------------------- 1 | '`Joi`'オブジェクトを使用することにより、パス、リクエストデータ、レスポンスに 2 | 対し、検証ルールを高度にカスタマイズすることが可能です。 3 | 4 | ログインエンドポイントを提供するサーバーを作成し、'`/login`'へのHTTP `POST` 5 | リクエストに対し、"login successful"を返します。 6 | 7 | このエンドポイントはリクエストデータで以下の変数を受け付けます。 8 | 9 | ```isGuest``` (ブール値) 10 | ```username``` (文字列) 11 | ```accessToken``` (英数字) 12 | ```password``` (英数字) 13 | 14 | バリデーションは以下の条件をチェックします。 15 | 16 | i) ```isGuest``` がfalseの場合, ```username```が必要となる。 17 | ii) ```password```と```accessToken```は同時に使用することは出来ない。 18 | iii) 上記で指定されたパラメータ以外のものが送られて来た場合、検証はPASSとする。 19 | 20 | ----------------------------------------------------------------- 21 | ## ヒント 22 | 23 | コマンドラインから渡されたポート番号をlistenするサーバーを作ります。 24 | ルートのコンフィギュレーションは以下のようなものになるでしょう。 25 | 26 | ```js 27 | 28 | var routeConfig = { 29 | path: '/a/path/', 30 | method: 'POST', 31 | handler: myHandler, 32 | config: { 33 | validate: { 34 | payload: Joi.object({ 35 | username: Joi.string(), 36 | password: Joi.string().alphanum(), 37 | accessToken: Joi.string().alphanum(), 38 | birthyear: Joi.number().integer().min(1900).max(2013), 39 | email: Joi.string().email() 40 | }) 41 | .options({allowUnknown: true}) 42 | .with('username', 'birthyear') 43 | .without('password', 'accessToken') 44 | } 45 | } 46 | } 47 | ``` 48 | 49 | ルートに関する情報は以下を参照して下さい。 50 | 51 | https://hapijs.com/api 52 | 53 | Joiの情報は以下にあります。 54 | 55 | {rootdir:/node_modules/joi/README.md} 56 | -------------------------------------------------------------------------------- /exercises/validation_using_joi_object/problem.ko.md: -------------------------------------------------------------------------------- 1 | `Joi`를 사용하면 경로, request, response에서의 유효성 검사를 고도로 커스터마이징 할 수 있습니다. 2 | 3 | 로그인 endpoint를 제공하고 `/login`에 HTTP `POST` request가 보내졌을 때 "login successful"이라고 응답하는 서버를 만드세요. 4 | 5 | endpoint는 request에서 다음 변수들을 받아들입니다. 6 | 7 | `isGuest` (불리언) 8 | `username` (문자열) 9 | `accessToken` (영숫자) 10 | `password` (영숫자) 11 | 12 | 유효성 검사는 다음 조건으로 구성돼야 합니다. 13 | 14 | i) `isGuest`가 거짓이라면, `username`가 필요하다. 15 | ii) `password`는 `accessToken`과 동시에 사용할 수 없다. 16 | iii) 위의 기재된 매개 변수 이외의 것들이 보내지면 유효성 검사를 통과시킨다. 17 | 18 | 유효성 검사가 성공적이라면, handler는 `login successful`라는 문자열을 반환해야 한다. 19 | 20 | ----------------------------------------------------------------- 21 | ## 힌트 22 | 23 | `8080` 포트를 사용하는 서버를 만드세요. route 설정을 다음과 같습니다. 24 | 25 | ```js 26 | 27 | var routeConfig = { 28 | path: '/a/path/', 29 | method: 'POST', 30 | handler: myHandler, 31 | config: { 32 | validate: { 33 | payload: Joi.object({ 34 | username: Joi.string(), 35 | password: Joi.string().alphanum(), 36 | accessToken: Joi.string().alphanum(), 37 | birthyear: Joi.number().integer().min(1900).max(2013), 38 | email: Joi.string().email() 39 | }) 40 | .options({allowUnknown: true}) 41 | .with('username', 'birthyear') 42 | .without('password', 'accessToken') 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | route에 관한 자세한 정보는 다음을 참조하세요. 49 | 50 | https://hapijs.com/api 51 | 52 | Joi에 관한 자세한 정보는 다음을 참조하세요. 53 | 54 | {rootdir:/node_modules/joi/README.md} 55 | -------------------------------------------------------------------------------- /exercises/validation_using_joi_object/problem.md: -------------------------------------------------------------------------------- 1 | By using a `Joi` object we can specify highly customizable validation rules in 2 | paths, request payloads, and responses. 3 | 4 | Create a server exposing a login endpoint and reply with "login successful" when 5 | an HTTP `POST` request is sent to `/login`. 6 | 7 | The endpoint will accept following payload variables: 8 | 9 | ```isGuest``` (boolean) 10 | ```username``` (string) 11 | ```accessToken``` (alphanumeric) 12 | ```password``` (alphanumeric) 13 | 14 | Validation should consist of following conditions: 15 | 16 | i) if ```isGuest``` is false, a ```username``` is required. 17 | ii) ```password``` cannot appear together with ```accessToken```. 18 | iii) if any other parameters than specified above are sent, they should pass the validation. 19 | 20 | If the validation is successful, the handler must return a text of `login successful` 21 | 22 | ----------------------------------------------------------------- 23 | ##HINTS 24 | 25 | Create a server that listens on port `8080` with the following code: 26 | 27 | ```js 28 | 29 | var routeConfig = { 30 | path: '/a/path/', 31 | method: 'POST', 32 | handler: myHandler, 33 | config: { 34 | validate: { 35 | payload: Joi.object({ 36 | username: Joi.string(), 37 | password: Joi.string().alphanum(), 38 | accessToken: Joi.string().alphanum(), 39 | birthyear: Joi.number().integer().min(1900).max(2013), 40 | email: Joi.string().email() 41 | }) 42 | .options({allowUnknown: true}) 43 | .with('username', 'birthyear') 44 | .without('password', 'accessToken') 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | All route information can be found [here](https://hapijs.com/api). 51 | 52 | Joi information can be found here: 53 | 54 | {rootdir:/node_modules/joi/README.md} 55 | -------------------------------------------------------------------------------- /exercises/validation_using_joi_object/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const Joi = require('@hapi/joi'); 3 | 4 | (async () => { 5 | try { 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: Number(process.argv[2] || 8080) 9 | }); 10 | 11 | server.route({ 12 | method: 'POST', 13 | path: '/login', 14 | config: { 15 | handler: (request, h) => 'login successful', 16 | validate: { 17 | payload: Joi.object({ 18 | isGuest: Joi.boolean().required(), 19 | username: Joi.string().when('isGuest', { is: false, then: Joi.required() }), 20 | password: Joi.string().alphanum(), 21 | accessToken: Joi.string().alphanum() 22 | }).options({ allowUnknown: true }).without('password', 'accessToken') 23 | } 24 | } 25 | }); 26 | 27 | await server.start(); 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | })(); 32 | -------------------------------------------------------------------------------- /exercises/validation_using_joi_object/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | const Hapi = require('@hapi/hapi'); 2 | const Joi = require('@hapi/joi'); 3 | 4 | (async () => { 5 | try { 6 | const server = Hapi.Server({ 7 | host: 'localhost', 8 | port: Number(process.argv[2] || 8080) 9 | }); 10 | 11 | server.route({ 12 | method: 'POST', 13 | path: '/login', 14 | config: { 15 | handler: (request, h) => 'authentification réussie', 16 | validate: { 17 | payload: Joi.object({ 18 | isGuest: Joi.boolean().required(), 19 | username: Joi.string().when('isGuest', { is: false, then: Joi.required() }), 20 | password: Joi.string().alphanum(), 21 | accessToken: Joi.string().alphanum() 22 | }).options({ allowUnknown: true }).without('password', 'accessToken') 23 | } 24 | } 25 | }); 26 | 27 | await server.start(); 28 | } catch (error) { 29 | console.log(error); 30 | } 31 | })(); 32 | -------------------------------------------------------------------------------- /exercises/views/exercise.js: -------------------------------------------------------------------------------- 1 | const exercise = require('../../lib/exercise') 2 | 3 | exercise.queryUrl = (port) => `http://localhost:${port}/?name=${exercise.__('name_param')}`; 4 | 5 | module.exports = exercise; 6 | -------------------------------------------------------------------------------- /exercises/views/problem.fr.md: -------------------------------------------------------------------------------- 1 | Créez un serveur qui répond aux requêtes sur `/?name=Vues` à l’aide d’un 2 | gabarit (*template*) stocké dans `templates/index.html`, qui produira le HTML 3 | suivant : 4 | 5 | ```html 6 | 7 | Bonjour Vues 8 | 9 | Bonjour Vues 10 | 11 | 12 | ``` 13 | 14 | ----------------------------------------------------------------- 15 | 16 | ## Conseils 17 | 18 | La propriété `view` permet de définir un template à utiliser pour générer la 19 | réponse. 20 | 21 | ```js 22 | handler: { 23 | view: "index.html" 24 | } 25 | ``` 26 | 27 | La méthode `server.views()` configure quant à elle la gestion des templates pour 28 | notre serveur. Elle reçoit en argument un objet de configuration qui permet 29 | d’associer divers moteurs à des extensions spécifiques de fichiers. Cet objet 30 | peut également spécifier le dossier des templates. 31 | 32 | ```js 33 | server.views({ 34 | engines: { 35 | html: require('handlebars') 36 | }, 37 | path: Path.join(__dirname, 'templates') 38 | }); 39 | ``` 40 | 41 | Dans cet exercice, nous utiliserons Handlebars. Pour l’installer : 42 | 43 | ```sh 44 | npm install handlebars 45 | ``` 46 | 47 | Avec les templates Handlebars, vous pouvez injecter une variable directement 48 | dans le HTML en l’enrobant de double accolades, par exemple `{{foo}}`. 49 | 50 | Le template reçoit des informations issues de la requête. Par exemple, les 51 | paramètres de la *query string* présente dans l’URL lui sont passés via l’objet 52 | `query`. Ces paramètres peuvent alors être utilisés par le template. Ces 53 | paramètres sont automatiquement analysés et n’ont pas besoin d’une déclaration 54 | explicite dans le `path` de la route. 55 | 56 | ```html 57 |
{{query.paramName}}
58 | ``` 59 | -------------------------------------------------------------------------------- /exercises/views/problem.ja.md: -------------------------------------------------------------------------------- 1 | '`/?name=Handling`'というリクエストに対し、テンプレート(`templates/index.html`)を 2 | 使ってレスポンスするサーバーを作ります。このテンプレートは以下のHTMLを出力します。 3 | 4 | ```html 5 | 6 | Hello Handling 7 | 8 | Hello Handling 9 | 10 | 11 | ``` 12 | 13 | ----------------------------------------------------------------- 14 | ## ヒント 15 | 16 | このエクササイズでは、`@hapi/vision`モジュールが必要となります。これはhapiのプラグインで、 17 | テンプレートを処理するためのものです。このモジュールを使用するためには、下記のように 18 | コード中でプラグインを登録する必要があります。 19 | 20 | ```js 21 | var Vision = require('@hapi/vision'); 22 | 23 | await server.register(Vision); 24 | ``` 25 | 26 | ハンドラのviewキーを使用し、レスポンスの生成に使用するテンプレートを指定します。 27 | 28 | ```js 29 | handler: { 30 | view: "index.html" 31 | } 32 | ``` 33 | 34 | サーバーメソッド'`server.views()`'を使い、サーバー中にあるテンプレートの 35 | 設定を行うことが出来ます。このメソッドは設定オブジェクト(configurationオブジェクト) 36 | を引数にとります。このオブジェクトで以下を設定することが出来ます。 37 | 38 | * 各ファイル拡張子に対する、テンプレートエンジン 39 | * テンプレートファイルのあるディレクトリへのパス 40 | 41 | ```js 42 | server.views({ 43 | engines: { 44 | html: require('handlebars') 45 | }, 46 | path: Path.join(__dirname, 'templates') 47 | }); 48 | ``` 49 | 50 | このエクササイズではHandlebarsを使用します。以下のコマンドでHanlebarsを 51 | インストールすることが出来ます。 52 | 53 | ```sh 54 | npm install handlebars 55 | ``` 56 | 57 | Handlebarsテンプレートを使用することにより、HTML中に変数を直接書くことが 58 | 出来ます。変数は2重の中括弧で囲む形式で記述します。(例: `{{foo}}`) 59 | 60 | テンプレートはリクエストから情報を得ます。例えばURLを通じて渡される 61 | クエリパラメータです。このクエリパラメータは`query`オブジェクトとなり、 62 | テンプレート中で利用可能となります。クエリパラメータは自動的に解釈・処理 63 | されます。ルートの'`path`'で宣言は行いません。 64 | 65 | 66 | ```html 67 |
{{query.paramName}}
68 | ``` 69 | -------------------------------------------------------------------------------- /exercises/views/problem.ko.md: -------------------------------------------------------------------------------- 1 | `/?name=Handling`이라는 request에 `templates/index.html`에 위치한 템플릿을 사용해서 응답하는 서버를 만드세요. 템플릿이 출력하는 HTML은 다음과 같습니다. 2 | 3 | ```html 4 | 5 | Hello Handling 6 | 7 | Hello Handling 8 | 9 | 10 | ``` 11 | 12 | ----------------------------------------------------------------- 13 | ## 힌트 14 | 15 | 이번 과제는 `@hapi/vision`이 필요하므로 설치해주세요. 이 모듈은 템플릿을 렌더링하는 hapi 플러그인 입니다. 다음과 같이 플러그인을 등록하세요. 16 | 17 | ```js 18 | var Vision = require('@hapi/vision'); 19 | 20 | await server.register(Vision); 21 | ``` 22 | 23 | `view` 키는 response를 만드는데 사용되는 템플릿을 지정하는 데 사용됩니다. 24 | 25 | ```js 26 | handler: { 27 | view: "index.html" 28 | } 29 | ``` 30 | 31 | `server.views()`는 서버에서 사용되는 템플릿을 구성하기 위한 서버 메서드 입니다. 32 | 이 메서드는 설정 객체를 받아서 파일 확장자에 따라 다른 엔진을 설정할 수 있게 해줍니다. 또한 템플릿의 디렉터리 경로를 설정할 수도 있습니다. 33 | 34 | ```js 35 | server.views({ 36 | engines: { 37 | html: require('handlebars') 38 | }, 39 | path: Path.join(__dirname, 'templates') 40 | }); 41 | ``` 42 | 43 | 이번 과제에서는 Handlebars를 사용합니다. 다음 명령어로 설치하세요. 44 | 45 | ```sh 46 | npm install handlebars 47 | ``` 48 | 49 | Handlebars 템플릿을 사용하면, HTML 내에서 변수를 렌더링할 수 있습니다. 사용법은 다음과 같습니다. `{{foo}}`. 변수를 중괄호로 두 번 묶어주세요. 50 | 51 | 템플릿은 request로부터 몇 가지 정보를 받습니다. 예를 들어 URL을 통해서 전달되는 쿼리 매개변수가 있습니다. 이 매개변수는 `query` 객체가 되며, 템플릿 내에서 사용할 수 있습니다. 쿼리 매개변수는 자동으로 해석되며, route의 `path`에 선언되지 않습니다. 52 | 53 | ```html 54 |
{{query.paramName}}
55 | ``` 56 | -------------------------------------------------------------------------------- /exercises/views/problem.md: -------------------------------------------------------------------------------- 1 | Create a server which responds to requests to `/?name=Handling` using a template 2 | located at `templates/index.html` which outputs the following HTML: 3 | 4 | ```html 5 | 6 | Hello Handling 7 | 8 | Hello Handling 9 | 10 | 11 | ``` 12 | 13 | ----------------------------------------------------------------- 14 | ##HINTS 15 | 16 | This exercise requires you to install the `@hapi/vision` module, which is a hapi 17 | plugin for rendering templates. You'll need to register the plugin in your code 18 | in order to render your templates: 19 | 20 | ```js 21 | var Vision = require('@hapi/vision'); 22 | 23 | await server.register(Vision); 24 | ``` 25 | 26 | The `view` key can be used to define the template to be used to generate the 27 | response. 28 | 29 | ```js 30 | handler: { 31 | view: "index.html" 32 | } 33 | ``` 34 | 35 | `server.views()` is the server method that we use to configure the templates 36 | used on our server. This method receives a configuration object in which we can 37 | set different engines based on file extension. This object can also set a 38 | directory path for your templates. 39 | 40 | ```js 41 | server.views({ 42 | engines: { 43 | html: require('handlebars') 44 | }, 45 | path: Path.join(__dirname, 'templates') 46 | }); 47 | ``` 48 | 49 | In this exercise, we'll be using Handlebars. To install handlebars: 50 | 51 | ```sh 52 | npm install handlebars 53 | ``` 54 | 55 | With Handlebars templates, you can render a variable directly in HTML by 56 | surrounding the variable with curly braces, e.g. `{{foo}}`. 57 | 58 | The template receives some information from the request. For example, the query 59 | parameters that were passed in via the URL are available in the `query` object. 60 | These parameters can then be used in the template. Query params get 61 | automatically parsed and aren't declared in the route `path`. 62 | 63 | ```html 64 |
{{query.paramName}}
65 | ``` 66 | -------------------------------------------------------------------------------- /exercises/views/solution/solution.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Hapi = require('@hapi/hapi'); 3 | const Vision = require('@hapi/vision'); 4 | const Handlebars = require('handlebars'); 5 | 6 | (async () => { 7 | try { 8 | const serverPort = process.argv[2] || 8080; 9 | const server = Hapi.Server({ 10 | host: 'localhost', 11 | port: serverPort 12 | }); 13 | 14 | await server.register(Vision); 15 | 16 | server.views({ 17 | engines: { 18 | html: Handlebars 19 | }, 20 | path: Path.join(__dirname, 'templates') 21 | }); 22 | 23 | server.route({ 24 | path: '/', 25 | method: 'GET', 26 | handler: { 27 | view: 'index.html' 28 | } 29 | }); 30 | 31 | await server.start(); 32 | 33 | console.log(`Server running at: ${server.info.uri}`); 34 | } catch (error) { 35 | console.log(error); 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /exercises/views/solution/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | Hello {{query.name}} 3 | 4 | Hello {{query.name}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /exercises/views/solution_fr/solution.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const Hapi = require('@hapi/hapi'); 3 | const Vision = require('@hapi/vision'); 4 | const Handlebars = require('handlebars'); 5 | 6 | (async () => { 7 | try { 8 | const serverPort = process.argv[2] || 8080; 9 | const server = Hapi.Server({ 10 | host: 'localhost', 11 | port: serverPort 12 | }); 13 | 14 | await server.register(Vision); 15 | 16 | server.views({ 17 | engines: { 18 | html: Handlebars 19 | }, 20 | path: Path.join(__dirname, 'templates') 21 | }); 22 | 23 | server.route({ 24 | path: '/', 25 | method: 'GET', 26 | handler: { 27 | view: 'index.html' 28 | } 29 | }); 30 | 31 | await server.start(); 32 | 33 | console.log(`Server running at: ${server.info.uri}`); 34 | } catch (error) { 35 | console.log(error); 36 | } 37 | })(); 38 | -------------------------------------------------------------------------------- /exercises/views/solution_fr/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | Bonjour {{query.name}} 3 | 4 | Bonjour {{query.name}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /i18n/br.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "REPOLSE BEM COM HAPI", 3 | "subtitle": "\u001b[23mSelecione um exercício a aperte \u001b[3mEnter\u001b[23m para começar", 4 | "menu": { 5 | "credits": "CRÉDITOS" 6 | }, 7 | "common": { 8 | "exercise": { 9 | "fail": { 10 | "cannot_connect": "Erro ao conectar em http://localhost:{{port}}: {{code}}", 11 | "page_not_found": "Página não encontrada em {{url}}", 12 | "wrong_status_code": "Código de status {{statusCode}} retornado da url {{url}}, esperado {{expected}}." 13 | }, 14 | "pass": { 15 | "status_code": "Código de status é correto" 16 | } 17 | } 18 | }, 19 | "exercises": { 20 | "VIEWS": { 21 | "name_param": "o manuseamento" 22 | }, 23 | "PROXIES": { 24 | "greeting": "Olá Proxies" 25 | }, 26 | "HELPING": { 27 | "name_param": "Mãos que Ajudam" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /i18n/credits/br.txt: -------------------------------------------------------------------------------- 1 | {yellow}{bold}makemehapi é trazido a você pelas seguintes pessoas:{/bold}{/yellow} 2 | {bold}Nome Usuário GitHub{/bold} 3 | -------------------------------------------------------------------------------- /i18n/credits/en.txt: -------------------------------------------------------------------------------- 1 | {yellow}{bold}makemehapi is brought to you by the following persons:{/bold}{/yellow} 2 | {bold}Name GitHub Username{/bold} 3 | -------------------------------------------------------------------------------- /i18n/credits/fr.txt: -------------------------------------------------------------------------------- 1 | {yellow}{bold}makemehapi vous est proposé par les personnes suivantes :{/bold}{/yellow} 2 | {bold}Nom Compte GitHub{/bold} 3 | -------------------------------------------------------------------------------- /i18n/credits/ja.txt: -------------------------------------------------------------------------------- 1 | {yellow}{bold}makemehapi は以下のメンバーがお届けいたしました。:{/bold}{/yellow} 2 | {bold}Name GitHub Username{/bold} 3 | -------------------------------------------------------------------------------- /i18n/credits/ko.txt: -------------------------------------------------------------------------------- 1 | {yellow}{bold}makemehapi는 아래 구성원들의 공헌으로 만들어졌습니다.{/bold}{/yellow} 2 | {bold}Name GitHub Username{/bold} 3 | -------------------------------------------------------------------------------- /i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "REST WELL WITH HAPI", 3 | "subtitle": "\u001b[23mSelect an exercise and hit \u001b[3mEnter\u001b[23m to begin", 4 | "menu": { 5 | "credits": "CREDITS" 6 | }, 7 | "common": { 8 | "exercise": { 9 | "fail": { 10 | "cannot_connect": "Error connecting to http://localhost:{{port}}: {{code}}", 11 | "page_not_found": "Page not found at {{url}}", 12 | "wrong_status_code": "Status code {{statusCode}} returned from url {{url}}, expected {{expected}}." 13 | }, 14 | "pass": { 15 | "status_code": "Status code returned is correct" 16 | } 17 | } 18 | }, 19 | "exercises": { 20 | "VIEWS": { 21 | "name_param": "the handling" 22 | }, 23 | "PROXIES": { 24 | "greeting": "Hello Proxies" 25 | }, 26 | "HELPING": { 27 | "name_param": "Helping Hands" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "HAPI S’OCCUPE DU REST", 3 | "subtitle" : "\u001b[23mSélectionnez un exercice et tapez \u001b[3mEnter\u001b[23m pour démarrer", 4 | "menu": { 5 | "credits": "CRÉDITS" 6 | }, 7 | "common": { 8 | "exercise": { 9 | "fail": { 10 | "cannot_connect": "Connexion impossible à http://localhost:{{port}}: {{code}}", 11 | "page_not_found": "Page introuvable à l’adresse {{url}}", 12 | "wrong_status_code": "Code d’état {{statusCode}} renvoyé par l’URL {{url}}, on attendait {{expected}}." 13 | }, 14 | "pass": { 15 | "status_code": "le code d'état est correct" 16 | } 17 | } 18 | }, 19 | "exercise": { 20 | "HELLO_HAPI": "BONJOUR HAPI", 21 | "ROUTES": "ROUTES", 22 | "HANDLING": "GESTION DE REQUÊTE", 23 | "DIRECTORIES": "RÉPERTOIRES", 24 | "VIEWS": "VUES", 25 | "PROXIES": "PROXIES", 26 | "HELPING": "ASSISTANTS", 27 | "STREAMS": "FLUX", 28 | "VALIDATION": "VALIDATION", 29 | "VALIDATION USING JOI OBJECT": "VALIDATION AVEC UN OBJET JOI", 30 | "UPLOADS": "TÉLÉVERSEMENTS", 31 | "COOKIES": "COOKIES", 32 | "AUTH": "AUTHENTICATION" 33 | }, 34 | "exercises": { 35 | "VIEWS": { 36 | "name_param": "les vues" 37 | }, 38 | "PROXIES": { 39 | "greeting": "Bonjour les proxies" 40 | }, 41 | "HELPING": { 42 | "name_param": "les petites mains" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /i18n/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "HAPIで楽をしよう", 3 | "subtitle": "\u001b[23m課題を選択し、\u001b[3mEnter\u001b[23mで始めて下さい", 4 | "menu": { 5 | "credits": "クレジット" 6 | }, 7 | "common": { 8 | "exercise": { 9 | "fail": { 10 | "cannot_connect": "エラー。サーバーに接続出来ませんでした。URL-> http://localhost:{{port}}: {{url}}", 11 | "page_not_found": "ページが見つかりません。URL-> {{url}}", 12 | "wrong_status_code": "ステータスコード {{statusCode}} が返されました。URL-> {{url}} (期待値は {{expected}})" 13 | }, 14 | "pass": { 15 | "status_code": "ステータスコードは正しい" 16 | } 17 | } 18 | }, 19 | "exercise": { 20 | "HELLO_HAPI": "HELLO HAPI", 21 | "ROUTES": "ルート", 22 | "HANDLING": "ハンドリング", 23 | "DIRECTORIES": "ディレクトリ", 24 | "VIEWS": "ビュー", 25 | "PROXIES": "プロキシ", 26 | "HELPING": "ヘルパー", 27 | "STREAMS": "ストリーム", 28 | "VALIDATION": "バリデーション - 1", 29 | "VALIDATION USING JOI OBJECT": "バリデーション - 2", 30 | "UPLOADS": "アップロード", 31 | "COOKIES": "クッキー", 32 | "AUTH": "認証" 33 | }, 34 | "exercises": { 35 | "VIEWS": { 36 | "name_param": "the handling" 37 | }, 38 | "PROXIES": { 39 | "greeting": "Hello Proxies" 40 | }, 41 | "HELPING": { 42 | "name_param": "Helping Hands" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /i18n/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "HAPI의 은혜를 누려보자", 3 | "subtitle": "\u001b[23m과제를 선택한 다음 \u001b[3m엔터\u001b[23m로 시작하세요", 4 | "menu": { 5 | "credits": "만든 사람들" 6 | }, 7 | "common": { 8 | "exercise": { 9 | "fail": { 10 | "cannot_connect": "다음 주소로의 접속에 실패하였습니다. http://localhost:{{port}}: {{code}}", 11 | "page_not_found": "다음 페이지를 찾을 수 없습니다. {{url}}", 12 | "wrong_status_code": "HTTP 상태 코드 {{statusCode}}가 다음 URL로부터 응답하였습니다. {{url}}, 기댓값은 {{expected}}입니다." 13 | }, 14 | "pass": { 15 | "status_code": "상태 코드가 정확" 16 | } 17 | } 18 | }, 19 | "exercises": { 20 | "VIEWS": { 21 | "name_param": "the handling" 22 | }, 23 | "PROXIES": { 24 | "greeting": "Hello Proxies" 25 | }, 26 | "HELPING": { 27 | "name_param": "Helping Hands" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /images/makemehapi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccarruitero/makemehapi/dc079a1fed9d0555d6ef02e86d19cdb0efae6aa0/images/makemehapi.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Workshopper = require('workshopper-adventure'); 4 | const path = require('path'); 5 | const credits = require('./credits'); 6 | 7 | const name = 'makemehapi'; 8 | 9 | function fpath (f) { 10 | return path.join(__dirname, f); 11 | } 12 | 13 | const workshopper = Workshopper({ 14 | name, 15 | exerciseDir: fpath('./exercises/'), 16 | appDir: __dirname, 17 | languages: ['en', 'fr', 'ja', 'ko'], 18 | helpFile: fpath('./i18n/help/{lang}.txt'), 19 | menuItems: [{ 20 | name: 'credits', 21 | handler: credits 22 | }] 23 | }); 24 | 25 | workshopper.addAll(require('./exercises/menu.json')); 26 | 27 | module.exports = workshopper; 28 | -------------------------------------------------------------------------------- /lib/exercise.js: -------------------------------------------------------------------------------- 1 | const through2 = require('through2'); 2 | const hyperquest = require('hyperquest'); 3 | const bl = require('bl'); 4 | let exercise = require('workshopper-exercise')(); 5 | const filecheck = require('workshopper-exercise/filecheck'); 6 | const execute = require('workshopper-exercise/execute'); 7 | const comparestdout = require('workshopper-exercise/comparestdout'); 8 | const verifyStatusProcessor = require('./verifyStatusProcessor'); 9 | const { rndport } = require('./utils'); 10 | 11 | exercise.longCompareOutput = true; 12 | 13 | exercise = filecheck(exercise); 14 | 15 | exercise = execute(exercise); 16 | 17 | exercise.addSetup(function (mode, callback) { 18 | this.submissionPort = rndport(); 19 | this.solutionPort = this.submissionPort + 1; 20 | 21 | this.submissionArgs = [this.submissionPort]; 22 | this.solutionArgs = [this.solutionPort]; 23 | 24 | this.submissionResult = {}; 25 | 26 | process.nextTick(callback); 27 | }); 28 | 29 | exercise.addProcessor(function (mode, callback) { 30 | this.submissionStdout.pipe(process.stdout); 31 | 32 | // replace stdout with our own streams 33 | this.submissionStdout = through2(); 34 | if (mode === 'verify') { 35 | this.solutionStdout = through2(); 36 | } 37 | 38 | setTimeout(query.bind(this, mode), 2000); 39 | 40 | process.nextTick(() => { 41 | callback(null, true); 42 | }); 43 | }); 44 | 45 | exercise = comparestdout(exercise); 46 | 47 | exercise = verifyStatusProcessor(exercise); 48 | 49 | function killChild (exercise) { 50 | [ exercise.submissionChild, exercise.solutionChild ].forEach(function (child) { 51 | if (child && typeof child.kill == 'function') child.kill() 52 | }) 53 | } 54 | 55 | // delayed for 2000ms to wait for servers to start so we can start 56 | // playing with them 57 | function query (mode) { 58 | const exercise = this; 59 | 60 | function connect (port, stream, result = null) { 61 | const url = exercise.queryUrl(port); 62 | 63 | function error (err) { 64 | const msg = exercise.__('fail.cannot_connect', { port, code: err.code }); 65 | exercise.emit('fail', msg); 66 | killChild(exercise); 67 | } 68 | 69 | const opts = Object.assign({ method: 'GET' }, exercise.requestOpts || {}); 70 | 71 | const request = hyperquest(url, opts) 72 | .on('error', error) 73 | .on('response', (res) => { 74 | if (result) { 75 | result.response = res; 76 | result.url = url; 77 | } 78 | }); 79 | 80 | const handleInput = exercise.handleInput; 81 | if (handleInput && (typeof handleInput === 'function')) { 82 | handleInput(request, stream, port); 83 | } else { 84 | request.pipe(stream); 85 | } 86 | } 87 | 88 | connect(this.submissionPort, this.submissionStdout, this.submissionResult); 89 | 90 | if (mode === 'verify') { 91 | connect(this.solutionPort, this.solutionStdout); 92 | } 93 | } 94 | 95 | module.exports = exercise; 96 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | const rndport = () => 1024 + Math.floor(Math.random() * 64511); 2 | 3 | module.exports = { 4 | rndport 5 | }; 6 | -------------------------------------------------------------------------------- /lib/verifyStatusProcessor.js: -------------------------------------------------------------------------------- 1 | const verifyStatusProcessor = (exercise) => { 2 | exercise.addVerifyProcessor(function (callback) { 3 | const { response, url } = this.submissionResult; 4 | const { statusCode } = response; 5 | const expected = 200; 6 | let pass = false; 7 | 8 | if (statusCode === expected) { 9 | exercise.emit('pass', exercise.__('pass.status_code')); 10 | pass = true; 11 | } else { 12 | const msg = exercise.__('fail.wrong_status_code', { statusCode, url, expected }); 13 | exercise.emit('fail', msg); 14 | } 15 | 16 | callback(null, pass); 17 | }); 18 | return exercise; 19 | } 20 | 21 | module.exports = verifyStatusProcessor; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "makemehapi", 3 | "version": "7.1.4", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/ccarruitero/makemehapi" 7 | }, 8 | "license": "BSD", 9 | "bin": { 10 | "makemehapi": "./bin/makemehapi" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.32" 14 | }, 15 | "description": "Self guided workshops to teach you about hapi.", 16 | "main": "index.js", 17 | "scripts": { 18 | "start": "node ./bin/makemehapi.js", 19 | "lint": "eslint *.js exercises/**/**/*.js i18n/**/*.json", 20 | "test": "npm run lint && workshopper-adventure-test", 21 | "release": "standard-version" 22 | }, 23 | "dependencies": { 24 | "@hapi/basic": "^7.0.0", 25 | "@hapi/boom": "^10.0.0", 26 | "@hapi/h2o2": "^10.0.0", 27 | "@hapi/hapi": "^21.0.0", 28 | "@hapi/inert": "^7.0.0", 29 | "@hapi/joi": "^17.1.1", 30 | "@hapi/vision": "^7.0.0", 31 | "bl": "^5.0.0", 32 | "colors-tmpl": "^1.0.0", 33 | "combined-stream": "^1.0.8", 34 | "form-data": "^4.0.0", 35 | "handlebars": "^4.7.2", 36 | "hyperquest": "^2.1.3", 37 | "rot13-transform": "^1.0.0", 38 | "through2": "^4.0.2", 39 | "workshopper-adventure": "^7.0.0", 40 | "workshopper-exercise": "^3.0.1", 41 | "workshopper-wrappedexec": "^0.1.3" 42 | }, 43 | "keywords": [ 44 | "hapijs", 45 | "hapi" 46 | ], 47 | "preferGlobal": true, 48 | "devDependencies": { 49 | "eslint": "^8.27.0", 50 | "eslint-config-standard": "^17.0.0", 51 | "eslint-plugin-import": "^2.18.0", 52 | "eslint-plugin-json": "^3.0.0", 53 | "eslint-plugin-node": "^11.0.0", 54 | "eslint-plugin-promise": "^6.1.1", 55 | "eslint-plugin-standard": "^5.0.0", 56 | "standard": "^17.0.0", 57 | "standard-version": "^9.0.0", 58 | "workshopper-adventure-test": "^1.2.0" 59 | } 60 | } 61 | --------------------------------------------------------------------------------