# 提供服务

服务端应用在服务器上运行,提供了一些服务,比如发布内容,上传文件的服务。如果想让大家可以通过网络使用这些服务,在这台服务器上就必须要运行一个 Web 服务器。下面我们就来看看,如何写几行代码,创建一个 Web 服务器,提供一个简单的服务。

# 准备

# 任务:准备项目<web-server>

在新窗口观看视频

在项目里,项目所在目录的下面,基于 develop 分支,创建并且切换到一个新的分支,名字叫 web-server。

git checkout -b web-server

# 语法

# require()

Node.js 里面自带了很多模块,它们都提供了不同的功能,在我们的项目里可以直接使用这些模块提供的功能。首先要在想在使用这些模块的文件里导入它们,导入模块的时候用的就是 require() 这个函数,一般我们还会给导入的模块起个名字。

Node.js 有自己的一套模块系统,就是创建与使用模块的一套方法,这套模块系统并不是标准的 JavaScript 语言提供的模块系统,因为之前在 JavaScript 语言里并不存在模块系统,是后来才有的,所以 Node.js 就自己做了一个这样的模块系统。

这里我们用的 require() 并不是标准的导入模块的写法,但目前 Node.js 只支持这种方法。在后面我们会在项目里使用 TypeScript ,这样就可以用 JavaScript 语言提供的标准的模块系统的写法了。

示例:

const http = require('http');

上面这行代码就是导入了 Node.js 里的 http 这个模块,给导入进来的东西起了个名字叫 http,这样在这个文件里就可以使用 http 来使用 Node.js 的 http 模块里提供的功能了。

# Web 服务器

通过 Web 服务器,我们的服务端应用就可以给客户端提供需要的服务与资源。用 Node.js 自带的模块,写几行代码就可以创建一个 Web 服务器。

# 任务:创建 Web 服务器

在新窗口观看视频

用 Node.js 自带的 http 模块,创建一个 Web 服务器,添加一个简单的服务。在客户端请求访问这个服务,服务端就会响应回去一句 hello ~,在客户端那里可以决定怎么样使用服务端响应回来的数据。

1:打开文件

在编辑器,打开 src/main.js 文件,清空文件里的内容,然后输入下面这些代码,后面我们会逐行解释它们的作用。

const http = require('http');

const server = http.createServer((request, response) => {
  response.write('hello ~');
  response.end();
});

server.listen(3000, () => {
  console.log('🚀 服务已启动!');
});

2:导入模块<http>

const http = require('http');

Node.js 本身提供了一些功能模块,这里我们要用的是它提供的 http 这个模块提供的功能,去创建一个 Web 服务器。 在 Node.js 里导入模块可以使用 require(),把要导入的模块的名字告诉它就可以了,这里就是 http。导入进来的 http 模块交给了一个叫 http 的东西,这样就可以通过 http 这个东西来使用 http 模块提供的功能了。

3:创建服务器

const server = http.createServer();

上面这行用了一下 http 模块里提供的 createServer() 方法,执行这个方法就会得到一个服务器,我们把得到的这个服务器交给了 server 。现在这个服务器还不能做什么,可以再给它添加点要做的事情,把这些事情交给 createServer() 这个方法。像下面这样修改一下:

const server = http.createServer((request, response) => {
  response.write('hello ~');
  response.end();
});

这次在使用 createServer() 的时候,给它提供了一个函数,我们把这个 Web 服务要做的事情放在这个函数里了。这个函数支持两个参数。request,表示请求,Node.js 会把客户端发出的这个请求相关的一些东西交给这个参数。

response 指的是响应,这个参数上面提供了一些方法可以处理如何回应客户端的请求。这里我们就是用了一下它上面的 write() 方法设置了响应的内容是一行文字:hello ~ ,最后又用了一下 end() 方法结束响应。

4:监听服务

server.listen(3000, () => {
  console.log('🚀 服务已启动!');
});

