Node.js를 사용하여 MVC 프레임워크를 구현하는 방법에 대한 간단한 예제 분석

黄舟
풀어 주다: 2017-08-07 11:51:08
원래의
1571명이 탐색했습니다.

아래 편집기에서는 Node.js를 사용하여 간단한 MVC 프레임워크를 구현하는 방법에 대한 기사를 제공합니다. 편집자님이 꽤 좋다고 생각하셔서 지금 공유하고 모두에게 참고용으로 드리고자 합니다. 편집기를 따라 살펴보겠습니다

Node.js를 사용하여 정적 리소스 서버 구축 기사에서 서버의 정적 리소스 요청 처리를 완료했지만 현재는 동적 요청에 응답할 수 없습니다. 클라이언트가 발행한 요청. 개인화된 콘텐츠를 반환합니다. 정적 리소스만으로 이러한 복잡한 웹 사이트 애플리케이션을 어떻게 지원할 수 있습니까? 이 기사에서는 <span style="font-family:NSimsun">Node<code><span style="font-family:NSimsun">Node</span>处理动态请求,以及如何搭建一个简易的 MVC 框架。因为前文已经详细介绍过静态资源请求如何响应,本文将略过所有静态部分。

一个简单的示例

先从一个简单示例入手,明白在 Node 中如何向客户端返回动态内容。

假设我们有这样的需求:

当用户访问<span style="font-family:NSimsun">/actors</span>时返回男演员列表页

当用户访问<span style="font-family:NSimsun">/actresses</span>를 사용하여 동적 요청을 처리하는 방법과 노드를 구축하는 방법을 소개합니다. 간단한 MVC 프레임워크. 이전 글에서는 정적 리소스 요청에 응답하는 방법을 자세히 소개했기 때문에 이번 글에서는 정적 부분을 모두 건너뛰겠습니다.

간단한 예

Node에서 클라이언트에 동적 콘텐츠를 반환하는 방법을 이해하려면 간단한 예부터 시작하세요.

simple request다음과 같은 요구 사항이 있다고 가정해 보겠습니다.

사용자가 를 방문하면 배우 목록 페이지로 돌아갑니다.<span style="font-family:NSimsun">/actors</span>

사용자가 를 방문하면 <span style="font-family:NSimsun">/actresses<strong></strong></span>는 여배우 목록을 반환합니다함수는 다음 코드로 완성할 수 있습니다.

