Быстрый старт для разработки Koa2

koa

Начало работы с Коа2

Создать Коа2

Сначала мы создаем каталог проекта с именем koa2 и открываем его с помощью VS Code. Затем мы создаем app.js и вводим следующий код:

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');

// 创建一个Koa对象表示web app本身:
const app = new Koa();

// 对于任何请求,app将调用该异步函数处理请求:
app.use(async (ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
});

// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');

Для каждого http-запроса koa будет вызывать асинхронную функцию, которую мы передаем для обработки. Например:

async (ctx, next) => {
    await next();
    // 设置response的Content-Type:
    ctx.response.type = 'text/html';
    // 设置response的内容:
    ctx.response.body = '<h1>Hello, koa2!</h1>';
}

Среди них параметр ctx — это переменная, переданная koa, которая инкапсулирует запрос и ответ, через которую мы можем получить доступ к запросу и ответу, next — это следующая асинхронная функция, переданная koa для обработки. Итак, как начать коа? Прежде всего, вам нужно установить koa, вы можете установить его напрямую с помощью npm, вы можете обратиться кИнформация с официального сайта Коа.

在这里插入图片描述
Затем создайте новый package.json в каталоге проекта koa прямо сейчас. Этот файл используется для управления пакетами зависимостей, необходимыми для запуска проекта koa. Обратите внимание на номер версии koa, когда полагаетесь на него. Например:

{
    "name": "hello-koa2",
    "version": "1.0.0",
    "description": "Hello Koa 2 example with async",
    "main": "app.js",
    "scripts": {
        "start": "node app.js"
    },
    "keywords": [
        "koa",
        "async"
    ],
    "author": "xzh",
    "license": "Apache-2.0",
    "repository": {
        "type": "git",
        "url": "https://github.com/michaelliao/learn-javascript.git"
    },
    "dependencies": {
        "koa": "2.7.0"
    }
}

Среди них зависимости — это пакеты и номера версий, от которых зависит наш проект, и нам нужно обратить внимание на соответствующие номера версий. Другие поля используются для описания информации о проекте и могут быть заполнены произвольно. Затем выполните npm install в каталоге koa, чтобы установить необходимые зависимости для проекта. После завершения установки структура каталогов проекта выглядит следующим образом:

hello-koa/
|
+- .vscode/
|  |
|  +- launch.json        //VSCode 配置文件
|
+- app.js              //使用koa的js
|
+- package.json          //项目配置文件
|
+- node_modules/     //npm安装的所有依赖包

Затем запустите проект с помощью npm start, и вы увидите эффект.

在这里插入图片描述
Конечно, вы также можете напрямую использовать командный узел app.js для запуска программы в командной строке, и имя, наконец, выполнит команду, соответствующую start в файле package.json:

"scripts": {
    "start": "node app.js"
}

Далее давайте более подробно рассмотрим логику выполнения koa.Основной код выглядит следующим образом:

app.use(async (ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
});

Каждый раз, когда получен HTTP-запрос, koa будет вызывать асинхронную функцию, зарегистрированную через app.use(), и передавать параметры ctx и next. Итак, зачем вам нужно вызывать await next()? Причина в том, что koa объединяет множество асинхронных функций в цепочку обработки, каждая асинхронная функция может делать что-то свое, а затем использовать await next() для вызова следующей асинхронной функции, здесь мы вызываем промежуточное ПО каждой асинхронной функции.

Например, вы можете использовать следующие три компонента промежуточного слоя для формирования цепочки обработки, последовательной печати журналов, записи времени обработки и вывода HTML.

// 导入koa,和koa 1.x不同,在koa2中,我们导入的是一个class,因此用大写的Koa表示:
const Koa = require('koa');

// 创建一个Koa对象表示web app本身:
const app = new Koa();

app.use(async (ctx, next) => {
    console.log(`${ctx.request.method} ${ctx.request.url}`); // 打印URL
    await next(); // 调用下一个middleware
});

