NodeJS超长总结 3
冰洋 11/7/2018 Node
# 🌹🌹 🐱 🐶 🐭 🐘 🐳 ✈️ 🚄 🚗 ⚽️ 💆 🥚 🧒 🌹 🐯
上次总结到了http的实现。。。。另开一章,继续总结记录
# koa
# 开始一个koa
# 1. 简介
- Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。
- 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。
- Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
# 2. 安装
- node 7.6以上版本
- 低版本用bable编译
$ nvm install 7
$ npm i koa
$ node my-koa-app.js
1
2
3
2
3
# 3. 应用程序
# hellow world:
const Koa = require('koa'); // 引入
const app = new Koa(); // 创建实力
app.use(async ctx => { //
ctx.body = 'Hello World';
});
app.listen(3000); //
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 级联
const Koa = require('koa');
const app = new Koa();
// logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# app.listen(...)
创建并返回 HTTP 服务器,将给定的参数传递给 Server#listen():
const Koa = require('koa');
const app = new Koa();
app.listen(3000);
1
2
3
2
3
这里的 app.listen(...) 方法只是以下方法的语法糖:
const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
1
2
3
4
2
3
4
这意味着您可以将同一个应用程序同时作为 HTTP 和 HTTPS 或多个地址:
const http = require('http');
const https = require('https');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000);
https.createServer(app.callback()).listen(3001);
1
2
3
4
5
6
2
3
4
5
6
# app.callback()
返回适用于 http.createServer() 方法的回调函数来处理请求。你也可以使用此回调函数将 koa 应用程序挂载到 Connect/Express 应用程序中。
# app.use(function)
将给定的中间件方法添加到此应用程序
# app.keys=
设置签名的 Cookie 密钥。这些被传递给 KeyGrip,但是你也可以传递你自己的 KeyGrip 实例。
app.keys = ['im a newer secret', 'i like turtle'];
app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');
1
2
2
这些密钥可以倒换,并在使用 { signed: true } 参数签名 Cookie 时使用。
ctx.cookies.set('name', 'tobi', { signed: true });
1
# app.context
- app.context 是从其创建 ctx 的原型。您可以通过编辑 app.context 为 ctx 添加其他属性。
- 这对于将 ctx 添加到整个应用程序中使用的属性或方法非常有用,这可能会更加有效(不需要中间件)和/或 更简单(更少的 require()),而更多地依赖于ctx,这可以被认为是一种反模式。
例如,要从 ctx 添加对数据库的引用:
app.context.db = db();
app.use(async ctx => {
console.log(ctx.db);
});
1
2
3
4
5
2
3
4
5
注意
- ctx 上的许多属性都是使用 getter ,setter 和 Object.defineProperty() 定义的。你只能通过在 app.context 上使用 Object.defineProperty() 来编辑这些属性(不推荐)。
- 安装的应用程序目前使用其父级的 ctx 和设置。 因此,安装的应用程序只是一组中间件。
# 错误处理
// 默认情况下,将所有错误输出到 stderr,除非 app.silent 为 true。 当 err.status 是 404 或 err.expose 是 true 时默认错误处理程序也不会输出错误。 要执行自定义错误处理逻辑,如集中式日志记录,您可以添加一个 “error” 事件侦听器:
app.on('error', err => {
log.error('server error', err)
});
// 如果 req/res 期间出现错误,并且 _无法_ 响应客户端,Context实例仍然被传递:
app.on('error', (err, ctx) => {
log.error('server error', err, ctx)
});
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
当发生错误 并且 仍然可以响应客户端时,也没有数据被写入 socket 中,Koa 将用一个 500 “内部服务器错误” 进行适当的响应。在任一情况下,为了记录目的,都会发出应用级 “错误”。
# 4.上下文 Context/ctx
- Koa Context 将 node 的 request 和 response 对象封装到单个对象中。
- Context/ctx就是req和res的封装
app.use(async ctx => {
ctx; // 这是 Context
// 会自动将一些api委托给 ctx.request或者 koa Response
// 例如 ctx.type 和 ctx.length 委托给 response 对象,ctx.path 和 ctx.method 委托给 request。
ctx.request; // 这是 koa Request 请求报文
ctx.response; // 这是 koa Response 相应报文
});
1
2
3
4
5
6
7
2
3
4
5
6
7
# 5.API Context/ctx 具体方法和访问器. 详细参考官方文档
ctx.req
node的request对象ctx.res
node的response对象ctx.request
koa 的 Request 对象.ctx.response
koa 的 Response 对象.ctx.state
:- 推荐的命名空间,用于通过中间件传递信息和你的前端视图。
ctx.state.user = await User.find(id);
ctx.app
应用程序实例引用ctx.cookies.get(name, [options])
- 通过 options 获取 cookie name:
signed 所请求的cookie应该被签名
- koa使用cookie模块,其中只需传递参数
- 通过 options 获取 cookie name:
ctx.cookies.set(name, value, [options])
# 自己实现一个koa
- koa使用方法
let Koa = require('./koa/application');
let app = new Koa();
let logger = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 1000);
})
}
// return或者await 都可以达到同样的效果
let fs = require('fs');
app.use((ctx, next) => {
ctx.body = fs.createReadStream('./1.txt');
// console.log(1);
// //throw new Error('出错了');
// return next(); // 这个函数是异步函数
// console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
await logger();
next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(5);
await next();
console.log(6);
});
app.on('error', err => {
console.log(err);
})
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- application.js
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');
let Stream = require('stream');
let EventEmitter = require('events');
class Koa extends EventEmitter{
constructor(){
super();
this.middlewares = [];
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}
use(fn){
this.middlewares.push(fn);
}
createContext(req,res){
// 上下文就是一个对象而已
let ctx = this.context;
// ctx上拥有两个自定义的属性 request response
ctx.request = this.request;
ctx.response = this.response;
// req和res是自己身上的
ctx.req = ctx.request.req = req;
ctx.res = ctx.response.res = res;
return ctx;
}
compose(ctx,middlewares){
function dispatch(index) {
if(index === middlewares.length) return Promise.resolve();
let fn = middlewares[index];
return Promise.resolve(fn(ctx,()=>dispatch(index+1)));
}
return dispatch(0);
}
handleRequest(req,res){
let ctx = this.createContext(req,res);
let p = this.compose(ctx,this.middlewares); // 把函数组合起来
p.then(() => {
let body = ctx.body;// 当所有的函数都执行完后取出body的值,响应回去即可
console.log(body instanceof Stream);
if (body instanceof Stream){
body.pipe(res);
}else if(Buffer.isBuffer(body) || typeof body === 'string'){
res.end(body);
}else if(typeof body === 'object' ){
res.end(JSON.stringify(body));
}else{
res.end(body);
}
}).catch(err => {
this.emit('error', err);
});
}
listen(...args){
let server = http.createServer(this.handleRequest.bind(this));
server.listen(...args);
}
}
module.exports = Koa;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
- context.js
// 上下文对象
let proto = {
}
// ctx.path
function defineGetter(target,property) {
proto.__defineGetter__(property,function () {
return this[target][property]
});
}
function defineSetter(target,property) {
proto.__defineSetter__(property,function (value) {
this[target][property] = value
})
}
// ctx.path = ctx.request.path;
defineGetter('request','path');
defineGetter('request','query');
defineGetter('response','body');
// ctx.body = ctx.response.body;
defineSetter('response','body')
module.exports = proto;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- request.js
let request = {
// 类似于 Object.definProperty 属性中的get
get url(){
return this.req.url
},
get path(){
let {pathname} = require('url').parse(this.req.url);
return pathname
},
get query(){
let { query } = require('url').parse(this.req.url,true);
return query
},
get headers() {
return this.req.headers
}
}
module.exports = request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- response.js
let response = {
get body(){
return this._body;
},
set body(value){
this._body = value;
}
}
response.body
module.exports = response
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 获取请求参数
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
console.log(ctx.method); //获取请求方法
console.log(ctx.url); //获取请求URL
console.log(ctx.query); //获取解析的查询字符串对象
console.log(ctx.querystring); //根据 ? 获取原始查询字符串.
console.log(ctx.headers);//获取请求头对象
ctx.body = ctx.url;
});
app.listen(3000, () => {
console.log('server is starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 获取请求体
const Koa = require('koa');
const querystring = require('querystring');
const app = new Koa();
app.use(async (ctx) => {
if (ctx.method == 'GET') {
ctx.set('Content-Type', 'text/html;charset=utf-8');
ctx.body = (
`
<form method="POST">
<input name="username" >
<input type="submit">
</form>
`
);
} else if (ctx.method == 'POST') {
ctx.set('Content-Type', 'application/json');
ctx.body = await parseBody(ctx);
} else {
ctx.body = 'Not Allowed';
}
});
function parseBody(ctx) {
return new Promise(function (resolve, reject) {
let buffers = [];
ctx.req.on('data', function (data) {
buffers.push(data);
});
ctx.req.on('end', function (data) {
let body = buffers.toString();
body = querystring.parse(body);
resolve(body);
});
ctx.req.on('error', function (errdata) {
reject(err);
});
});
}
app.listen(3000, () => {
console.log('server is starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 模板引擎 koa-views
npm i koa-views ejs -S
koa-views 第三方模块 专门实现 模板引擎的
# 用法
// koa-views 第三方模块 专门实现 模板引擎的
let Koa = require('koa');
let views = require('koa-views')
let app = new Koa();
let path = require('path');
app.use(views(path.join(__dirname, 'views'), {
extension: 'ejs' // 自己引入ejs
}));
app.use(async (ctx,next)=>{
// render方法返回的是一个promise 如果不写await
await ctx.render('index',{name:'zfpx'});
});
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 自己实现 koa-views
let Koa = require('koa');
// let views = require('koa-views')
let app = new Koa();
let path = require('path');
// 自己简单实现
function views(dir,{extension}) {
return async (ctx,next)=>{
// ctx.render是一个promise方法
ctx.render = async function (p,obj) {
let realPath = path.join(dir, p) + '.'+ extension;
let fs = require('fs');
let util = require('util');
let readFile = util.promisify(fs.readFile);
let str = await readFile(realPath,'utf8');
ctx.body = require(extension).render(str, obj)
}
await next();
}
}
app.use(views(path.join(__dirname, 'views'), {
extension: 'ejs' // 自己引入ejs
}));
app.use(async (ctx,next)=>{
// render方法返回的是一个promise 如果不写await
await ctx.render('index',{name:'zfpx'});
});
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 自己实现一个简单的ejs
先复习下正则
let path = require('path');
let fs = require('fs');
let r = fs.readFileSync(path.join(__dirname, './1.html'), 'utf8');
let obj = { arr: [1, 2, 3] };
function render(str,obj) {
let head = `let tmpl=''\r\n`;
head += `with (obj) {\r\n`
let content = 'tmpl+=`\r\n';
str = str.replace(/<%=([\s\S]*?)%>/g,function () {
return '${'+arguments[1]+'}'
})
content +=str.replace(/<%([\s\S]*?)%>/g,function () {
return '`\r\n'+arguments[1] +"\r\ntmpl+=`"
})
let tail = '`}\r\n return tmpl';
return head + content + tail
}
r = render(r,obj);
let fnStr = new Function('obj',r);
let result = fnStr(obj);
fs.writeFile('./1.js',result)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 静态资源中间件 koa-static
npm install --save koa-static
# 用法
let Koa = require('koa');
// let static = require('koa-static');
let app = new Koa();
let path = require('path');
app.use(static(path.join(__dirname,'public')));
app.use( (ctx,next) => {
ctx.body = 'hello world'
})
app.listen(3000);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 自己简单实现
let Koa = require('koa');
let app = new Koa();
let path = require('path');
let fs = require('fs');
let {promisify} = require('util');
let stat = promisify(fs.stat);
function static(root) {
return async (ctx,next)=>{
let realPath = path.join(root,ctx.path);
try{
let statObj = await stat(realPath);
if(statObj.isDirectory()){
let p = path.join(realPath,'index.html');
await stat(p);
ctx.body = fs.createReadStream(p);
}else{
ctx.body = fs.createReadStream(realPath);
}
}catch(e){
return next();
}
}
}
app.use(static(path.join(__dirname,'public')));
app.use( (ctx,next) => {
ctx.body = 'hello world'
})
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 使用中间件获取普通请求体 koa-bodyparser
npm i koa-bodyparser -S
# 用法
const Koa = require('koa');
const querystring = require('querystring');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
app.use(bodyParser());
app.use(async (ctx) => {
if (ctx.method == 'GET') {
ctx.set('Content-Type', 'text/html;charset=utf-8');
ctx.body = (
`
<form method="POST">
<input name="username" >
<input type="submit">
</form>
`
);
} else if (ctx.method == 'POST') {
ctx.set('Content-Type', 'application/json');
ctx.body = ctx.request.body;
} else {
ctx.body = 'Not Allowed';
}
});
app.listen(3000, () => {
console.log('server is starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 自己实现
let Koa = require('koa');
let app = new Koa();
let fs = require('fs');
let path = require('path');
app.use(async (ctx, next) => {
if (ctx.path === '/' && ctx.method === 'GET') {
ctx.set('Content-Type', 'text/html;charset=utf8');
ctx.body = fs.createReadStream(path.join(__dirname, '1.html'));
} else {
await next();
}
});
function bodyParser(ctx) {
return new Promise((resolve, reject) => {
let arr = [];
ctx.req.on('data', function (data) {
arr.push(data);
});
ctx.req.on('end', function () {
resolve(Buffer.concat(arr));
})
})
}
app.use(async (ctx, next) => {
if (ctx.path === '/login' && ctx.method === 'POST') {
console.log('提交来了');
// a=b
ctx.set('Content-Type', 'text/plain;charset=utf8');
ctx.body = await bodyParser(ctx);
}
})
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 使用中间件获取包含文件的请求体
npm i koa-better-body -S
const Koa = require('koa');
const querystring = require('querystring');
const path = require('path');
const convert = require('koa-convert');
const bodyParser = require('koa-better-body');
const app = new Koa();
app.use(convert(bodyParser({
uploadDir: path.join(__dirname, 'uploads'),
keepExtensions: true
})));
app.use(async (ctx) => {
if (ctx.method == 'GET') {
ctx.set('Content-Type', 'text/html;charset=utf-8');
ctx.body = (
`
<form method="POST" enctype="multipart/form-data">
<input name="username" >
<input name="avatar" type="file" >
<input type="submit">
</form>
`
);
} else if (ctx.method == 'POST') {
ctx.set('Content-Type', 'application/json');
console.log(ctx.request.fields);
ctx.body = ctx.request.body;
} else {
ctx.body = 'Not Allowed';
}
});
app.listen(3000, () => {
console.log('server is starting at port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
username: 'zfpx',
avatar: [File {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
size: 78540,
path: '\%uploads\%upload_b631c6cbae762214afbe18b6e18d9f68.png',
name: 'mm.png',
type: 'image/png',
hash: null,
lastModifiedDate: 2018 - 03 - 09 T09: 12: 20.679 Z,
_writeStream: [WriteStream]
}]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# cookie
- ctx.cookies.get(name,[optins]):读取上下文请求中的cookie。
- ctx.cookies.set(name,value,[options]):在上下文中写入cookie。
- domain:写入cookie所在的域名
- path:写入cookie所在的路径
- maxAge:Cookie最大有效时长
- expires:cookie失效时间
- httpOnly:是否只用http请求中获得
- overwirte:是否允许重写
app.use(async (ctx, next) => {
console.log(ctx.url);
if (ctx.url == '/write') {
ctx.cookies.set('name', 'zfpx');
ctx.body = 'write';
} else {
next();
}
});
app.use(async (ctx) => {
if (ctx.url == '/read') {
ctx.body = ctx.cookies.get('name');
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# session
$ npm install koa-session
const Koa = require('koa');
const session = require('koa-session');
const app = new Koa();
app.keys = ['zfpx'];
app.use(session({}, app));
app.use(async (ctx) => {
let visit = ctx.session.visit;
if (visit) {
visit = visit + 1;
} else {
visit = 1;
}
ctx.session.visit = visit;
ctx.body = `这是你的第${visit}次访问`;
});
app.listen(3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# generator
npm install -g koa-generator
$ koa /tmp/foo && cd /tmp/foo
$ npm install
$ npm start
1
2
3
2
3
# form-data
const Koa = require('koa');
const views = require('koa-views');
const fs = require('fs');
let querystring = require('querystring');
let path = require('path');
let uuid = require('uuid');
const app = new Koa();
app.use(async (ctx, next) => {
if (ctx.method == 'GET') {
ctx.set('Content-Type', 'text/html;charset=utf8');
ctx.body = (
`
<form id="userform" method="POST" enctype="multipart/form-data">
用户名:<input type="text" name="username">
密码<input type="text" name="password">
头像<input type="file" name="avatar">
<input type="submit">
</form>
`
);
} else if (ctx.method == 'POST') {
let buffers = [];
ctx.req.on('data', function (data) {
buffers.push(data);
});
ctx.req.on('end', function () {
let result = Buffer.concat(buffers);
let type = ctx.headers['content-type'];
let matched = type.match(/\bboundary=(.+)\b/);
if (matched) {
let seperator = '--' + matched[1];
let body = process(seperator, result);
ctx.body = body;
} else {
next();
}
});
ctx.body = 'hello';
} else {
next();
}
});
app.listen(3000);
Buffer.prototype.split = Buffer.prototype.split || function (sep) {
let len = Buffer.byteLength(sep);
let parts = [];
let offset = 0;
let pos = -1;
while (-1 != (pos = this.indexOf(sep, offset))) {
parts.push(this.slice(offset, pos));
offset = pos + len;
}
parts.push(this.slice(offset));
return parts;
}
function process(seperator, result) {
let lines = result.split(seperator);
lines = lines.slice(1, -1);
let body = {};
let files = [];
lines.forEach(function (line) {
let [desc, val] = line.split('\r\n\r\n');
desc = desc.toString();
val = val.slice(0, -2);
if (desc.includes('filename')) {//如果是文件的话
let [, line1, line2] = desc.split('\r\n');
let obj1 = querystring.parse(line1, '; ');
let obj2 = querystring.parse(line2, '; ');
let filepath = path.join(__dirname, 'uploads', uuid.v4());
fs.writeFileSync(filepath, val);
files.push({
...obj1, filepath
});
} else {
let matched = desc.match(/\bname=(.+)\b/);
if (matched)
body[matched[1]] = val.toString();
}
});
return { body, files };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82