전단 면접 - 단판 koa 실현

목차
  • koa 의 사용
  • koa 소스 코드 를 간단하게 읽 어 보 세 요
  • ctx 가 무언 가 를 마 운 트 했 습 니 다
  • next 가 구축 한 양파 모형
  • 중간 부품 은 비동기 코드 를 포함 하여 어떻게 정확 한 집행 을 보장 합 니까
  • next 를 여러 번 호출 하여 혼란 을 초래 하 는 문 제 를 해결 합 니 다
  • 이벤트 구동 을 바탕 으로 이상 처리
  • koa 의 사용
    koa 의 사용 은 매우 간단 합 니 다. 의존 도 를 도입 하여 작성 합 니 다.
    const Koa = require('koa')
    let app = new Koa()
    app.use((ctx, next) => {
      console.log(ctx)
    })
    app.listen(4000)

    그리고 브 라 우 저 에서 열기 http://127.0.0.1:4000 하면 접근 할 수 있 습 니 다.
    반환 이 지정 되 지 않 았 다 면 body koa 는 기본적으로 처리 되 었 습 니 다 Not Foundctx
    코드 를 더 확장 해서 ctx 위 에 어떤 것 이 있 는 지 보 세 요.
    // ...
      console.log(ctx)
      console.log('native req ----') // node   req
      console.log(ctx.req.url)
      console.log(ctx.request.req.url)
      console.log('koa request ----') // koa   request
      console.log(ctx.url)
      console.log(ctx.request.url)
      // native req ----
      // /
      // /
      // koa request ----
      // /
      // /
    // ...

    이상 코드 는 창고 에 보관 하고 스스로 찾 습 니 다.
    koa 홈 페이지 에 서 는 ctx 에 일련의 requestresponse 속성 별명 을 마 운 트 했다 는 설명 이 있 습 니 다.
    ctx = {}
    ctx.request = {}
    ctx.response = {}
    ctx.req = ctx.request.req = req
    ctx.res = ctx.response.res = res
    // ctx.url     ctx.request.url

    next
    아래 코드 는 창고 에 보관 하고 스스로 찾 습 니 다.
    next 를 사용 해 보 세 요.
    const Koa = require('koa')
    let app = new Koa()
    app.use((ctx, next) => {
      console.log(1)
      next()
      console.log(2)
    })
    app.use((ctx, next) => {
      console.log(3)
      next()
      console.log(4)
    })
    app.use((ctx, next) => {
      console.log(5)
      next()
      console.log(6)
    })
    app.listen(4000)
    // 1
    // 3
    // 5
    // 6
    // 4
    // 2

    위의 코드 인쇄 결 과 를 통 해 알 수 있 듯 이 next 의 역할 은 바로 자리 표시 자 를 만 드 는 것 이다.다음 과 같은 형식 으로 볼 수 있다.
    app.use((ctx, next) => {
      console.log(1)
      app.use((ctx, next) => {
        console.log(3)
        app.use((ctx, next) => {
          console.log(5)
          next()
          console.log(6)
        })
        console.log(4)
      })
      console.log(2)
    })

    이것 이 바로 양파 모형 이다.
    만약 어떤 중간 부품 에 비동기 코드 가 있다 면?
    const Koa = require('koa')
    let app = new Koa()
    //     
    const logger = () => {
      return new Promise((resolve, reject) => {
        setTimeout(_ => {
          console.log('logger')
          resolve()
        }, 1000)
      })
    }
    app.use((ctx, next) => {
      console.log(1)
      next()
      console.log(2)
    })
    app.use(async (ctx, next) => {
      console.log(3)
      await logger()
      next()
      console.log(4)
    })
    app.use((ctx, next) => {
      console.log(5)
      next()
      console.log(6)
    })
    app.listen(4000)
    // 1
    // 3
    // 2
    //   1s
    // logger
    // 5
    // 6
    // 4

    이때 인쇄 결 과 는 우리 가 예상 한 결과 가 아니 라 우리 가 기대 하 는 것 은 1 -> 3 -> 1s logger -> 5-> 6-> 4 ->2 이다.
    이때 우 리 는 next 앞 에 하 나 를 더 해 야 한다 await.
    // ...
    app.use(async (ctx, next) => {
      console.log(1)
      await next()
      console.log(2)
    })
    // ...

    koa 소스 코드 간단하게 읽 기koa 더 작고 표 현 력 이 있 으 며 건장 한 web 개발 구조 가 되도록 노력 합 니 다.
    그 소스 코드 도 매우 가 볍 고 읽 기 쉽다.
    핵심 파일 네 개
  • application.js: 간단 한 포장 http.createServer() 및 통합 context.js
  • context.js: 대리 및 통합 request.jsresponse.js
  • request.js: 원생 req 을 바탕 으로 포장 하 는 것 이 더 좋다
  • response.js: 원생 res 을 바탕 으로 포장 하 는 것 이 더 좋다
  • 원본 탐색 시작
    아래 와 관련 된 코드 는 창고 에 저장 되 어 있 으 며, 필요 한 것 은 스스로 찾 습 니 다.
    koa 는 ES6 로 이 루어 졌 는데 주로 두 가지 핵심 방법 app.listen()app.use((ctx, next) =< { ... }) 이다.
    먼저 application.js 에서 실현 app.listen()
    const http = require('http')
    class Koa {
      constructor () {
        // ...
      }  
      //       
      handleRequest (req, res) {
        // ...
      }  
      listen (...args) {
        let server = http.createServer(this.handleRequest.bind(this))
        server.listen(...args)
      }  
    }
    module.exports = Koa

    뭐 공부 해요?
    위의 간단 한 사용 ctx 에서 볼 수 있 습 니 다.
    ctx = {}
    ctx.request = {}
    ctx.response = {}
    ctx.req = ctx.request.req = req
    ctx.res = ctx.response.res = res
    ctx.xxx = ctx.request.xxx
    ctx.yyy = ctx.response.yyy

    우 리 는 상기 몇 개의 대상 이 필요 하 며, 최종 적 으로 모두 ctx 대상 에 대리 되 어야 한다.
    세 파일 만 들 기 context.js/request.js/response.jsrequest.js 내용
    const url = require('url')
    let request = {}
    module.exports = request
    response.js 내용
    let response = {}
    module.exports = response
    context.js 내용
    let context = {}
    
    module.exports = context
    application.js 에 위의 세 개의 파일 을 도입 하여 인 스 턴 스 에 올 립 니 다.
    const context = require('./context')
    const request = require('./request')
    const response = require('./response')
    class Koa extends Emitter{
      constructor () {
        super()
        // Object.create      
        this.context = Object.create(context)
        this.request = Object.create(request)
        this.response = Object.create(response)
      }
    }

    등 호 를 직접 사용 하여 값 을 부여 할 수 없 기 때문에 변수 속성 을 수정 할 때 원본 변 수 를 직접 변경 합 니 다. 대상 이 같은 메모리 공간 을 참조 하기 때 문 입 니 다.
    그래서 Object.create 방법 으로 의존 을 차단 하 는 것 은
    function create (parentPrototype) {
      function F () {}
      F.prototype = parentPrototype
      return new F()
    }

    그리고 사용자 요청 을 처리 하고 ctx 에서 대리 request / response
      //      
      createContext (req, res) {
        let ctx = this.context
        //   
        ctx.request = this.request
        ctx.req = ctx.request.req = req
        //   
        ctx.response = this.response
        ctx.res = ctx.response.res = res
        return ctx
      }
      handleRequest (req, res) {
        let ctx = this.createContext(req, res)
        return ctx
      }
    context.js 에서 __defineGetter__ / __defineSetter__ 를 사용 하여 대 리 를 실현 하 는데 그 는 Object.defineProperty() 방법의 변종 으로 따로 설정 get/set 할 수 있 고 덮어 쓰 지 않 습 니 다.
    let context = {}
    //      
    function defineGetter (key, property) {
      context.__defineGetter__ (property, function () {
        return this[key][property]
      })
    }
    //      
    function defineSetter (key, property) {
      context.__defineSetter__ (property, function (val) {
        this[key][property] = val
      })
    }
    //    request
    defineGetter('request', 'path')
    defineGetter('request', 'url')
    defineGetter('request', 'query')
    //    response
    defineGetter('response', 'body')
    defineSetter('response', 'body')
    module.exports = context
    request.js 에서 ES5 가 제공 하 는 속성 접근 기 를 사용 하여 패 키 징 을 실현 합 니 다.
    const url = require('url')
    let request = {
      get url () {
        return this.req.url //    this       ctx.request
      },
      get path () {
        let { pathname } = url.parse(this.req.url)
        return pathname
      },
      get query () {
        let { query } = url.parse(this.req.url, true)
        return query
      }
      // ...     
    }
    module.exports = request
    response.js 에서 ES5 가 제공 하 는 속성 접근 기 를 사용 하여 패 키 징 을 실현 합 니 다.
    let response = {
      set body (val) {
        this._body = val
      },
      get body () {
        return this._body //    this       ctx.response
      }
      // ...     
    }
    module.exports = response

    이상 은 패키지 request/response 를 실현 하고 ctx 에 대리 되 었 습 니 다.
    ctx = {}
    ctx.request = {}
    ctx.response = {}
    ctx.req = ctx.request.req = req
    ctx.res = ctx.response.res = res
    ctx.xxx = ctx.request.xxx
    ctx.yyy = ctx.response.yyy

    next 구 축 된 양파 모형
    다음은 koa 의 두 번 째 방법 app.use((ctx, next) =< { ... }) 을 실현 합 니 다.
    use 에는 cookie、session、static... 등 처리 함수 가 하나씩 저장 되 어 있 으 며 형식 으로 실 행 됩 니 다.
      constructor () {
        // ...
        //        
        this.middlewares = []
      }
      //      
      use (fn) {
        this.middlewares.push(fn)
      }

    사용자 요청 을 처리 할 때 등 록 된 미들웨어 를 실행 하 기 를 기대 합 니 다.
      //      
      compose (middlewares, ctx) {
        function dispatch (index) {
          //             
          //        promise
          if (index === middlewares.length) return Promise.resolve()
          let middleware = middlewares[index]
          //          ,       ,       await
          //       promise
          return Promise.resolve(middleware(ctx, () => dispatch(index + 1)))
        }
        return dispatch(0)
      }
      //       
      handleRequest (req, res) {
        let ctx = this.createContext(req, res)
        this.compose(this.middlewares, ctx)
        return ctx
      }

    이상 의 dispatch 교체 함 수 는 여러 곳 에서 활용 되 는데 예 를 들 어 koa 의 핵심 이다.
    미들웨어 에 비동기 코드 가 포함 되 어 있 는데 어떻게 정확 한 집행 을 보장 합 니까?
    되 돌아 오 는 promise 는 주로 미들웨어 에 비동기 코드 가 들 어 있 는 상황 을 처리 하기 위해 서 입 니 다.
    모든 미들웨어 가 실 행 된 후 페이지 를 렌 더 링 해 야 합 니 다.
      //       
      handleRequest (req, res) {
        let ctx = this.createContext(req, res)
        res.statusCode = 404 //   404    body    
        let ret = this.compose(this.middlewares, ctx)
        ret.then(_ => {
          if (!ctx.body) { //    body
            res.end(`Not Found`)
          } else if (ctx.body instanceof Stream) { //  
            res.setHeader('Content-Type', 'text/html;charset=utf-8')
            ctx.body.pipe(res)
          } else if (typeof ctx.body === 'object') { //   
            res.setHeader('Content-Type', 'text/josn;charset=utf-8')
            res.end(JSON.stringify(ctx.body))
          } else { //    
            res.setHeader('Content-Type', 'text/html;charset=utf-8')
            res.end(ctx.body)
          }
        })
        return ctx
      }

    여러 가지 상황 을 고려 하여 겸용 해 야 한다.
    next 를 여러 번 호출 하여 혼란 을 야기 하 는 문 제 를 해결 하 다.
    이상 코드 를 통 해 다음 과 같은 테스트 를 진행 합 니 다.
    실행 결 과 는?
    // 1 => 3 =>1s,logger => 4
    //   => 3 =>1s,logger => 4  => 2

    우리 의 기대 에 결코 만족 하지 않 는 다.
    실행 과정 은 다음 과 같 기 때문이다.
    두 번 째 단계 에서 들 어 오 는 i 값 은 1 입 니 다. 첫 번 째 미들웨어 함수 내부 이기 때 문 입 니 다. 그러나 copose 내부 의 index 는 이미 2 이기 때문에 i < 2, 그래서 잘못 보 고 했 습 니 다. 한 미들웨어 함수 내부 에서 next 함 수 를 여러 번 호출 할 수 없 음 을 알 수 있 습 니 다.
    해결 방법 은 fllag 를 양파 모델 로 실행 중인 함수 미들웨어 의 아래 표 시 를 기록 하 는 것 입 니 다. 한 미들웨어 에서 next 를 두 번 실행 하면 index 는 fllag 보다 작 습 니 다.
      /**
       *      
       * @param {Array} middlewares 
       * @param {context} ctx 
       */ 
      compose (middlewares, ctx) {
        let flag = -1
        function dispatch (index) {
          // 3)flag            
          // 3.1)          next  index   flag
          // if (index <= flag) return Promise.reject(new Error('next() called multiple times'))
          flag = index
          // 2)      :     
          // 2.1)       promise
          if (index === middlewares.length) return Promise.resolve()
          // 1)         ,       ,       await
          // 1.1)      promise
          let middleware = middlewares[index]
          return Promise.resolve(middleware(ctx, () => dispatch(index + 1)))
        }
        return dispatch(0)
      }

    이벤트 기반 드라이브 처리 이상
    중간 부품 에 나타 난 이상 을 어떻게 처리 합 니까?Node 사건 으로 구동 되 기 때문에 우 리 는 events 모듈 만 계승 하면 된다.
    const Emitter = require('events')
    class Koa extends Emitter{
      // ...
      //       
      handleRequest (req, res) {
        // ...
        let ret = this.compose(this.middlewares, ctx)
        ret.then(_ => {
          // ...
        }).catch(err => { //       
          this.emit('error', err)
        })
        return ctx
      }  
    }

    그리고 위 에서 이상 포획 을 할 때 아래 와 같이 사용 하면 됩 니 다.
    const Koa = require('./src/index')
    
    let app = new Koa()
    
    app.on('error', err => {
      console.log(err)
    })

    테스트 사례 코드 는 창고 에 저장 되 어 있 으 며 필요 한 자체 추출 입 니 다.
    총결산
    이상 을 통 해 우 리 는 간단 하고 쉬 운 KOA 파일 을 실 현 했 고 request/response.js 파일 은 더 많은 속성 을 지원 해 야 합 니 다.
    전체 코드 와 테스트 용례 는 @ careteen / koa 에 저장 되 어 있 으 며, 관심 이 있 으 면 디 버 깅 하 러 갈 수 있 습 니 다.

    좋은 웹페이지 즐겨찾기