app.use(async (ctx, next) => {
    const start = new Date().getTime(); // 当前时间
    await next(); // 调用下一个middleware
    const ms = new Date().getTime() - start; // 耗费时间
    console.log(`Time: ${ms}ms`); // 打印耗费时间
});

app.use(async (ctx, next) => {
    await next();
    ctx.response.type = 'text/html';
    ctx.response.body = '<h1>Hello, koa2!</h1>';
});

// 在端口3000监听:
app.listen(3000);
console.log('app started at port 3000...');

koa-router

В приведенном выше примере мы всегда возвращаем один и тот же HTML-код при обработке HTTP-запросов, что не очень удобно Обычно мы должны вызывать разные функции обработки для разных URL-адресов, чтобы возвращать разные результаты.

Чтобы решить проблему перехода по URL-адресам, нам нужно ввести промежуточное программное обеспечение koa-router, которое отвечает за обработку сопоставления URL-адресов. Сначала добавьте зависимость koa-router в package.json:

"koa-router": "7.4.0"

Затем установите зависимости с помощью npm install. Затем мы модифицируем app.js, чтобы использовать koa-router для обработки сопоставления URL-адресов.

const Koa = require('koa');

// 注意require('koa-router')返回的是函数:
const router = require('koa-router')();

const app = new Koa();

app.use(async (ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
    await next();
});

router.get('/hello/:name', async (ctx, next) => {
    var name = ctx.params.name;
    ctx.response.body = `<h1>Hello, ${name}!</h1>`;
});

router.get('/', async (ctx, next) => {
    ctx.response.body = '<h1>Index</h1>';
});

app.use(router.routes());
app.listen(3000);
console.log('app started at port 3000...');

Следует отметить, что require('koa-router') возвращает функцию, похожую на:

const fn_router = require('koa-router');
const router = fn_router();

Затем мы используем router.get('/path', async fn) для регистрации запроса GET. Вы можете использовать /hello/:name с переменными в пути запроса, а доступ к переменным можно получить через ctx.params.name. Когда мы заходим на главную страницу:http://localhost:3000/

在这里插入图片描述
При входе в браузере:http://localhost:3000/hello/koa
在这里插入图片描述

отправить запрос

Запрос на получение обрабатывается с помощью router.get('/path', async fn). Если вы хотите обрабатывать почтовые запросы, вы можете использовать router.post('/path', async fn).

При обработке URL-адресов с почтовыми запросами мы столкнемся с проблемой: почтовые запросы обычно отправляют форму и JSON в качестве тела запроса, но ни исходный объект запроса, предоставленный Node.js, ни объект запроса, предоставленный koa, не предоставляет функцию. разбора тела запроса! На этом этапе вам нужно использовать плагин koa-bodyparser.

Поэтому при использовании koa-router для почтового запроса необходимо добавить зависимость koa-bodyparser в package.json:

"koa-bodyparser": "4.2.1"

Теперь мы можем использовать koa-bodyparser для отправки почтовых запросов, например:

const Koa = require('koa');

// 注意require('koa-router')返回的是函数:
const router = require('koa-router')();
const bodyParser = require('koa-bodyparser');

const app = new Koa();
app.use(async (ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
    await next();
});

router.get('/hello/:name', async (ctx, next) => {
    var name = ctx.params.name;
    ctx.response.body = `<h1>Hello, ${name}!</h1>`;
});

router.get('/', async (ctx, next) => {
    ctx.response.body = `<h1>Index</h1>
        <form action="/signin" method="post">
            <p>Name: <input name="name" value="koa"></p>
            <p>Password: <input name="password" type="password"></p>
            <p><input type="submit" value="Submit"></p>
        </form>`;
});

//POST请求
router.post('/signin', async (ctx, next) => {
    var
        name = ctx.request.body.name || '',
        password = ctx.request.body.password || '';
    console.log(`signin with name: ${name}, password: ${password}`);
    if (name === 'koa' && password === '12345') {
        ctx.response.body = `<h1>Welcome, ${name}!</h1>`;
    } else {
        ctx.response.body = `<h1>Login failed!</h1>
        <p><a href="/">Try again</a></p>`;
    }
});