const http = require(&#39;http&#39;);
const url = require(&#39;url&#39;);

http.createServer((req, res) => {
  const pathName = url.parse(req.url).pathname;
  if ([&#39;/actors&#39;, &#39;/actresses&#39;].includes(pathName)) {
    res.writeHead(200, {
      &#39;Content-Type&#39;: &#39;text/html&#39;
    });
    const actors = [&#39;Leonardo DiCaprio&#39;, &#39;Brad Pitt&#39;, &#39;Johnny Depp&#39;];
    const actresses = [&#39;Jennifer Aniston&#39;, &#39;Scarlett Johansson&#39;, &#39;Kate Winslet&#39;];
    let lists = [];
    if (pathName === &#39;/actors&#39;) {
      lists = actors;
    } else {
      lists = actresses;
    }

    const content = lists.reduce((template, item, index) => {
      return template + `<p>No.${index+1} ${item}</p>`;
    }, `<h1>${pathName.slice(1)}</h1>`);
    res.end(content);
  } else {
    res.writeHead(Node.js를 사용하여 MVC 프레임워크를 구현하는 방법에 대한 간단한 예제 분석);
    res.end(&#39;<h1>Requested page not found.</h1>&#39;)
  }
}).listen(9527);
로그인 후 복사

위 코드의 핵심 요청이 도착하면 해당 경로에 해당하는 논리적 처리가 있는지 확인합니다. 요청이 어떤 경로와도 일치하지 않으면 Node.js를 사용하여 MVC 프레임워크를 구현하는 방법에 대한 간단한 예제 분석를 반환합니다. 일치가 성공하면 해당 논리가 처리됩니다.

위 코드는 당연히 보편적이지 않으며, 경로 매칭 후보가 2개뿐이고(요청 방식도 구분되지 않았음), 데이터베이스와 템플릿 파일을 사용하지 않았다는 전제하에 코드는 다음과 같습니다. 벌써 좀 엉켜버렸어. 따라서 다음에는 데이터, 모델 및 성능을 분리하고 각각이 자신의 임무를 수행할 수 있는 간단한 MVC 프레임워크를 구축하겠습니다.

간단한 MVC 프레임워크 구축

MVC는 각각 다음을 의미합니다.

M: 모델(데이터)

V: 보기(성능)

C: 컨트롤러(로직)

Node, MVC 아키텍처 요청을 처리하는 과정은 다음과 같습니다.

요청이 서버에 도착합니다

서버가 요청을 라우팅에 전달합니다.

라우팅이 경로 매칭을 통해 요청을 해당 컨트롤러로 전달합니다

컨트롤러가 요청을 받습니다. 그리고 모델에 데이터를 요청합니다

모델은 필요한 데이터를 컨트롤러에 반환합니다

컨트롤러는 수신된 데이터를 재처리해야 할 수도 있습니다

컨트롤러는 처리된 데이터를 뷰에 전달합니다

뷰는 데이터와 템플릿을 기반으로 응답 콘텐츠를 생성합니다

서버가 이를 사용합니다. 콘텐츠가 클라이언트에 반환됩니다.

이를 기반으로 다음 모듈을 준비해야 합니다.

서버: 요청을 모니터링하고 응답합니다.

라우터: 요청을 올바른 컨트롤러에 전달합니다. 처리

컨트롤러: 비즈니스 로직을 실행하고 모델에서 데이터 검색, view


로 전달모델: 데이터 제공

view: html

제공 다음 디렉토리 생성:

-- server.js
-- lib
  -- router.js
-- views
-- controllers
-- models
로그인 후 복사

server

서버 생성 .js 파일:

const http = require(&#39;http&#39;);
const router = require(&#39;./lib/router&#39;)();

router.get(&#39;/actors&#39;, (req, res) => {
  res.end(&#39;Leonardo DiCaprio, Brad Pitt, Johnny Depp&#39;);
});

http.createServer(router).listen(9527, err => {
  if (err) {
    console.error(err);
    console.info(&#39;Failed to start server&#39;);
  } else {
    console.info(`Server started`);
  }
});
로그인 후 복사

먼저 이 파일을 무시하세요. 자세한 내용은 아래에서 완료될 모듈입니다. 여기에서 먼저 소개하고 요청이 도착한 후 처리를 위해 전달됩니다.

라우터 모듈

라우터 모듈은 실제로 한 가지만 완료하면 되며, 처리를 위해 올바른 컨트롤러에 요청을 전달하는 것이 이상적으로는 다음과 같이 사용할 수 있습니다.

const router = require(&#39;./lib/router&#39;)();
const actorsController = require(&#39;./controllers/actors&#39;);

router.use((req, res, next) => {
  console.info(&#39;New request arrived&#39;);
  next()
});

router.get(&#39;/actors&#39;, (req, res) => {
  actorsController.fetchList();
});

router.post(&#39;/actors/:name&#39;, (req, res) => {
  actorsController.createNewActor();
});
로그인 후 복사

일반적으로 우리는 그것을 희망합니다. 또한 라우팅을 지원합니다. 미들웨어 및 비미들웨어의 경우 요청이 도착한 후 라우터는 처리를 위해 이를 일치하는 미들웨어에 전달합니다. 미들웨어는 요청 개체 및 응답 개체에 액세스할 수 있는 기능입니다.

로그 추가, 오류 처리 등 모든 코드 실행

요청(req) 및 응답 개체(res) 수정 ), 예를 들어 req.url에서 쿼리 매개변수를 가져와서 req.query에 할당합니다

응답 종료


다음 미들웨어 호출(next)

참고:


다음과 같은 경우에 주의해야 합니다. 특정 미들웨어에서 최종 응답이 없거나 다음 미들웨어로 제어권을 넘기는 다음 메서드에 대한 호출이 없으면 요청이 중단됩니다.

__비라우팅 미들웨어__는 모든 요청과 일치하도록 다음과 같은 방식으로 추가됩니다.

router.use(fn);
로그인 후 복사

예를 들어 위의 예에서

router.use((req, res, next) => {
  console.info(&#39;New request arrived&#39;);
  next()
});
로그인 후 복사

__routing middleware__는 요청 방법과 경로를 정확히 일치시키기 위해 다음과 같은 방식으로 추가됩니다.

router.HTTP_METHOD(path, fn)
로그인 후 복사

정렬 후 프레임워크를 먼저 작성합니다. :

/lib/router.js

🎜🎜🎜🎜
const METHODS = [&#39;GET&#39;, &#39;POST&#39;, &#39;PUT&#39;, &#39;DELETE&#39;, &#39;HEAD&#39;, &#39;OPTIONS&#39;];

module.exports = () => {
  const routes = [];

  const router = (req, res) => {
    
  };

  router.use = (fn) => {
    routes.push({
      method: null,
      path: null,
      handler: fn
    });
  };

  METHODS.forEach(item => {
    const method = item.toLowerCase();
    router[method] = (path, fn) => {
      routes.push({
        method,
        path,
        handler: fn
      });
    };
  });
};
로그인 후 복사
🎜 위의 내용은 주로 use, get, post 및 기타 메서드를 라우터에 추가합니다. 이러한 메서드가 호출될 때마다 경로 규칙이 경로에 추가됩니다. 🎜🎜🎜참고:🎜🎜🎜Javascript의 함수는 호출할 수 있고 속성과 메서드도 포함하는 특수 개체입니다. 🎜🎜다음 초점은 라우터 기능에 있습니다. 🎜🎜req 객체에서 메서드와 경로 이름을 가져옵니다🎜.

依据 method、pathname 将请求与routes数组内各个 route 按它们被添加的顺序依次匹配

如果与某个route匹配成功,执行 route.handler,执行完后与下一个 route 匹配或结束流程 (后面详述)

如果匹配不成功,继续与下一个 route 匹配,重复3、4步骤


 const router = (req, res) => {
    const pathname = decodeURI(url.parse(req.url).pathname);
    const method = req.method.toLowerCase();
    let i = 0;

    const next = () => {
      route = routes[i++];
      if (!route) return;
      const routeForAllRequest = !route.method && !route.path;
      if (routeForAllRequest || (route.method === method && pathname === route.path)) {
        route.handler(req, res, next);
      } else {
        next();
      }
    }

    next();
  };
로그인 후 복사

对于非路由中间件,直接调用其 handler。对于路由中间件,只有请求方法和路径都匹配成功时,才调用其 handler。当没有匹配上的 route 时,直接与下一个route继续匹配。

需要注意的是,在某条 route 匹配成功的情况下,执行完其 handler 之后,还会不会再接着与下个 route 匹配,就要看开发者在其 handler 内有没有主动调用 next() 交出控制权了。

在__server.js__中添加一些route:


router.use((req, res, next) => {
  console.info(&#39;New request arrived&#39;);
  next()
});

router.get('/actors', (req, res) => {
  res.end('Leonardo DiCaprio, Brad Pitt, Johnny Depp');
});

router.get('/actresses', (req, res) => {
  res.end('Jennifer Aniston, Scarlett Johansson, Kate Winslet');
});

router.use((req, res, next) => {
  res.statusCode = Node.js를 사용하여 MVC 프레임워크를 구현하는 방법에 대한 간단한 예제 분석;
  res.end();
});
로그인 후 복사

每个请求抵达时,首先打印出一条 log,接着匹配其他route。当匹配上 actors 或 actresses 的 get 请求时,直接发回演员名字,并不需要继续匹配其他 route。如果都没匹配上,返回 Node.js를 사용하여 MVC 프레임워크를 구현하는 방법에 대한 간단한 예제 분석。

在浏览器中依次访问 http://localhost:9527/erwe、http://localhost:9527/actors、http://localhost:9527/actresses 测试一下:

Node.js를 사용하여 MVC 프레임워크를 구현하는 방법에 대한 간단한 예제 분석

<span style="font-family:NSimsun">network</span> 中观察到的结果符合预期,同时后台命令行中也打印出了三条 <span style="font-family:NSimsun">New request arrived</span>语句。

接下来继续改进 router 模块。

首先添加一个 router.all 方法,调用它即意味着为所有请求方法都添加了一条 route:


router.all = (path, fn) => {
    METHODS.forEach(item => {
      const method = item.toLowerCase();
      router[method](path, fn);
    })
  };
로그인 후 복사

接着,添加错误处理。

/lib/router.js


const defaultErrorHander = (err, req, res) => {
  res.statusCode = 500;
  res.end();
};

module.exports = (errorHander) => {
  const routes = [];

  const router = (req, res) => {
      ...
    errorHander = errorHander || defaultErrorHander;

    const next = (err) => {
      if (err) return errorHander(err, req, res);
      ...
    }

    next();
  };
로그인 후 복사

server.js


...
const router = require(&#39;./lib/router&#39;)((err, req, res) => {
  console.error(err);
  res.statusCode = 500;
  res.end(err.stack);
});
...
로그인 후 복사

默认情况下,遇到错误时会返回 500,但开发者使用 router 模块时可以传入自己的错误处理函数将其替代。

修改一下代码,测试是否能正确执行错误处理:


router.use((req, res, next) => {
  console.info(&#39;New request arrived&#39;);
  next(new Error(&#39;an error&#39;));
});
로그인 후 복사

这样任何请求都应该返回 500:

error stack

继续,修改 route.path 与 pathname 的匹配规则。现在我们认为只有当两字符串相等时才让匹配通过,这没有考虑到 url 中包含路径参数的情况,比如:

localhost:9527/actors/Leonardo
로그인 후 복사

router.get(&#39;/actors/:name&#39;, someRouteHandler);
로그인 후 복사

这条route应该匹配成功才是。

新增一个函数用来将字符串类型的 route.path 转换成正则对象,并存入 route.pattern:


const getRoutePattern = pathname => {
 pathname = &#39;^&#39; + pathname.replace(/(\:\w+)/g, &#39;\(\[a-zA-Z0-9-\]\+\\s\)&#39;) + &#39;$&#39;;
 return new RegExp(pathname);
};
로그인 후 복사

这样就可以匹配上带有路径参数的url了,并将这些路径参数存入 req.params 对象:


    const matchedResults = pathname.match(route.pattern);
    if (route.method === method && matchedResults) {
      addParamsToRequest(req, route.path, matchedResults);
      route.handler(req, res, next);
    } else {
      next();
    }
로그인 후 복사


const addParamsToRequest = (req, routePath, matchedResults) => {
  req.params = {};
  let urlParameterNames = routePath.match(/:(\w+)/g);
  if (urlParameterNames) {
    for (let i=0; i < urlParameterNames.length; i++) {
      req.params[urlParameterNames[i].slice(1)] = matchedResults[i + 1];
    }
  }
}
로그인 후 복사

添加个 route 测试一下:


router.get(&#39;/actors/:year/:country&#39;, (req, res) => {
  res.end(`year: ${req.params.year} country: ${req.params.country}`);
});
로그인 후 복사

访问<span style="font-family:NSimsun">http://localhost:9527/actors/1990/China</span>试试:

url parameters

router 模块就写到此,至于查询参数的格式化以及获取请求主体,比较琐碎就不试验了,需要可以直接使用 bordy-parser 等模块。

现在我们已经创建好了router模块,接下来将 route handler 内的业务逻辑都转移到 controller 中去。

修改__server.js__,引入 controller:


...
const actorsController = require(&#39;./controllers/actors&#39;);
...
router.get(&#39;/actors&#39;, (req, res) => {
  actorsController.getList(req, res);
});

router.get(&#39;/actors/:name&#39;, (req, res) => {
  actorsController.getActorByName(req, res);
});

router.get(&#39;/actors/:year/:country&#39;, (req, res) => {
  actorsController.getActorsByYearAndCountry(req, res);
});
...
로그인 후 복사

新建__controllers/actors.js__:


const actorsTemplate = require(&#39;../views/actors-list&#39;);
const actorsModel = require(&#39;../models/actors&#39;);

exports.getList = (req, res) => {
  const data = actorsModel.getList();
  const htmlStr = actorsTemplate.build(data);
  res.writeHead(200, {
    &#39;Content-Type&#39;: &#39;text/html&#39;
  });
  res.end(htmlStr);
};

exports.getActorByName = (req, res) => {
  const data = actorsModel.getActorByName(req.params.name);
  const htmlStr = actorsTemplate.build(data);
  res.writeHead(200, {
    &#39;Content-Type&#39;: &#39;text/html&#39;
  });
  res.end(htmlStr);
};

exports.getActorsByYearAndCountry = (req, res) => {
  const data = actorsModel.getActorsByYearAndCountry(req.params.year, req.params.country);
  const htmlStr = actorsTemplate.build(data);
  res.writeHead(200, {
    &#39;Content-Type&#39;: &#39;text/html&#39;
  });
  res.end(htmlStr);
};
로그인 후 복사

在 controller 中同时引入了 view 和 model, 其充当了这二者间的粘合剂。回顾下 controller 的任务:

controller 收到请求,向 model 索要数据
model 给 controller 返回其所需数据
controller 可能需要对收到的数据做一些再加工
controller 将处理好的数据交给 view

在此 controller 中,我们将调用 model 模块的方法获取演员列表,接着将数据交给 view,交由 view 生成呈现出演员列表页的 html 字符串。最后将此字符串返回给客户端,在浏览器中呈现列表。

从 model 中获取数据

通常 model 是需要跟数据库交互来获取数据的,这里我们就简化一下,将数据存放在一个 json 文件中。

/models/test-data.json


[
  {
    "name": "Leonardo DiCaprio",
    "birth year": 1974,
    "country": "US",
    "movies": ["Titanic", "The Revenant", "Inception"]
  },
  {
    "name": "Brad Pitt",
    "birth year": 1963,
    "country": "US",
    "movies": ["Fight Club", "Inglourious Basterd", "Mr. & Mrs. Smith"]
  },
  {
    "name": "Johnny Depp",
    "birth year": 1963,
    "country": "US",
    "movies": ["Edward Scissorhands", "Black Mass", "The Lone Ranger"]
  }
]
로그인 후 복사

接着就可以在 model 中定义一些方法来访问这些数据。

models/actors.js


const actors = require(&#39;./test-data&#39;);

exports.getList = () => actors;

exports.getActorByName = (name) => actors.filter(actor => {
  return actor.name == name;
});

exports.getActorsByYearAndCountry = (year, country) => actors.filter(actor => {
  return actor["birth year"] == year && actor.country == country;
});
로그인 후 복사

当 controller 从 model 中取得想要的数据后,下一步就轮到 view 发光发热了。view 层通常都会用到模板引擎,如 dust 等。同样为了简化,这里采用简单替换模板中占位符的方式获取 html,渲染得非常有限,粗略理解过程即可。

创建 /views/actors-list.js:


const actorTemplate = `
<h1>{name}</h1>
<p><em>Born: </em>{contry}, {year}</p>
<ul>{movies}</ul>
`;

exports.build = list => {
  let content = &#39;&#39;;
  list.forEach(actor => {
    content += actorTemplate.replace(&#39;{name}&#39;, actor.name)
          .replace(&#39;{contry}&#39;, actor.country)
          .replace(&#39;{year}&#39;, actor["birth year"])
          .replace(&#39;{movies}&#39;, actor.movies.reduce((moviesHTML, movieName) => {
            return moviesHTML + `<li>${movieName}</li>`
          }, &#39;&#39;));
  });
  return content;
};
로그인 후 복사

在浏览器中测试一下:

test mvc

至此,就大功告成啦!

위 내용은 Node.js를 사용하여 MVC 프레임워크를 구현하는 방법에 대한 간단한 예제 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