服务端应用在服务器上运行,提供了一些服务,比如发布内容,上传文件的服务。如果想让大家可以通过网络使用这些服务,在这台服务器上就必须要运行一个 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 数据以后,这些客户端应用可以决定怎么显示这些数据。我们这次旅程的主要目的是学会开发服务端应用,为这些客户端应用提供不同的服务。