router.get('/', async (ctx, next) => {
    ctx.response.body = '<h1>Index</h1>';
});

app.use(bodyParser());
app.use(router.routes());

app.listen(3000);
console.log('app started at port 3000...');

Затем, когда мы запускаем службу с помощью npm start, вводим koa и 12345, тест проходит.

оптимизация

Теперь, хотя мы можем обрабатывать разные URL-адреса в зависимости от ввода, код чрезвычайно удобочитаем и расширяем. Правильный способ его написания — отделить страницу от логики, поэтому делаем копию url-koa, переименовываем ее в url2-koa и рефакторим проект. Рефакторинговая структура каталогов проекта выглядит следующим образом:

url2-koa/
|
+- .vscode/
|  |
|  +- launch.json 
|
+- controllers/
|  |
|  +- login.js        //处理login相关URL
|  |
|  +- users.js      //处理用户管理相关URL
|
+- app.js           //使用koa的js
|
+- package.json 
|
+- node_modules/          //npm安装的所有依赖包

Мы добавляем файл index.js в каталог контроллеров и добавляем следующее:

var fn_index = async (ctx, next) => {
    ctx.response.body = `<h1>Index</h1>
        <form action="/signin" method="post">
            <p>Name: <input name="name" value="koa"></p>
            <p>Password: <input name="password" type="password"></p>
            <p><input type="submit" value="Submit"></p>
        </form>`;
};

var fn_signin = async (ctx, next) => {
    var
        name = ctx.request.body.name || '',
        password = ctx.request.body.password || '';
    console.log(`signin with name: ${name}, password: ${password}`);
    if (name === 'koa' && password === '12345') {
        ctx.response.body = `<h1>Welcome, ${name}!</h1>`;
    } else {
        ctx.response.body = `<h1>Login failed!</h1>
        <p><a href="/">Try again</a></p>`;
    }
};

module.exports = {
    'GET /': fn_index,
    'POST /signin': fn_signin
};

В приведенном выше примере index.js предоставляет два обработчика URL-адресов через module.exports. Затем мы модифицируем app.js для автоматического сканирования каталога контроллеров, поиска всех файлов js и регистрации каждого URL-адреса.

var files = fs.readdirSync(__dirname + '/controllers');

// 过滤出.js文件:
var js_files = files.filter((f)=>{
    return f.endsWith('.js');
});

// 处理每个js文件:
for (var f of js_files) {
    console.log(`process controller: ${f}...`);
    // 导入js文件:
    let mapping = require(__dirname + '/controllers/' + f);
    for (var url in mapping) {
        if (url.startsWith('GET ')) {
            // 如果url类似"GET xxx":
            var path = url.substring(4);
            router.get(path, mapping[url]);
            console.log(`register URL mapping: GET ${path}`);
        } else if (url.startsWith('POST ')) {
            // 如果url类似"POST xxx":
            var path = url.substring(5);
            router.post(path, mapping[url]);
            console.log(`register URL mapping: POST ${path}`);
        } else {
            // 无效的URL:
            console.log(`invalid URL: ${url}`);
        }
    }
}

Если приведенный выше пример кажется немного трудоемким, приведенную выше функцию можно разделить.

function addMapping(router, mapping) {
    for (var url in mapping) {
        if (url.startsWith('GET ')) {
            var path = url.substring(4);
            router.get(path, mapping[url]);
            console.log(`register URL mapping: GET ${path}`);
        } else if (url.startsWith('POST ')) {
            var path = url.substring(5);
            router.post(path, mapping[url]);
            console.log(`register URL mapping: POST ${path}`);
        } else {
            console.log(`invalid URL: ${url}`);
        }
    }
}

function addControllers(router) {
    var files = fs.readdirSync(__dirname + '/controllers');
    var js_files = files.filter((f) => {
        return f.endsWith('.js');
    });

    for (var f of js_files) {
        console.log(`process controller: ${f}...`);
        let mapping = require(__dirname + '/controllers/' + f);
        addMapping(router, mapping);
    }
}