之前我们把用 createServer() 创建的服务器交给了 server,这里用一下 server 上提供的 listen() 方法,设置一下监听服务。这个 listen() 方法提供了两个参数,第一个参数是监听服务的端口号,第二个参数是个函数,在运行这个 Web 服务器的时候会调用这个函数,我们这里只是简单的在控制台上输出一行文字。

5:运行 Web 服务器

在终端,项目所在目录的下面,执行:

node src/main.js

node 这个命令行工具运行一下我们之前在 src/main.js 文件里写的 Web 服务,服务会一直运行,除非手动按 ctrl + C 停止运行。执行了命令之后会输出一行文字:🚀 服务已启动!

6:访问服务

打开浏览器,访问 http://localhost:3000,在打开的页面上,你会看到一个 hello ~

7:做一次提交

在新窗口观看视频

在以后每做完一个任务,如果这个任务修改了项目,我们就需要对项目做一次提交,保存一下项目的这个状态。比如刚才我们用 http 模块创建了一个 Web 服务器,修改了项目里的 src/main.js 这个文件。所以完成这个任务以后,就要做一次提交。

git add .
git commit -m '创建 Web 服务器'

刚才我们用 Node.js 自带的一个叫 http 的模块创建了一个 Web 服务器,提供了一个简单的服务,访问它的时候只能回应一句 hello ~ 运行这个服务器以后,在客户端就可以通过 HTTP 协议访问这个服务器了。

浏览器在这里就相当于是一个客户端,访问服务的时候用的地址是 http:// 开头的,现在的浏览器一般会在地址栏里隐藏这部分内容。访问的主机是 localhost,它表示的是本地主机,也就是要访问的服务是在当前这台电脑上运行的。

在访问的地址里还包含了一个端口号,就是地址里冒号右边的数字(:3000),这是因为我们在搭建 Web 服务器的时候,设置的让它监听的端口号就是 3000 。所以要通过这个端口才能访问到这个 Web 服务。如果不在访问的地址里单独设置这个端口号,说明访问的 Web 服务器监听的是默认的端口号,80 是 HTTP 协议的默认端口号。

# 使用编辑器的源代码管理

在新窗口观看视频

# 请求与响应

在新窗口观看视频

在客户端向服务端请求它需要的资源,服务端收到请求以后会作出一个响应,客户端收到了响应可以决定如何处理这个响应。服务端可以根据客户端请求的地址,作出不同的响应。

在请求与响应里都可以带着一些头部数据,比如请求的时候可以在头部数据里说明一下这个请求,这样在服务端那里可以读取请求里的头部数据。服务端回应客户端的时候,在响应里面也可以包含头部数据,比如在头部数据里描述一下响应的数据类型,这样客户端收到了响应之后,可以根据响应里的头部数据决定怎么样处理响应里带的数据。

# 任务:理解请求(Request)

在新窗口观看视频

在服务端我们可以得到请求相关的东西,比如请求里带的数据,头部(Headers)等等。Node.js 的 http 模块会把这些东西组织好,交给一个函数的参数,在控制台上输出这个参数,观察一下它里面到底有什么。

1:输出 request 参数

src/main.js<修改>

在给 createServer() 方法提供的函数参数里面,添加下面这行代码,输出函数接受的 request 参数:

console.log(request);

2:重启服务

修改了服务之后必须重新启动才能生效。在运行应用的终端,按 ctrl + C 可以停止运行服务,然后再重新运行一下服务。

3:访问服务

为了在控制台上输出请求相关的东西,需要在客户端请求一下我们的应用。

4:观察结果

在终端,观察一下输出的 request 参数的值,也就是请求相关的东西。比如你会发现它有个 headers 属性,它里面就是客户端发送请求的时候,在请求里包含的头部数据。假设我们想在服务端用一下这些头部数据,比哪有一个叫 user-agent 的头部数据,它的值就就是跟发出这个请求的客户端相关的一些情况,比如操作系统是什么,用的浏览器是什么。

在服务端可以像这样得到请求里的 user-agent 这个头部数据的值:

