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');