addControllers(router);

Для удобства мы извлекаем код для сканирования каталога контроллеров и создания маршрутизатора из app.js в качестве промежуточного программного обеспечения и называем его: controller.js.

const fs = require('fs');

function addMapping(router, mapping) {
    for (var url in mapping) {
        if (url.startsWith('GET ')) {
            var path = url.substring(4);
            router.get(path, mapping[url]);
            console.log(`register URL mapping: GET ${path}`);
        } else if (url.startsWith('POST ')) {
            var path = url.substring(5);
            router.post(path, mapping[url]);
            console.log(`register URL mapping: POST ${path}`);
        } else {
            console.log(`invalid URL: ${url}`);
        }
    }
}

function addControllers(router) {
    var files = fs.readdirSync(__dirname + '/controllers');
    var js_files = files.filter((f) => {
        return f.endsWith('.js');
    });

    for (var f of js_files) {
        console.log(`process controller: ${f}...`);
        let mapping = require(__dirname + '/controllers/' + f);
        addMapping(router, mapping);
    }
}

module.exports = function (dir) {
    let
        controllers_dir = dir || 'controllers', // 如果不传参数,扫描目录默认为'controllers'
        router = require('koa-router')();
    addControllers(router, controllers_dir);
    return router.routes();
};

Затем мы можем использовать controller.js непосредственно в файле app.js. Например:

const Koa = require('koa');
const bodyParser = require('koa-bodyparser');

const app = new Koa();

// 导入controller 中间件
const controller = require('./controller');

app.use(bodyParser());
app.use(controller());

app.listen(3000);
console.log('app started at port 3000...');

Кросс-домен Koa2

Та же политика происхождения

Так называемая политика одинакового происхождения — это функция безопасности браузера. Клиентские сценарии разного происхождения не могут читать и записывать ресурсы друг друга без явной авторизации. Политика единого происхождения была введена в браузер компанией Netscape. В настоящее время все браузеры реализуют эту политику. Первоначально его значение означало, что файл cookie, установленный веб-страницей A, не может быть открыт веб-страницей B, если только две веб-страницы не являются «одним и тем же источником». . Так называемое «одинаковое происхождение» относится к «трем одинаковым», то есть с одним и тем же протоколом, одним и тем же доменным именем и одним и тем же портом.

Например, есть следующий URL:woohoo.netease.com/ah.html,Протокол http:// и доменное имяwww.netease.com, порт 80 (порт по умолчанию можно не указывать), то тот же источник ситуация следующая:

在这里插入图片描述

перекрестный домен

Из-за политики браузера в отношении одного и того же источника сценарии, созданные не из одного и того же источника, не могут работать с объектами из других источников.Если вы хотите выполнить политику одного и того же источника, вам необходимо выполнять междоменные операции. Существует два основных решения для междоменных запросов Ajax для браузеров: JSONP и CORS.

Ajax

Ajax — это метод создания быстрых динамических веб-страниц, который позволяет частично обновлять веб-страницы без перезагрузки всей веб-страницы. В следующем сценарии междоменного запроса через Ajax первые два локальных сервиса запускаются через koa: один порт 3200, а другой 3201.

app1.js

onst koa = require('koa');
const app = new koa();

const Router = require('koa-router');
const router = new Router();

const serve = require('koa-static');

const path = require('path');

const staticPath = path.resolve(__dirname, 'static');

// 设置静态服务
const staticServe = serve(staticPath, {
  setHeaders: (res, path, stats) => {
    if (path.indexOf('jpg') > -1) {
      res.setHeader('Cache-Control', ['private', 'max-age=60']);
    }
  }
});
app.use(staticServe);

router.get('/ajax', async (ctx, next) => {
  console.log('get request', ctx.request.header.referer);
  ctx.body = 'received';
});

app.use(router.routes());

