koa架设一个HTTP服务php
const Koa = require('koa'); const app = new Koa(); app.listen(3000);
``css
const Koa = require('koa'); const app = new Koa(); app.use(async ctx => { ctx.body = 'Hello World'; }); app.listen(3000);
*Koa提供一个Context对象,表示一次对话的上下文(包括HTTP 请求和HTTP回复)。经过加工这个对象,就能够控制返回给用户的内容。
Context.response.body属性就是发送给用户的内容html
const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.response.body = 'Hello World'; }; app.use(main); app.listen(3000);
Koa默认的返回类型是text/plain,若是想返回其余类型的内容,能够利用ctx.request.accepts判断一下,客户端但愿接受什么数据(根据HTTP Request的Accept字段),而后使用ctx.response.type指定返回类型。node
const Koa = require('koa'); const app = new Koa(); const main = ctx => { if (ctx.request.accepts('xml')) { ctx.response.type = 'xml'; ctx.response.body = '<data>Hello World</data>'; } else if (ctx.request.accepts('json')) { ctx.response.type = 'json'; ctx.response.body = { data: 'Hello World' }; } else if (ctx.request.accepts('html')) { ctx.response.type = 'html'; ctx.response.body = '<p>Hello World</p>'; } else { ctx.response.type = 'text'; ctx.response.body = 'Hello World'; } }; app.use(main); app.listen(3000);
实际开发中,返回给用户的网页每每都写成模板文件,咱们可让Koa先读取模板文件,而后将这个模板返回给用户。web
const fs = require('fs'); const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.response.type = 'html'; ctx.response.body = fs.createReadStream('./demos/template.html'); } app.use (main); app.listen(3000)
两个代码效果是相同
数据库
const fs = require('fs'); const Koa = require('koa'); const app = new Koa(); app.use(ctx=>{ ctx.response.type = 'html'; ctx.response.body = fs.createReadStream('./demos/template.html'); }) app.listen(3000)
Koa2中提供了ctx.method属性,能够轻松的获得请求的类型,而后根据请求类型编写不一样的相应方法,这在工做中很是经常使用。npm
const Koa = require('koa'); const app = new Koa(); app.use(async(ctx)=>{ //当请求是get请求时,显示表单让用户填写 if(ctx.url === '/' && ctx.method === 'GET'){ let html = ` <h1>Koa2 request post demo</h1> <form method="POST" action='/'> <p>username</p> <input name="username"/> <br/> <p>age</p> <input name="age" /> <br/> <p>website</p> <input name="website" /> <button type="submit">submit</button> </form> `; ctx.body = html; //当请求是post请求时 }else if(ctx.url === '/' && ctx.method === 'POST'){ ctx.body = '接收到post请求' }else{ //其余页面显示404页面 ctx.body = '<h1>404!</h1>' } }) app.listen(3000,()=>{ console.log('demo server is starting at port 3000') })
注意,POST必须写成POST,post不能够(必须大写,小写不行)编程
声明一个方法,而后用Promise对象进行解析。这里咱们使用了ctx.req.on来接收事件。json
function parsePostData(ctx){ return new Promise((resolve,reject)=>{ try{ let postdata = ''; ctx.req.addListener('end',function(){ resolve(postdata) }) ctx.req.on('data',(data)=>{ postdata += data; }) }catch(error){ reject(error) } }) }
写一个字符串封装JSON兑现对象的方法。api
function parseQueryStr(queryStr) { let queryData = {}; let queryStrList = queryStr.split('&'); console.log(queryStrList); for (let [index,queryStr] of queryStrList.entries()){ //entries() 方法返回一个数组的迭代对象,该对象包含数组的键值对 (key/value)。 //迭代对象中数组的索引值做为 key, 数组元素做为 value。 let itemList = queryStr.split('='); console.log(itemList); //key:value queryData[itemList[0]] = decodeURIComponent(itemList[1]); // decodeURIComponent() 函数可对 encodeURIComponent() 函数编码的 URI 进行解码。 } return queryData; }
所有代码
const Koa = require('koa'); const app = new Koa(); app.use(async(ctx)=>{ //当请求是get请求时,显示表单让用户填写 if(ctx.url === '/' && ctx.method === 'GET'){ let html = ` <h1>Koa2 request post demo</h1> <form method="POST" action='/'> <p>username</p> <input name="username"/> <br/> <p>age</p> <input name="age" /> <br/> <p>website</p> <input name="website" /> <button type="submit">submit</button> </form> `; ctx.body = html; //当请求是post请求时 }else if(ctx.url === '/' && ctx.method === 'POST'){ let postData = await parsePostData(ctx); ctx.body = postData; }else{ //其余页面显示404页面 ctx.body = '<h1>404!</h1>' } }) function parsePostData(ctx){ return new Promise((resolve,reject)=>{ try{ let postdata = ''; ctx.req.addListener('end',function(){ let parseData = parseQueryStr(postdata) resolve(parseData) }) ctx.req.on('data',(data)=>{ postdata += data; }) }catch(error){ reject(error) } }) } function parseQueryStr(queryStr) { let queryData = {}; let queryStrList = queryStr.split('&'); console.log(queryStrList); for (let [index,queryStr] of queryStrList.entries()){ //entries() 方法返回一个数组的迭代对象,该对象包含数组的键值对 (key/value)。 //迭代对象中数组的索引值做为 key, 数组元素做为 value。 let itemList = queryStr.split('='); console.log(itemList); //key:value queryData[itemList[0]] = decodeURIComponent(itemList[1]); // decodeURIComponent() 函数可对 encodeURIComponent() 函数编码的 URI 进行解码。 } return queryData; } app.listen(3000,()=>{ console.log('demo server is starting at port 3000') })
在koa2中GET请求经过request接收,可是接受的方法有两种:query和querystring。
querystring:返回的是请求字符串
const Koa = require('koa'); const app = new Koa(); app.use(async(ctx)=>{ let url = ctx.url; //从request中接收get请求 let request = ctx.request; let req_query = request.query; let req_querystring = request.querystring; ctx.body = { url, req_query, req_querystring } }) app.listen(3000,()=>{ console.log('[demo] server is starting at port 3000') })
直接在ctx中获得get请求,ctx中也分为query和querystring
const Koa = require('koa'); const app = new Koa(); app.use(async(ctx)=>{ let url = ctx.url; //从上下文中直接获取 let ctx_query = ctx.query; let ctx_querystring = ctx.querystring; ctx.body = { url, ctx_query, ctx_querystring } }) app.listen(3000,()=>{ console.log('[demo] server is starting at port 3000') })
网站通常都有多个页面。经过ctx.request.path能够获取用户请求的路径,由此实现简单的路由
const Koa = require('koa'); const app = new Koa(); const main = ctx => { if (ctx.request.path !== '/') { ctx.response.type = 'html'; ctx.response.body = '<a href="/">Index Page</a>'; } else { ctx.response.body = 'Hello World'; } }; app.use(main); app.listen(3000);
原生路由用起来不太方便,咱们可使用封装好的koa-route模块
const Koa = require('koa'); const route = require('koa-route'); const app = new Koa(); const about = ctx =>{ ctx.response.type = 'html'; ctx.response.body = '<a href="/">Index Page</a>' }; const main = ctx =>{ ctx.response.body = 'Hello World' } app.use(route.get('/',main)); app.use(route.get('/about',about)) app.listen(3000);
根路径/
的处理函数是main
,/about
路径的处理函数是about
若是网站提供静态资源(图片、字体、样式、脚本......),为它们一个个写路由就很麻烦,也不必。koa-static模块封装了这部分的请求。
const Koa = require('koa'); const app = new Koa(); const path = require('path'); const serve = require('koa-static'); const main = serve(path.join(__dirname)); app.use(main); app.listen(3000);
const Koa = require('koa'); const path = require('path'); const static = require('koa-static'); const app = new Koa(); const staticPath = './static'; app.use(static(path.join(__dirname,staticPath))); app.use(async(ctx)=>{ ctx.body = 'hello static' }) app.listen(3000,()=>{ console.log('demo server is starting at port 3000') })
有些场合,服务器须要重定向(redirect)访问请求。好比,用户登陆之后,将他重定向到登陆前的页面。ctx.response.redirect()方法能够发出一个302跳转,将用户导向另外一个路由
const Koa = require('koa'); const route = require('koa-route'); const app = new Koa(); const redirect = ctx => { ctx.response.redirect('/'); }; const main = ctx => { ctx.response.body = 'Hello World'; }; app.use(route.get('/', main)); app.use(route.get('/redirect', redirect)); app.listen(3000);
访问 http://127.0.0.1:3000/redirect ,浏览器会将用户导向根路由。
要想实现原生路由,须要获得地址栏输入的路径,而后根据路径的不一样进行跳转。用ctx.request.url就能够实现
const Koa = require('koa') const app = new Koa() app.use( async ( ctx ) => { let url = ctx.request.url ctx.body = url }) app.listen(3000)
Koa的最大特点,也是最重要的一个设计,就是中间件(middleware)
const Koa = require('koa'); const app = new Koa(); const main = ctx => { console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`); ctx.response.body = 'Hello World'; }; app.use(main); app.listen(3000);
1534751382271 GET /
const Koa = require('koa'); const app = new Koa(); const logger = (ctx,next)=>{ console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`); next(); } const main = ctx =>{ ctx.response.body = '我是中间件' } app.use(logger); app.use(main); app.listen(3000);
多个中间件会造成一个栈结构(middle stack),以'先进后出'(first-in-last-out)的顺序执行
1. 最外层的中间件首先执行。 2. 调用next函数,把执行权交给下一个中间件。 3. ... 4. 最内层的中间件最后执行。 5. 执行结束后,把执行权交回上一层的中间件。 6. ... 7. 最外层的中间件收回执行权以后,执行next函数后面的代码。
``
const Koa = require('koa'); const app = new Koa(); const one = (ctx, next) => { console.log('>> one'); next(); console.log('<< one'); } const two = (ctx, next) => { console.log('>> two'); next(); console.log('<< two'); } const three = (ctx, next) => { console.log('>> three'); next(); console.log('<< three'); } app.use(one); app.use(two); app.use(three); app.listen(3000);
迄今为止,全部例子的中间件都是同步的,不包含异步操做。若是有异步操做(好比读取数据库),中间件就必须写成async函数
const fs = require('fs.promised'); const Koa = require('koa'); const app = new Koa(); const main = async function(ctx,next){ ctx.response.type = 'html'; ctx.response.body = await fs.readFile('./demos/template.html','utf-8') } app.use(main); app.listen(3000);
fs.readFile是一个异步操做,必须写成await fs.readFile(),而后中间件必须写成async函数
koa-compose模块能够将多个中间件合成为一个
const Koa = require('koa'); const compose = require('koa-compose'); const app = new Koa(); const logger = (ctx,next) =>{ console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`); next(); } const main = ctx=>{ ctx.response.body = 'hello world2' } const middlewares = compose([logger,main]) app.use(middlewares); app.listen(3000);
在代码中使用后,直接能够用ctx.request.body进行获取POST请求参数,中间件自动给咱们做了解析。
const Koa = require('koa'); const app = new Koa(); const bodyParser = require('koa-bodyparser'); app.use(bodyParser()) app.use(async(ctx)=>{ //当请求是get请求时,显示表单让用户填写 if(ctx.url === '/' && ctx.method === 'GET'){ let html = ` <h1>Koa2 request post demo</h1> <form method="POST" action='/'> <p>username</p> <input name="username"/> <br/> <p>age</p> <input name="age" /> <br/> <p>website</p> <input name="website" /> <button type="submit">submit</button> </form> `; ctx.body = html; //当请求是post请求时 }else if(ctx.url === '/' && ctx.method === 'POST'){ let postData = ctx.request.body; ctx.body = postData; }else{ //其余页面显示404页面 ctx.body = '<h1>404!</h1>' } }) app.listen(3000,()=>{ console.log('demo server is starting at port 3000') })
若是代码运行过程当中发生错误,咱们须要吧错误信息返回给用户。HTTP协定约定这时要返回500状态码。Koa提供了ctx.throw()方法,用来抛出错误,ctx.throw(500)错误。
const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.throw(500); }; app.use(main); app.listen(3000);
若是将ctx.response.status设置成404,就至关于ctx.throw(404),返回404错误
const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.response.status = 404; ctx.response.body = 'Page Not Found--404' } app.use(main); app.listen(3000);
为了方便处理错误,最好使用try...catch将其捕获。可是,为每一个中间件都写try...catch太麻烦,咱们可让最外层的中间件,负责全部中间件的错误处理
const Koa = require('koa'); const app = new Koa(); const handler = async (ctx, next) => { try { await next(); }catch (err){ ctx.response.status = err.statusCode || err.status || 500; ctx.response.body = { message: err.message }; } }; const main = ctx =>{ ctx.throw(500); } app.use(handler); app.use(main); app.listen(3000);
运行过程当中一旦出错,Koa会触发一个error事件。监听这个事件,也能够处理错误
const Koa = require('koa'); const app = new Koa(); const main = ctx => { ctx.throw(500); }; app.on('error', (err, ctx) => { console.error('server error', err); }); app.use(main); app.listen(3000);
须要注意的是,若是被try...catch捕获,就不会触发error事件。这时,必须调用ctx.app.emit(),手动释放error事件,才能让监听函数生效。
const Koa = require('koa'); const app = new Koa(); const handler = async (ctx,next) => { try { await next(); }catch (err) { ctx.response.status = err.statusCode || err.status || 500; ctx.response.type = 'html'; ctx.response.body = '<p>Something wrong,please contact administrator</p>'; ctx.app.emit('error',err,ctx); } } const main = ctx =>{ ctx.throw(500); } app.on('error',function(err){ console.log('logging error',err.message); console.log(err) }) app.use(handler); app.use(main); app.listen(3000);
main函数抛出错误,被handler函数捕获。catch代码块里面使用ctx.app.emit()手动释放error事件,才能让监听函数监听到
ctx.cookies 用来读写Cookie
const Koa = require('koa'); const app = new Koa(); const main = function(ctx){ const n = Number(ctx.cookies.get('view') || 0)+1; ctx.cookies.set('view',n); ctx.response.body = n + 'views'; } app.use(main); app.listen(3000);
ctx.cookies.set(name,value,[options]) 在上下文中写入cookie
const Koa = require('koa'); const app = new Koa(); app.use(async(ctx)=>{ if (ctx.url === '/index'){ ctx.cookies.set( 'myName', 'xiaoLiLi',{ domain: '127.0.0.1', //写cookie所在的域名 path: '/index', //写cookie所在的路径 maxAge: 1000*60*60*24, //cookie有效时长(一天) expires: new Date('2018-12-28'), //cookie失效时间 httpOnly: false, //是否只用于http请求中获取 overwrite: false //是否容许重写 } ) ctx.body = 'cookie is OK' }else{ if(ctx.cookies.get('myName')){ ctx.body = ctx.cookies.get('myName'); }else{ ctx.body = 'cookie is none'; } } }) app.listen(3000,()=>{ console.log('demo server is starting at port 3000'); })
Web应用离不开处理表单。本质上,表单就是POST方法发送到服务器的键值对。koa-body模块能够用来从POST请求的数据体里面提取键值对。
const Koa = require('koa'); const koaBody = require('koa-body'); const app = new Koa(); const main = async function(ctx) { const body = ctx.request.body; if(!body.name) ctx.throw(400,'.name required'); ctx.body = {name: body.name}; } app.use(koaBody()); app.use(main); app.listen(3000);
koa-body 模块还能够用来处理文件上传
const os = require('os'); const path = require('path'); const Koa = require('koa'); const fs = require('fs'); const koaBody = require('koa-body'); const app = new Koa(); const main = async function(ctx) { const tmpdir = os.tmpdir(); const filePaths = []; const files = ctx.request.body.files || {}; for (let key in files) { const file = files[key]; const filePath = path.join(tmpdir, file.name); const reader = fs.createReadStream(file.path); const writer = fs.createWriteStream(filePath); reader.pipe(writer); filePaths.push(filePath); } ctx.body = filePaths; }; app.use(koaBody({ multipart: true })); app.use(main); app.listen(3000);
打开另外一个命令行窗口,运行下面的命令,上传一个文件。注意,/path/to/file要更换为真实的文件路径。
新建view/index.ejs
<!DOCTYPE html> <html> <head> <title><%= title %></title>http://jspang.com/wp-admin/post.php?post=2760&action=edit# </head> <body> <h1><%= title %></h1> <p>EJS Welcome to <%= title %></p> </body> </html>
const Koa = require('koa'); const views = require('koa-views'); const path = require('path'); const app = new Koa(); //加载模板引擎 app.use(views(path.join(__dirname,'./view'),{ extension: 'ejs' })) app.use(async(ctx)=>{ let title = 'hello koa2'; await ctx.render('index',{ title }) }) app.listen(3000,()=>{ console.log('demo server is starting at port 3000') })
在koa2中GET请求经过request接收,可是接受的方法有两种:query和querystring。
querystring:返回的是请求字符串。
const etag = ctx.response.get(ETag);
设置响应头field到value
ctx.set('Cache-Control','no-cache')
用值val附加额外的标头field
ctx.append('Link', '<http://127.0.0.1/>');
用一个对象设置多个响应标头fields:
ctx.set({ 'Etag': '1234', 'Last-Modified': date });
获取响应Content-Type不含参数'charset'
const ct = ctx.type;
设置响应Content-Type经过mime字符串或文件扩展名
ctx.type = 'text/plain; charset=utf-8'; ctx.type = 'image/png'; ctx.type = '.png'; ctx.type = 'png';
注意: 在适当的状况下为你选择 charset, 好比 response.type = 'html' 将默认是 "utf-8". 若是你想覆盖 charset, 使用 ctx.set('Content-Type', 'text/html') 将响应头字段设置为直接值。
很是相似 ctx.request.is(). 检查响应类型是不是所提供的类型之一。这对于建立操纵响应的中间件特别有用。
例如, 这是一个中间件,能够削减除流以外的全部HTML响应。
const minify = require('html-minifier'); app.use(async (ctx, next) => { await next(); if (!ctx.response.is('html')) return; let body = ctx.body; if (!body || body.pipe) return; if (Buffer.isBuffer(body)) body = body.toString(); ctx.body = minify(body); });
执行 [302] 重定向到 url.
字符串 “back” 是特别提供Referrer支持的,当Referrer不存在时,使用 alt 或“/”。
ctx.redirect('back'); ctx.redirect('back', '/index.html'); ctx.redirect('/login'); ctx.redirect('http://google.com');
要更改 “302” 的默认状态,只需在该调用以前或以后分配状态。要变动主体请在此调用以后:
ctx.status = 301; ctx.redirect('/cart'); ctx.body = 'Redirecting to shopping cart';
将 Last-Modified 标头设置为适当的 UTC 字符串。您能够将其设置为 Date 或日期字符串。
ctx.response.lastModified = new Date();
设置包含 " 包裹的 ETag 响应, 请注意,没有相应的 response.etag getter。
ctx.response.etag = crypto.createHash('md5').update(ctx.body).digest('hex');