request.headers['user-agent'];

因为 user-agent 里面有个小横线,所以不能用点的形式访问这个属性,可以用这种方括号的形式来访问它的值。这个属性的值看起来像下面这样:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/80.0.3987.106 Safari/537.36

# 任务:理解响应(Response)

在新窗口观看视频

之前我们在服务端设置的给客户端响应的数据是一行文字,浏览器收到文字就把它显示在页面上了。下面我们修改一下这个响应的数据,比如响应一个 HTML 格式的内容,网页内容就是用这种标记语言组织的,浏览器认识这种 HTML 语言,知道怎么显示它们。不过在服务端把这种数据交给浏览器的时候要告诉它数据的类型是 HTML,这样浏览器才能正确的处理响应里的数据。

1:设置响应头部数据

src/main.js<修改>

修改给 createServer() 方法提供的函数参数,用下面的内容替换一下函数的主体部分:

response.writeHead(200, {
  'Content-Type': 'text/html',
});

这次给客户端响应的数据类型是 HTML,所以我们要通过头部数据告诉客户端响应里的数据格式是什么,这样客户端才能正确的处理得到的响应里的数据。

response 参数上面有个 writeHead() 方法,它可以设置响应里的头部,这个方法的第一个参数是响应的状态码,比如 200,这个状态码表示的是服务端成功处理了请求。第二个参数是个对象,里面可以添加一些响应里要带着的头部数据。

设置数据类型用的是 Content-Type 这个头部数据,对应的值设置成 text/html,意思就是数据是 HTML 格式的。 头部数据的名字,还有这个表示数据类型用的文本都是有规范的。除了这些规定可以使用的头部数据,我们也可以添加一些自定义的头部数据。

response.write(`<input />;`);

上面用了 response.write() 方法设置了一下要响应的数据,这个 <input /> 是个 HTML 元素,在网页上显示出来的话应该是个可以输入文字的文本框。

response.end();

使用 response.end() 结束响应,如果不用这个方法,客户端发出请求以后一直收不到服务端的响应,所以就会卡住。

2:重启服务

在运行应用的终端,按 ctrl + C 可以停止运行服务,然后再重新运行一下服务。

3:访问应用

在浏览器,访问一下应用,你会在页面上看到一个可以输入文字的文本框。

# 任务:根据请求的地址作出响应

在新窗口观看视频

根据请求的地址,服务端可以决定响应什么样的数据。

1:设置路由

src/main.js<修改>

createServer() 方法提供的函数参数的主体部分替换成下面这些:

switch (request.url) {
  case '/':
    response.write('hello ~');
    break;
  case '/posts':
    response.write('posts');
    break;
  case '/signup':
    response.write('signup');
    break;
  default:
    response.writeHead(404);
    response.write('404');
    break;
}

response.end();

2:重启服务

修改了服务之后必须重新启动才能生效。在运行应用的终端,按 ctrl + C 可以停止运行服务,然后再重新运行一下服务。

3:访问服务

在浏览器,访问 http://localhost:3000/ 会在页面上显示 hello ~,访问 http://localhost:3000/posts 的时候,会显示 posts,访问 http://localhost:3000/signup 就会显示 signup,访问 http://localhost:3000/ufo 时,会显示 404

# JSON

在新窗口观看视频

我们要开发的这个服务端应用,可能需要对不同类型的客户端提供服务,比如浏览器,移动端,小程序等等。这些客户端与服务端交换数据的时候需要一种通用的数据格式,不管是谁都可以读懂这种数据,一般我们都会选择用 JSON 这种数据格式。

在客户端可以把 JSON 格式的数据发送给服务端,在服务端可以把要发给客户端的数据转换成 JSON 格式的。无论是客户端还是服务端都认识这种格式的数据,也都知道如何处理这种格式的数据。