app.listen(3200);
console.log('koa server is listening port 3200');

app2.js

const koa = require('koa');
const app = new koa();
const Router = require('koa-router');

const router = new Router();

router.get('/ajax', async (ctx, next) => {
  console.log('get request', ctx.request.header.referer);
  ctx.body = 'received';
});

app.use(router.routes());

app.listen(3200);
console.log('app2 server is listening port 3200');

Поскольку в этом примере требуется использование подключаемого модуля koa-static, перед запуском службы необходимо установить подключаемый модуль koa-static. Затем добавьте новый файл origin.html и добавьте следующий код:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>cross-origin test</title>
</head>
<body style="width: 600px; margin: 200px auto; text-align: center">
  <button onclick="getAjax()">AJAX</button>
  <button onclick="getJsonP()">JSONP</button>
</body>
<script type="text/javascript">

  var baseUrl = 'http://localhost:3201';

  function getAjax() {
    var xhr = new XMLHttpRequest();            
    xhr.open('GET',  baseUrl + '/ajax', true);
    xhr.onreadystatechange = function() {
      // readyState == 4说明请求已完成
      if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { 
        // 从服务器获得数据  
        alert(xhr.responseText);
      } else {
        console.log(xhr.status);
      }
    };
    xhr.send();
  }
</script>
</html>

Когда ajax отправляет междоменный запрос, консоль сообщает об ошибке:

Failed to load http://localhost:3201/ajax: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3200' is therefore not allowed access.

Хотя консоль сообщила об ошибке, запрос AJAX получил 200. Это связано с механизмом CORS браузера, который будет подробно объяснен позже.

在这里插入图片描述

JSONP

Хотя политика браузера с одинаковым источником ограничивает XMLHttpRequest запросом данных из разных доменов. Однако можно вводить на страницу js-скрипты разных доменов, и скрипт, запрошенный элементом script, будет выполняться непосредственно браузером.

Добавьте следующий скрипт в файл скрипта origin.html:

function getJsonP() {
    var script = document.createElement('script');
    script.src = baseUrl + '/jsonp?type=json&callback=onBack';
    document.head.appendChild(script);
}

function onBack(res) {
  alert('JSONP CALLBACK:  ', JSON.stringify(res)); 
}

При нажатии кнопки JSONP метод getJsonP добавит скрипт на текущую страницу, а атрибут src указывает на междоменный GET-запрос:http://localhost:3201/jsonp?type=json&callback=onBack,Приведите запрашиваемые параметры в формате запроса. callback — это ключ. Он используется для определения имени функции обратного вызова междоменного запроса. Это значение должно соответствовать фону и сценарию.

Затем добавьте код маршрутизации для запроса jsonp в app2.js:

router.get('/jsonp', async (ctx, next) => {
  const req = ctx.request.query;
  console.log(req);
  const data = {
    data: req.type
  }
  ctx.body = req.callback + '('+ JSON.stringify(data) +')';
})

app.use(router.routes());

Затем обновите, чтобы увидеть эффект. Следует отметить, что методы ajax, предоставляемые сторонними библиотеками js, такими как jquery и zepto, инкапсулируют запрос jsonp.Например, запрос ajax, отправленный jquery в jsonp, выглядит следующим образом:

function getJsonPByJquery() {
    $.ajax({
      url: baseUrl + '/jsonp',
      type: 'get',
      dataType: 'jsonp',  
      jsonpCallback: "onBack",   
      data: {
        type: 'json'
      }
    });
  }

CORS

Совместное использование ресурсов между источниками (CORS) — это механизм, который использует дополнительные заголовки HTTP, чтобы сообщить браузерам, что веб-приложениям, работающим в одном источнике (домене), разрешен доступ к определенным ресурсам с серверов в разных источниках. Когда ресурс запрашивает ресурс из домена, протокола или порта, отличного от сервера, на котором находится сам ресурс, ресурс инициирует междоменный HTTP-запрос.

