chore(learn): Applied MDX format to Chinese curriculum files (#40462)

This commit is contained in:
Randell Dawson
2020-12-16 13:07:30 +05:30
committed by GitHub
parent 873fce02a2
commit 9ce4a02a41
1665 changed files with 58741 additions and 88042 deletions
@@ -1,13 +1,14 @@
---
id: 587d7fb1367417b2b2512bf4
title: 使用链中间件来创建时间服务器
challengeType: 2
forumTopicId: 301510
title: 使用链中间件来创建时间服务器
---
## Description
<section id='description'>
使用<code>app.METHOD(path, middlewareFunction)</code>可以将中间件挂载到指定的路由。中间件也可以在路由定义中链接。
# --description--
使用`app.METHOD(path, middlewareFunction)`可以将中间件挂载到指定的路由。中间件也可以在路由定义中链接。
请看以下示例:
```js
@@ -20,42 +21,51 @@ app.get('/user', function(req, res, next) {
```
此方法可用于将服务操作拆分为较小的单元。这样可以让应用拥有更好的结构,以便于在不同的位置上复用代码。此方法还可用于对数据执行某些验证。在每一个中间件堆栈中,你都可以阻止当前链的执行,并将控制权传递给专门设计用于处理错误的函数。或者你可以将控制权传递给下一个匹配的路径,以处理特殊情况。我们将在高级 Express 章节中看到。
</section>
## Instructions
<section id='instructions'>
在路由<code>app.get('/now', ...)</code>链中,在中间件函数中,你应该在<code>req.time</code>里将当前时间添加到请求对象中。你可以使用<code>new Date().toString()</code>。在处理函数中,使用<code>{time: req.time}</code>结构的 JSON 对象来响应。
# --instructions--
在路由`app.get('/now', ...)`链中,在中间件函数中,你应该在`req.time`里将当前时间添加到请求对象中。你可以使用`new Date().toString()`。在处理函数中,使用`{time: req.time}`结构的 JSON 对象来响应。
提示: 如果不链接中间件,测试将不能通过。如果将中间件函数挂载在其他地方,即使输出结果正确,测试也会失败。
</section>
## Tests
<section id='tests'>
# --hints--
```yml
tests:
- text: 路由 /now 应该已经挂载了中间件
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/_api/chain-middleware-time'').then(data => { assert.equal(data.stackLength, 2, ''"/now" route has no mounted middleware''); }, xhr => { throw new Error(xhr.responseText); })'
- text: 路由 /now 应该返回一个从现在开始 +/-20 秒的时间
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/_api/chain-middleware-time'').then(data => { var now = new Date(); assert.isAtMost(Math.abs(new Date(data.time) - now), 20000, ''the returned time is not between +- 20 secs from now''); }, xhr => { throw new Error(xhr.responseText); })'
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
路由 /now 应该已经挂载了中间件
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/chain-middleware-time').then(
(data) => {
assert.equal(
data.stackLength,
2,
'"/now" route has no mounted middleware'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
路由 /now 应该返回一个从现在开始 +/-20 秒的时间
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/chain-middleware-time').then(
(data) => {
var now = new Date();
assert.isAtMost(
Math.abs(new Date(data.time) - now),
20000,
'the returned time is not between +- 20 secs from now'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
@@ -1,56 +1,70 @@
---
id: 587d7fb2367417b2b2512bf8
title: 从 POST 请求中获取数据
challengeType: 2
forumTopicId: 301511
title: 从 POST 请求中获取数据
---
## Description
<section id='description'>
在路径<code>/name</code>处挂载一个 POST 处理方法。和前面一样。我们已经在 html 首页准备了一份表格。它将提交与练习 10 相同的数据(查询字符串)。如果 body-parser 正确配置好了,你就可以在<code>req.body</code>对象中找到请求的参数。来看看一个常规的请求 /library 例子:
<blockquote>route: POST '/library'<br>urlencoded_body: userId=546&bookId=6754 <br>req.body: {userId: '546', bookId: '6754'}</blockquote>
和前面一样响应一个 JSON 对象<code>{name: 'firstname lastname'}</code>。你可以使用首页应用提供的 html 表单,来测试你的 API 是否正常工作。
# --description--
在路径`/name`处挂载一个 POST 处理方法。和前面一样。我们已经在 html 首页准备了一份表格。它将提交与练习 10 相同的数据(查询字符串)。如果 body-parser 正确配置好了,你就可以在`req.body`对象中找到请求的参数。来看看一个常规的请求 /library 例子:
<blockquote>route: POST '/library'<br>urlencoded_body: userId=546&#x26;bookId=6754 <br>req.body: {userId: '546', bookId: '6754'}</blockquote>
和前面一样响应一个 JSON 对象`{name: 'firstname lastname'}`。你可以使用首页应用提供的 html 表单,来测试你的 API 是否正常工作。
提示: 除了 GET 和 POST,还有其他几种 http 方法。按照惯例,http 动词之间有对应关系,它们分别对应你在服务端执行的某种操作,传统的对应关系:
POST (有时候是 PUT) - 使用请求发送信息,以创建新资源,
GET - 读取已存在的资源,不用修改它,
PUT 或者 PATCH (有时候是 POST) - 发送数据,以更新资源,
DELETE => 删除一个资源。
还有一些其他方法,常用于与服务进行交互。除了 GET 之外,上面列出的所有方法都可以负载数据(换言之,数据都能在请求体中找到)。也可以使用 body-parser 来正常工作。
</section>
## Instructions
<section id='instructions'>
# --hints--
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 测试 1:你的 API 应该使用正确的名称来响应
testString: 'getUserInput => $.post(getUserInput(''url'') + ''/name'', {first: ''Mick'', last: ''Jagger''}).then(data => { assert.equal(data.name, ''Mick Jagger'', ''Test 1: "POST /name" route does not behave as expected'') }, xhr => { throw new Error(xhr.responseText); })'
- text: 测试 2:你的 API 应该使用正确的名称来响应
testString: 'getUserInput => $.post(getUserInput(''url'') + ''/name'', {first: ''Keith'', last: ''Richards''}).then(data => { assert.equal(data.name, ''Keith Richards'', ''Test 2: "POST /name" route does not behave as expected'') }, xhr => { throw new Error(xhr.responseText); })'
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
测试 1:你的 API 应该使用正确的名称来响应
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.post(getUserInput('url') + '/name', { first: 'Mick', last: 'Jagger' }).then(
(data) => {
assert.equal(
data.name,
'Mick Jagger',
'Test 1: "POST /name" route does not behave as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
测试 2:你的 API 应该使用正确的名称来响应
```js
(getUserInput) =>
$.post(getUserInput('url') + '/name', {
first: 'Keith',
last: 'Richards'
}).then(
(data) => {
assert.equal(
data.name,
'Keith Richards',
'Test 2: "POST /name" route does not behave as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
@@ -1,50 +1,59 @@
---
id: 587d7fb2367417b2b2512bf6
title: 从客户端获取查询参数输入
challengeType: 2
forumTopicId: 301512
title: 从客户端获取查询参数输入
---
## Description
<section id='description'>
从客户端获取输入的另一种常见方式是使用查询字符串对路由路径中的数据进行编码。查询字符串使用标记 (?) 分隔,并且包含一对<code>field=value</code>。每一对键值使用符号 (&) 分隔。Express 能够从查询字符串中分析这些数据,并且把它放到<code>req.query</code>对象中。有些字符不能在出现在 URL 中,它们在发送前必须以<a href='https://en.wikipedia.org/wiki/Percent-encoding' target='_blank'>不同的格式</a>进行编码。如果你使用来自 JavaScript 的 API,你可以使用特定的方法来编码/解码这些字符。
<blockquote>route_path: '/library'<br>actual_request_URL: '/library?userId=546&bookId=6754' <br>req.query: {userId: '546', bookId: '6754'}</blockquote>
</section>
# --description--
## Instructions
<section id='instructions'>
构建一个 API ,使用路由挂载<code>GET /name</code>。使用一个 JSON 文件来响应,它的结构是这样的:<code>{ name: 'firstname lastname'}</code>。名字和姓氏参数应该编码在查询参数中,举个例子:<code>?first=firstname&last=lastname</code>.
提示: 在下面的练习中,我们将从相同的<code>/name</code>路由路径中 POST 请求接收数据。如果你愿意,你可以使用<code>app.route(path).get(handler).post(handler)</code>这中写法,此语法允许你在同一路径路由上链接不同的 HTTP 动词处理函数。可以节省一点打字时间,并且可以让代码看起来更清晰。
</section>
从客户端获取输入的另一种常见方式是使用查询字符串对路由路径中的数据进行编码。查询字符串使用标记 (?) 分隔,并且包含一对`field=value`。每一对键值使用符号 (&) 分隔。Express 能够从查询字符串中分析这些数据,并且把它放到`req.query`对象中。有些字符不能在出现在 URL 中,它们在发送前必须以[不同的格式](https://en.wikipedia.org/wiki/Percent-encoding)进行编码。如果你使用来自 JavaScript 的 API,你可以使用特定的方法来编码/解码这些字符。
## Tests
<section id='tests'>
<blockquote>route_path: '/library'<br>actual_request_URL: '/library?userId=546&#x26;bookId=6754' <br>req.query: {userId: '546', bookId: '6754'}</blockquote>
```yml
tests:
- text: 测试 1:你的 API 应该使用正确的名称来响应
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/name?first=Mick&last=Jagger'').then(data => { assert.equal(data.name, ''Mick Jagger'', ''Test 1: "GET /name" route does not behave as expected'') }, xhr => { throw new Error(xhr.responseText); })'
- text: 测试 2:你的 API 应该使用正确的名称来响应
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/name?last=Richards&first=Keith'').then(data => { assert.equal(data.name, ''Keith Richards'', ''Test 2: "GET /name" route does not behave as expected'') }, xhr => { throw new Error(xhr.responseText); })'
# --instructions--
```
构建一个 API ,使用路由挂载`GET /name`。使用一个 JSON 文件来响应,它的结构是这样的:`{ name: 'firstname lastname'}`。名字和姓氏参数应该编码在查询参数中,举个例子:`?first=firstname&last=lastname`.
</section>
提示: 在下面的练习中,我们将从相同的`/name`路由路径中 POST 请求接收数据。如果你愿意,你可以使用`app.route(path).get(handler).post(handler)`这中写法,此语法允许你在同一路径路由上链接不同的 HTTP 动词处理函数。可以节省一点打字时间,并且可以让代码看起来更清晰。
## Challenge Seed
<section id='challengeSeed'>
# --hints--
</section>
## Solution
<section id='solution'>
测试 1:你的 API 应该使用正确的名称来响应
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/name?first=Mick&last=Jagger').then(
(data) => {
assert.equal(
data.name,
'Mick Jagger',
'Test 1: "GET /name" route does not behave as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
测试 2:你的 API 应该使用正确的名称来响应
```js
(getUserInput) =>
$.get(getUserInput('url') + '/name?last=Richards&first=Keith').then(
(data) => {
assert.equal(
data.name,
'Keith Richards',
'Test 2: "GET /name" route does not behave as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
@@ -1,49 +1,57 @@
---
id: 587d7fb2367417b2b2512bf5
title: 从客户端获取路由参数输入
challengeType: 2
forumTopicId: 301513
title: 从客户端获取路由参数输入
---
## Description
<section id='description'>
在构建 API 时,我们要让用户告诉我们他们想从服务中获取什么。举个例子,如果客户请求数据库中存储的用户信息,他们需要一种方法让我们知道他们对哪个用户感兴趣。实现这个需求的的方式就是使用路由参数。路由参数是由斜杠 (/) 分隔的 URL 命名段。每一小段能捕获与其位置匹配的 URL 部分的值。捕获的值能够在<code>req.params</code>对象中找到。
# --description--
在构建 API 时,我们要让用户告诉我们他们想从服务中获取什么。举个例子,如果客户请求数据库中存储的用户信息,他们需要一种方法让我们知道他们对哪个用户感兴趣。实现这个需求的的方式就是使用路由参数。路由参数是由斜杠 (/) 分隔的 URL 命名段。每一小段能捕获与其位置匹配的 URL 部分的值。捕获的值能够在`req.params`对象中找到。
<blockquote>route_path: '/user/:userId/book/:bookId'<br>actual_request_URL: '/user/546/book/6754' <br>req.params: {userId: '546', bookId: '6754'}</blockquote>
</section>
## Instructions
<section id='instructions'>
在路由中<code>GET /:word/echo</code>构建一个 echo 服务,响应一个采用<code>{echo: word}</code>结构的 JSON 对象。你可以在<code>req.params.word</code>中找到要重复的单词。你可以在浏览器的地址栏测试你的路由,访问一些匹配的路由,比如:your-app-rootpath/freecodecamp/echo
</section>
# --instructions--
## Tests
<section id='tests'>
在路由中`GET /:word/echo`构建一个 echo 服务,响应一个采用`{echo: word}`结构的 JSON 对象。你可以在`req.params.word`中找到要重复的单词。你可以在浏览器的地址栏测试你的路由,访问一些匹配的路由,比如:your-app-rootpath/freecodecamp/echo
```yml
tests:
- text: 测试 1:你的 echo 服务应该正确地重复单词
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/eChOtEsT/echo'').then(data => { assert.equal(data.echo, ''eChOtEsT'', ''Test 1: the echo server is not working as expected'') }, xhr => { throw new Error(xhr.responseText); })'
- text: 测试 2:你的 echo 服务应该正确地重复单词
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/ech0-t3st/echo'').then(data => { assert.equal(data.echo, ''ech0-t3st'', ''Test 2: the echo server is not working as expected'') }, xhr => { throw new Error(xhr.responseText); })'
# --hints--
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
测试 1:你的 echo 服务应该正确地重复单词
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/eChOtEsT/echo').then(
(data) => {
assert.equal(
data.echo,
'eChOtEsT',
'Test 1: the echo server is not working as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
测试 2:你的 echo 服务应该正确地重复单词
```js
(getUserInput) =>
$.get(getUserInput('url') + '/ech0-t3st/echo').then(
(data) => {
assert.equal(
data.echo,
'ech0-t3st',
'Test 2: the echo server is not working as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
# --solutions--
@@ -1,13 +1,14 @@
---
id: 587d7fb1367417b2b2512bf3
title: 实现一个根层的请求记录器中间件
challengeType: 2
forumTopicId: 301514
title: 实现一个根层的请求记录器中间件
---
## Description
<section id='description'>
前面我们介绍了<code>express.static()</code>中间件函数。现在是时候更详细地了解什么是中间件了。中间件是一个接收 3 个参数的函数:请求对象、响应对象和在应用请求响应循环中的下一个函数。这些函数执行一些可能对应用程序产生副作用的代码,通常还会在请求对象或者响应对象里添加一些信息。当满足某些条件时,它们也可以结束发送响应的循环。如果它们没有发送响应,那么当它们完成时就会开始执行堆栈中的下一个函数。这将触发调用第 3 个参数<code>next()</code>。更多信息请查看 <a href='http://expressjs.com/en/guide/using-middleware.html' target='_blank'>express 文档</a>
# --description--
前面我们介绍了`express.static()`中间件函数。现在是时候更详细地了解什么是中间件了。中间件是一个接收 3 个参数的函数:请求对象、响应对象和在应用请求响应循环中的下一个函数。这些函数执行一些可能对应用程序产生副作用的代码,通常还会在请求对象或者响应对象里添加一些信息。当满足某些条件时,它们也可以结束发送响应的循环。如果它们没有发送响应,那么当它们完成时就会开始执行堆栈中的下一个函数。这将触发调用第 3 个参数`next()`。更多信息请查看 [express 文档](http://expressjs.com/en/guide/using-middleware.html)
看看下面的例子:
```js
@@ -17,42 +18,32 @@ function(req, res, next) {
}
```
假设我们在某个路由上安装了这个中间件函数。当一个请求与路由匹配时,它会显示字符串"我是中间件..."。然后它执行堆栈中的下一个函数。
在这个练习中,我们将构建根级中间件。正如我们在挑战 4 中看到的,要在根层级安装中间件函数,我们可以使用<code>app.use(&lt;mware-function&gt;)</code>方法。在这种情况下,该函数将对所有请求执行,但是你还是可以设置成更具体的条件来执行。举个例子,如果你希望某个函数只针对 POST 请求执行,可以使用<code>app.post(&lt;mware-function&gt;)</code>方法。所有 http 动作都有类似的方法,比如 GET、DELETE、PUT 等等。
</section>
假设我们在某个路由上安装了这个中间件函数。当一个请求与路由匹配时,它会显示字符串"我是中间件..."。然后它执行堆栈中的下一个函数。 在这个练习中,我们将构建根级中间件。正如我们在挑战 4 中看到的,要在根层级安装中间件函数,我们可以使用`app.use(<mware-function>)`方法。在这种情况下,该函数将对所有请求执行,但是你还是可以设置成更具体的条件来执行。举个例子,如果你希望某个函数只针对 POST 请求执行,可以使用`app.post(<mware-function>)`方法。所有 http 动作都有类似的方法,比如 GET、DELETE、PUT 等等。
# --instructions--
构建一个简单的日志记录器。对于每个请求,它应该在控制台中记录一个采用以下格式的字符串:`method path - ip`。一个简单的日志看起来就像这样:`GET /json - ::ffff:127.0.0.1`。注意`method``path`有一个空格,并且`path``ip`中间的破折号两边都有空格。在请求对象中,可以使用`req.method``req.path``req.ip`获取请求方法(http 动词)、路由相对路径和请求者的 IP 信息。记住,当你完成时,要调用`next()`方法,否则你的服务器将一直处于挂起状态。请确保 "Logs" 是打开的,观察一下当一些请求被响应时会发生什么……
## Instructions
<section id='instructions'>
构建一个简单的日志记录器。对于每个请求,它应该在控制台中记录一个采用以下格式的字符串:<code>method path - ip</code>。一个简单的日志看起来就像这样:<code>GET /json - ::ffff:127.0.0.1</code>。注意<code>method</code>和<code>path</code>有一个空格,并且<code>path</code>和<code>ip</code>中间的破折号两边都有空格。在请求对象中,可以使用<code>req.method</code>、<code>req.path</code>和<code>req.ip</code>获取请求方法(http 动词)、路由相对路径和请求者的 IP 信息。记住,当你完成时,要调用<code>next()</code>方法,否则你的服务器将一直处于挂起状态。请确保 "Logs" 是打开的,观察一下当一些请求被响应时会发生什么……
提示: Express 按照函数在代码中出现的顺序来评估函数。中间件也是如此。如果你想让中间件函数适用于所有路由,那么应该在路由之前配置好中间件。
</section>
## Tests
<section id='tests'>
# --hints--
```yml
tests:
- text: 根级记录器中间件应该被激活
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/_api/root-middleware-logger'').then(data => { assert.isTrue(data.passed, ''root-level logger is not working as expected''); }, xhr => { throw new Error(xhr.responseText); })'
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
根级记录器中间件应该被激活
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/root-middleware-logger').then(
(data) => {
assert.isTrue(
data.passed,
'root-level logger is not working as expected'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
# --solutions--
@@ -1,51 +1,37 @@
---
id: 587d7fb0367417b2b2512bed
title: 认识 Node 的控制台
challengeType: 2
forumTopicId: 301515
title: 认识 Node 的控制台
---
## Description
<section id='description'>
在开发过程中,能够随时看到代码的运行结果是非常重要的。Node 只是一个 JavaScript 环境。与客户端 JavaScript 一样,你可以使用控制台输出有用的调试信息。在本地计算机上,你可以在终端中输出调试信息。在 Glitch 上,你可以打开屏幕下方的日志。使用 "Logs" 按钮切换日志面板(在左上角,应用名称的下面)。
我们建议在做这些挑战题时,保持日志面板处于打开状态。通过这些错误日志,你可能会发现这些错误的本质原因。
</section>
# --description--
## Instructions
<section id='instructions'>
在开发过程中,能够随时看到代码的运行结果是非常重要的。Node 只是一个 JavaScript 环境。与客户端 JavaScript 一样,你可以使用控制台输出有用的调试信息。在本地计算机上,你可以在终端中输出调试信息。在 Glitch 上,你可以打开屏幕下方的日志。使用 "Logs" 按钮切换日志面板(在左上角,应用名称的下面)。
我们建议在做这些挑战题时,保持日志面板处于打开状态。通过这些错误日志,你可能会发现这些错误的本质原因。
# --instructions--
如果还没准备好,请阅读[介绍](/learn/apis-and-microservices/basic-node-and-express/)里面的指引,在 Glitch 里通过这个[链接](https://glitch.com/edit/#!/remix/clone-from-repo?REPO_URL=https://github.com/freeCodeCamp/boilerplate-express/)创建一个新项目。
修改 <code>myApp.js</code> 文件,在控制台打印出经典的 "Hello World" 即可。
</section>
修改 `myApp.js` 文件,在控制台打印出经典的 "Hello World" 即可。
## Tests
<section id='tests'>
# --hints--
```yml
tests:
- text: "控制台应该输出:<code>'Hello World'</code>"
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/_api/hello-console'').then(data => { assert.isTrue(data.passed, ''"Hello World" is not in the server console''); }, xhr => { throw new Error(xhr.responseText); })'
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
控制台应该输出:`'Hello World'`
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/hello-console').then(
(data) => {
assert.isTrue(data.passed, '"Hello World" is not in the server console');
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
# --solutions--
@@ -1,53 +1,43 @@
---
id: 587d7fb0367417b2b2512bef
title: 服务 HTML 文件
challengeType: 2
forumTopicId: 301516
title: 服务 HTML 文件
---
## Description
<section id='description'>
我们可以使用<code>res.sendFile(path)</code>方法来响应一个文件。你可以把响应一个文件的方法放到路由处理程序中:<code>app.get('/', ...)</code>。在后台,这个方法会根据你想发送的文件的类型,设置适当的 headers 头信息来告诉浏览器如何处理它。然后它会读取并发送文件。此方法需要文件的绝对路径。我们建议你使用 Node.js 的全局变量<code>__dirname</code>来计算出这个文件的绝对路径。
# --description--
我们可以使用`res.sendFile(path)`方法来响应一个文件。你可以把响应一个文件的方法放到路由处理程序中:`app.get('/', ...)`。在后台,这个方法会根据你想发送的文件的类型,设置适当的 headers 头信息来告诉浏览器如何处理它。然后它会读取并发送文件。此方法需要文件的绝对路径。我们建议你使用 Node.js 的全局变量`__dirname`来计算出这个文件的绝对路径。
```js
absolutePath = __dirname + relativePath/file.ext
```
</section>
# --instructions--
## Instructions
<section id='instructions'>
要发送的文件是<code>/views/index.html</code>。在 app 中点击 "Show Live" 按钮,你会看到一个大的 HTML 标题(以及我们稍后将使用的表单…),目前它们还没有任何样式。
要发送的文件是`/views/index.html`。在 app 中点击 "Show Live" 按钮,你会看到一个大的 HTML 标题(以及我们稍后将使用的表单…),目前它们还没有任何样式。
<strong>注意: </strong>你可以编辑上一个挑战的解题代码,或者创建一个新的挑战。如果你重写了之前的代码,请注意 Express 会从上到下重新解析对应的路由方法。它执行第一个匹配的路由处理方法。你必须注释掉前面的代码,否则服务器还是响应之前的字符串。
</section>
**注意:**你可以编辑上一个挑战的解题代码,或者创建一个新的挑战。如果你重写了之前的代码,请注意 Express 会从上到下重新解析对应的路由方法。它执行第一个匹配的路由处理方法。你必须注释掉前面的代码,否则服务器还是响应之前的字符串。
## Tests
<section id='tests'>
# --hints--
```yml
tests:
- text: app 应该响应 views/index.html 文件
testString: 'getUserInput => $.get(getUserInput(''url'')).then(data => { assert.match(data, /<h1>.*<\/h1>/, ''Your app does not serve the expected HTML''); }, xhr => { throw new Error(xhr.responseText); })'
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
app 应该响应 views/index.html 文件
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url')).then(
(data) => {
assert.match(
data,
/<h1>.*<\/h1>/,
'Your app does not serve the expected HTML'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
# --solutions--
@@ -1,47 +1,39 @@
---
id: 587d7fb1367417b2b2512bf1
title: 服务指定路由上的 JSON
challengeType: 2
forumTopicId: 301517
title: 服务指定路由上的 JSON
---
## Description
<section id='description'>
# --description--
HTML 服务器提供 HTMLAPI 服务器提供数据。<dfn>REST</dfn>(表现层状态转换)API 允许使用更简易的方式交换数据,从而不需要客户端知道服务器的实现细节。客户端只要知道需请求资源对应的 URL 是什么,以及需要对这个 URL 进行何种操作就够了。比如 GET 这个动作,就是从服务器上获取某些信息,它不会修改任何数据。如今,在 web 上传输数据的首选数据格式是 JSON。简言之,JSON 是一种将 JavaScript 对象表示为字符串的简易方式,因此可以很容易地传输这些数据。
我们来创建一个简单的 API,一个路径为<code>/json</code>且返回数据是 JSON 格式的路由,你可以像之前那样通过<code>app.get()</code>方法来做,然后在路由处理部分使用<code>res.json()</code>方法返回 JSON 格式的数据,这个方法可以接收一个配置对象。这个方法会结束请求响应循环(request-response loop),然后返回数据。<code>res.json()</code>将一个有效的 JavaScript 对象转化为字符串,然后会设置适当的头信息(headers)来告诉浏览器,这是一个 JSON 数据,最后返回给客户端进行处理。一个有效的对象通常是这种结构:<code>{key: data}</code>。数据可以是数字、字符串、嵌套对象或数组。也可以是变量或者函数返回值,在这种情况下,会以它们的执行结果为基准,再转成字符串。
</section>
## Instructions
<section id='instructions'>
当 GET 请求路由<code>/json</code>时,将对象<code>{"message": "Hello json"}</code>作为 JSON 格式返回给客户端。然后在浏览器里输入完整的 URL,比如<code>your-app-url/json</code>,就可以在屏幕上看到这个消息了。
</section>
我们来创建一个简单的 API,一个路径为`/json`且返回数据是 JSON 格式的路由,你可以像之前那样通过`app.get()`方法来做,然后在路由处理部分使用`res.json()`方法返回 JSON 格式的数据,这个方法可以接收一个配置对象。这个方法会结束请求响应循环(request-response loop),然后返回数据。`res.json()`将一个有效的 JavaScript 对象转化为字符串,然后会设置适当的头信息(headers)来告诉浏览器,这是一个 JSON 数据,最后返回给客户端进行处理。一个有效的对象通常是这种结构:`{key: data}`。数据可以是数字、字符串、嵌套对象或数组。也可以是变量或者函数返回值,在这种情况下,会以它们的执行结果为基准,再转成字符串。
## Tests
<section id='tests'>
# --instructions--
```yml
tests:
- text: '服务端<code>/json</code>应该返回一个 JSON 对象<code>{"message": "Hello json"}</code>'
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/json'').then(data => { assert.equal(data.message, ''Hello json'', ''The \''/json\'' endpoint does not serve the right data''); }, xhr => { throw new Error(xhr.responseText); })'
当 GET 请求路由`/json`时,将对象`{"message": "Hello json"}`作为 JSON 格式返回给客户端。然后在浏览器里输入完整的 URL,比如`your-app-url/json`,就可以在屏幕上看到这个消息了。
```
# --hints--
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
服务端`/json`应该返回一个 JSON 对象`{"message": "Hello json"}`
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/json').then(
(data) => {
assert.equal(
data.message,
'Hello json',
"The '/json' endpoint does not serve the right data"
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
# --solutions--
@@ -1,47 +1,39 @@
---
id: 587d7fb0367417b2b2512bf0
title: 服务静态资源
challengeType: 2
forumTopicId: 301518
title: 服务静态资源
---
## Description
<section id='description'>
HTML 服务器通常有一个或多个用户可以访问的目录。你可以将应用程序所需的静态资源 (样式表、脚本、图片) 放在那里。在 Express 中你可以使用中间件<code>express.static(path)</code>来设置此功能,它的参数就是静态资源文件的绝对路径。如果你不知道什么是中间件,也不用担心。我们稍后将详细讨论此事。一个最基本的中间件可以看做是一个函数,它拦截路由处理方法,并在里面添加了一点别的信息。使用<code>app.use(path, middlewareFunction)</code>方法来加载一个中间件。它的第一个参数是可选的,如果没设置第一个参数,那么应用的所有请求都会经过这个中间件处理。
</section>
# --description--
HTML 服务器通常有一个或多个用户可以访问的目录。你可以将应用程序所需的静态资源 (样式表、脚本、图片) 放在那里。在 Express 中你可以使用中间件`express.static(path)`来设置此功能,它的参数就是静态资源文件的绝对路径。如果你不知道什么是中间件,也不用担心。我们稍后将详细讨论此事。一个最基本的中间件可以看做是一个函数,它拦截路由处理方法,并在里面添加了一点别的信息。使用`app.use(path, middlewareFunction)`方法来加载一个中间件。它的第一个参数是可选的,如果没设置第一个参数,那么应用的所有请求都会经过这个中间件处理。
# --instructions--
使用`app.use()`来加载`express.static()`中间件,让所有的请求都能访问我们的静态资源目录。静态资源的绝对路径是`__dirname + /public`
## Instructions
<section id='instructions'>
使用<code>app.use()</code>来加载<code>express.static()</code>中间件,让所有的请求都能访问我们的静态资源目录。静态资源的绝对路径是<code>__dirname + /public</code>。
现在 app 就能正常返回一个样式文件了,访问 app 根路径时,静态资源目录的文件就会被加载进来,现在你的首页看起来应该好些了!
</section>
## Tests
<section id='tests'>
# --hints--
```yml
tests:
- text: 应用的静态资源文件应该来自<code>/public</code>目录
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/style.css'').then(data => { assert.match(data, /body\s*\{[^\}]*\}/, ''Your app does not serve static assets''); }, xhr => { throw new Error(xhr.responseText); })'
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应用的静态资源文件应该来自`/public`目录
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/style.css').then(
(data) => {
assert.match(
data,
/body\s*\{[^\}]*\}/,
'Your app does not serve static assets'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
# --solutions--
@@ -1,15 +1,17 @@
---
id: 587d7fb0367417b2b2512bee
title: 启动一个 Express 服务器
challengeType: 2
forumTopicId: 301519
title: 启动一个 Express 服务器
---
## Description
<section id='description'>
在 myApp.js 文件的前两行中,你可以看到创建一个 Express 应用对象是很简单的。这个 Express 对象有几个方法,在这些挑战中,我们将会学习它们更多的方法。一个基础的方法是<code>app.listen(port)</code>。它告诉服务器监听指定的端口,并且让这个服务处于运行状态。你可以在文件的底部看到它们。它在注释里面,因为出于测试原因,我们需要知道这个应用正在后台运行。你想要添加的所有代码,都放在这两部分之间。Glitch 将端口号存储在环境变量<code>process.env.PORT</code>中。它的值是<code>3000</code>
让我们在服务端输出第一个字符串!在 Express 中,路由采用这种结构:<code>app.METHOD(PATH, HANDLER)</code>。<code>METHOD</code>是小写的 http 方法。<code>PATH</code>是服务器上的相对路径(它是一个字符串,甚至是正则表达式)<code>HANDLER</code>是 Express 匹配路由时调用的处理函数。
处理函数采用这种形式:<code>function(req, res) {...}</code>,在这个处理函数的参数中<code>req</code>是请求对象。<code>res</code>是响应对象。举个例子,处理函数
# --description--
在 myApp.js 文件的前两行中,你可以看到创建一个 Express 应用对象是很简单的。这个 Express 对象有几个方法,在这些挑战中,我们将会学习它们更多的方法。一个基础的方法是`app.listen(port)`。它告诉服务器监听指定的端口,并且让这个服务处于运行状态。你可以在文件的底部看到它们。它在注释里面,因为出于测试原因,我们需要知道这个应用正在后台运行。你想要添加的所有代码,都放在这两部分之间。Glitch 将端口号存储在环境变量`process.env.PORT`中。它的值是`3000`
让我们在服务端输出第一个字符串!在 Express 中,路由采用这种结构:`app.METHOD(PATH, HANDLER)``METHOD`是小写的 http 方法。`PATH`是服务器上的相对路径(它是一个字符串,甚至是正则表达式)`HANDLER`是 Express 匹配路由时调用的处理函数
处理函数采用这种形式:`function(req, res) {...}`,在这个处理函数的参数中`req`是请求对象。`res`是响应对象。举个例子,处理函数:
```js
function(req, res) {
@@ -18,40 +20,32 @@ function(req, res) {
```
将会响应一个字符串 'Response String'。
</section>
## Instructions
<section id='instructions'>
当 GET 请求根路由( "/" )时,使用<code>app.get()</code>方法端响应一个 "Hello Express" 字符串。
<strong>注意:</strong> 在 Glitch 中点击 "Show Live" 按钮,通过查看日志确保你的代码正常运行,然后在浏览器中查看结果。
</section>
# --instructions--
## Tests
<section id='tests'>
当 GET 请求根路由( "/" )时,使用`app.get()`方法端响应一个 "Hello Express" 字符串。
```yml
tests:
- text: 'app 应该输出 "Hello Express" 字符串'
testString: 'getUserInput => $.get(getUserInput(''url'')).then(data => { assert.equal(data, ''Hello Express'', ''Your app does not serve the text "Hello Express"''); }, xhr => { throw new Error(xhr.responseText); })'
**注意:** 在 Glitch 中点击 "Show Live" 按钮,通过查看日志确保你的代码正常运行,然后在浏览器中查看结果。
```
# --hints--
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
app 应该输出 "Hello Express" 字符串
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url')).then(
(data) => {
assert.equal(
data,
'Hello Express',
'Your app does not serve the text "Hello Express"'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
# --solutions--
@@ -1,13 +1,14 @@
---
id: 587d7fb2367417b2b2512bf7
title: 使用 body-parser 来解析POST请求
challengeType: 2
forumTopicId: 301520
title: 使用 body-parser 来解析POST请求
---
## Description
<section id='description'>
# --description--
除了 GET 还有另一个常见的 http 动词,它是 POST。POST 是使用 HTML 表单发送客户端数据的默认方法。在 REST 规范中,POST 常用于发送数据,以便在数据库中创建新项目(新用户或新博客文章)。我们在这个项目中没有使用数据库,我们将学习如何处理 POST 请求。
在这些类型的请求中,数据不会出现在 URL 中,而是隐藏在请求正文中。这也是 HTML 请求的一部分,被称为负载。因为 HTML 是基于文本的,你看不到数据,这并不意味着它们是加密的。HTTP POST 请求的原始内容如下所示:
```http
@@ -19,42 +20,33 @@ Content-Length: 20
name=John+Doe&age=25
```
正如你所看到的,正文被编码成了查询字符串。这是 HTML 表单使用的默认格式。使用 Ajax,我们还可以使用 JSON 来处理具有更复杂结构的数据。还有另一种类型的编码:multipart/form-data。它用来上传二进制文件。
在本练习中,我们将使用网址编码 body。要解析来自 POST 请求的数据,你必须安装一个包:body-parser。这个包允许你使用一套可以解码不同格式数据的中间件,在<a href="https://github.com/expressjs/body-parser" target="_blank" >这里</a>查看文档。
</section>
正如你所看到的,正文被编码成了查询字符串。这是 HTML 表单使用的默认格式。使用 Ajax,我们还可以使用 JSON 来处理具有更复杂结构的数据。还有另一种类型的编码:multipart/form-data。它用来上传二进制文件。 在本练习中,我们将使用网址编码 body。要解析来自 POST 请求的数据,你必须安装一个包:body-parser。这个包允许你使用一套可以解码不同格式数据的中间件,在[这里](https://github.com/expressjs/body-parser)查看文档。
# --instructions--
## Instructions
<section id='instructions'>
在 package.json 中安装 body-parser 模块,然后在文件顶部 require 进来,用变量 bodyParser 保存它。
处理 URL 编码数据通过中间件的<code>bodyParser.urlencoded({extended: false})</code>方法。<code>extended=false</code>是一个配置选项,告诉解析器使用经典编码。当你使用它时,值只能是字符串或者数组。继承版使用起来数据更加灵活,它比 JSON 更好。传递给<code>app.use()</code>上一次方法调用返回的函数。通常中间件必须挂载在所有需要它的路由之前。
</section>
## Tests
<section id='tests'>
处理 URL 编码数据通过中间件的`bodyParser.urlencoded({extended: false})`方法。`extended=false`是一个配置选项,告诉解析器使用经典编码。当你使用它时,值只能是字符串或者数组。继承版使用起来数据更加灵活,它比 JSON 更好。传递给`app.use()`上一次方法调用返回的函数。通常中间件必须挂载在所有需要它的路由之前。
```yml
tests:
- text: '"body-parser" 中间件应该被挂载'
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/_api/add-body-parser'').then(data => { assert.isAbove(data.mountedAt, 0, ''"body-parser" is not mounted correctly'') }, xhr => { throw new Error(xhr.responseText); })'
# --hints--
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
"body-parser" 中间件应该被挂载
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/add-body-parser').then(
(data) => {
assert.isAbove(
data.mountedAt,
0,
'"body-parser" is not mounted correctly'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
# --solutions--
@@ -1,48 +1,40 @@
---
id: 587d7fb1367417b2b2512bf2
title: 使用 .env 文件
challengeType: 2
forumTopicId: 301521
title: 使用 .env 文件
---
## Description
<section id='description'>
<code>.env</code>文件是一个隐藏文件,用于将环境变量传给应用程序。这是一个私密文件,除了你之外没人可以访问它,它可以用来存储你想保持私有或者隐藏的数据。举个例子,可以存储第三方服务 API 密钥或者数据库 URI。你也可以使用它来存储配置选项。通过设置配置选项,你可以改变应用程序的行为,而无需重写一些代码。
在应用程序中可以通过<code>process.env.VAR_NAME</code>访问到环境变量。<code>process.env</code>是 Node 程序中的一个全局对象,可以给这个变量传字符串。按照惯例,变量名都是大写的,单词之间用下划线隔开。<code>.env</code>是一个 shell 文件,因此不需要用给变量名和值加引号。还有一点需要注意,当你给变量赋值时,等号周围不能有空格,举个例子:<code>VAR_NAME=value</code>。通常来讲,每一个变量会单独定义在新的一行。
</section>
# --description--
`.env`文件是一个隐藏文件,用于将环境变量传给应用程序。这是一个私密文件,除了你之外没人可以访问它,它可以用来存储你想保持私有或者隐藏的数据。举个例子,可以存储第三方服务 API 密钥或者数据库 URI。你也可以使用它来存储配置选项。通过设置配置选项,你可以改变应用程序的行为,而无需重写一些代码。
在应用程序中可以通过`process.env.VAR_NAME`访问到环境变量。`process.env`是 Node 程序中的一个全局对象,可以给这个变量传字符串。按照惯例,变量名都是大写的,单词之间用下划线隔开。`.env`是一个 shell 文件,因此不需要用给变量名和值加引号。还有一点需要注意,当你给变量赋值时,等号周围不能有空格,举个例子:`VAR_NAME=value`。通常来讲,每一个变量会单独定义在新的一行。
# --instructions--
## Instructions
<section id='instructions'>
让我们添加一个环境变量作为配置选项。
在<code>.env</code>文件中保存变量<code>MESSAGE_STYLE=uppercase</code>。它的作用是,告诉上一次挑战中的路由处理程序,当我们 GET 方法请求 /JSON 时,如果<code>process.env.MESSAGE_STYLE</code>的值为<code>uppercase</code>,那么返回的对象则应该是<code>{"message": "HELLO JSON"}</code>.
</section>
## Tests
<section id='tests'>
`.env`文件中保存变量`MESSAGE_STYLE=uppercase`。它的作用是,告诉上一次挑战中的路由处理程序,当我们 GET 方法请求 /JSON 时,如果`process.env.MESSAGE_STYLE`的值为`uppercase`,那么返回的对象则应该是`{"message": "HELLO JSON"}`.
```yml
tests:
- text: 客户端响应<code>/json</code>的值,应该随着环境变量<code>MESSAGE_STYLE</code>的变化而改变
testString: 'getUserInput => $.get(getUserInput(''url'') + ''/_api/use-env-vars'').then(data => { assert.isTrue(data.passed, ''The response of "/json" does not change according to MESSAGE_STYLE''); }, xhr => { throw new Error(xhr.responseText); })'
# --hints--
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
客户端响应`/json`的值,应该随着环境变量`MESSAGE_STYLE`的变化而改变
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/use-env-vars').then(
(data) => {
assert.isTrue(
data.passed,
'The response of "/json" does not change according to MESSAGE_STYLE'
);
},
(xhr) => {
throw new Error(xhr.responseText);
}
);
```
</section>
# --solutions--