假设你需要一个可以通过浏览器使用的应用,这个应用除了需要一个服务端应用提供的服务以外,你还得额外再去创建一个可以在浏览器上运行的应用,一般这种应用叫前端应用。在前端应用里可以请求使用服务端应用提供的服务,比如它如果需要一组内容列表数据,它可以请求服务端应用获取到这组数据,得到的数据一般就是 JSON 格式的。

前端应用收到了这组 JSON 格式的数据以后,会加工处理一下,再把它们放到事先设计好的界面上显示出来。

示例:

{
  "id": 1,
  "title": "关山月",
  "content": "明月出天山,苍茫云海间"
}

上面就是一个 JSON 格式的数据,一组大括号,里面是一些数据的属性,属性与属性之间用逗号分隔开。每个项目都有个名字还有一个对应的值,名字与值的中间是个冒号,文字要用双引号包装,数字可以不用双引号。注意最后一个数据项目不能添加逗号。

你会发现这种 JSON 格式的数据跟我们之前介绍的 JavaScript 语言里的 Object 非常像,其实这种格式就是根据 JavaScript 语言里的对象设计出来的。JSON 的全名是:JavaScript Object Notation。

示例:

[
  {
    id: 1,
    title: '关山月',
    content: '明月出天山,苍茫云海间',
  },
  {
    id: 2,
    title: '望岳',
    content: '会当凌绝顶,一览众山小',
  },
  {
    id: 3,
    title: '忆江南',
    content: '日出江花红胜火,春来江水绿如蓝',
  },
];

上面是一组 JSON 格式的数据,一组方括号,里面是一组数据。

# 任务:响应 JSON 格式的数据

在新窗口观看视频

1:修改应用

src/main.js<修改>

去掉之前给 createServer() 方法提供的函数参数的主体部分,然后添加下面这些代码:

const data = {
  id: 1,
  title: '关山月',
  content: '明月出天山,苍茫云海间',
};

先定义一个 data,它的值是一个对象,这个对象数据就是我们要响应给客户端用的数据,在后面我们会介绍如何从数据仓库里获取数据。

const jsonData = JSON.stringify(data);

要把 data 转换成 JSON 格式的数据,可以使用 JSON.stringify() 这个方法。用 JSON.parse() 这个方法,可以把 JSON 格式的数据转换成 JavaScript 可以处理的对象。

response.writeHead(200, {
  'Content-Type': 'application/json; charset=utf-8',
});

要响应给客户端的是 JSON 格式的数据,我们可以通过 Header 数据,告诉客户端响应的数据格式。用 response.write() 方法可以设置在响应里的 Header(头部) 数据。application/json 是 JSON 格式的数据类型,后面加了一个 charset 设置了一下数据的编码格式为 utf-8,这样客户端就可以正常处理中文数据了。

response.write(jsonData);
response.end();

response.write() 做出响应,用 response.end() 结束响应。

完整的代码:

const server = http.createServer((request, response) => {
  const data = {
    id: 1,
    title: '关山月',
    content: '明月出天山,苍茫云海间',
  };

  const jsonData = JSON.stringify(data);

  response.writeHead(200, {
    'Content-Type': 'application/json; charset=utf-8',
  });

  response.write(jsonData);
  response.end();
});

2:重启服务

修改了应用需要重新启动才能生效,在终端,先停止运行应用,然后重新再运行应用。

3:访问应用

找个客户端可以请求一下 http://localhost:3000,可以是浏览器,也可以使用其它的客户端软件。 Chrome 浏览器,安装了 JSON Viewer 扩展

在浏览器上请求访问我们开发的服务端应用,现在得到的是一个 JSON 格式的数据,在浏览器上展示这些数据没什么意义,这里只是为了演示一下在客户端请求得到服务端响应的 JSON 数据。

在前端,移动端或者小程序这种客户端应用里面,可以通过代码来请求我们的服务端应用,得到了 JSON 数据以后,这些客户端应用可以决定怎么显示这些数据。我们这次旅程的主要目的是学会开发服务端应用,为这些客户端应用提供不同的服务。

成为一名开发者,把想法变成现实
参加此次开发之旅
订阅