Существует множество способов реализации междоменных ajax-запросов, один из которых — использование CORS, и ключом к этому методу является его настройка на стороне сервера.

CORS делит запросы на простые запросы и непростые запросы. Среди них простые запросы — это запросы на получение и публикацию без дополнительных заголовков запроса, а если это запрос на публикацию, формат запроса не может быть application/json. Остальные запросы на размещение и публикацию, запросы с типом содержимого application/json и запросы с пользовательскими заголовками запросов не являются простыми запросами.

Для запросов CORS, которые не являются простыми запросами, перед формальным общением будет добавлен запрос HTTP-запроса, который называется предварительным запросом. Браузер сначала запрашивает у сервера, находится ли доменное имя текущей страницы в списке разрешенных сервером и какие HTTP-команды и поля заголовка можно использовать. Браузер выдаст формальный запрос XMLHttpRequest только в случае положительного ответа, в противном случае он сообщит об ошибке.

Сначала добавьте запрос на публикацию в origin.html и добавьте следующий код:

function corsWithJson() {
    $.ajax({
      url: baseUrl + '/cors',
      type: 'post',
      contentType: 'application/json',
      data: {
        type: 'json',
      },
      success: function(data) {
        console.log(data);
      }
    })
  }

Установив для Content-Type значение application/json, чтобы сделать его непростым запросом, метод «предварительного» запроса является OPTIONS, и сервер считает, что источник является междоменным, поэтому он возвращает 404. В дополнение к полю Origin заголовок запроса «preflight» включает в себя два специальных поля:

Access-Control-Request-MethodЭто поле является обязательным и используется для перечисления методов HTTP, которые будут использоваться в CORS-запросе браузера. В приведенном выше примере используется PUT.Access-Control-Request-HeadersЭто поле представляет собой строку с разделителями-запятыми, которая указывает дополнительные поля заголовка, которые будут отправлять запросы CORS браузера, такие как тип содержимого в примере.

В то же время CORS позволяет серверу добавлять некоторую информацию заголовка в заголовок ответа, чтобы отвечать на междоменные запросы. Затем введите koa2-cors в app2.js и добавьте следующий код:

app.use(cors({
  origin: function (ctx) {
      if (ctx.url === '/cors') {
          return "*"; 
      }
      return 'http://localhost:3201';
  },
  exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
  maxAge: 5,
  credentials: true,
  allowMethods: ['GET', 'POST', 'DELETE'], 
  allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}));

После перезапуска службы браузер повторно отправляет запрос POST. Вы можете видеть, что браузер отправляет два запроса.

在这里插入图片描述
Заголовок ответа OPTIONS указывает, что сервер установил Access-Control-Allow-Origin:*, поэтому он отправляет запрос POST и получает возвращаемое значение сервера.
在这里插入图片描述
Кроме того, в сообщении ответа на запрос OPTIONS есть некоторые другие поля, предоставляемые CORS в информации заголовка:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type,Authorization,Accept
Access-Control-Allow-Methods: GET,POST,DELETE
Access-Control-Max-Age: 5

Access-Control-Allow-Methods: Это поле является обязательным, и его значение представляет собой строку с разделителями-запятыми, указывающую все методы междоменных запросов, поддерживаемые сервером.Access-Control-Allow-Headers: Поле Access-Control-Allow-Headers является обязательным, если запрос браузера включает поле Access-Control-Request-Headers. Это также строка с разделителями-запятыми, указывающая все поля заголовка, поддерживаемые сервером, не ограничиваясь теми, которые запрошены браузером в «предпечатной проверке».Access-Control-Allow-Credentials: Это поле является необязательным. Его значение является логическим значением, указывающим, разрешать ли отправку файлов cookie. По умолчанию файлы cookie не включаются в запросы CORS.Access-Control-Max-Age: Это поле является необязательным и используется для указания периода действия этого запроса предварительной проверки в секундах.

Ссылаться на:

Платформа Koa2 использует CORS для выполнения междоменных запросов ajax.

Koa — среда веб-разработки следующего поколения на основе Node.js