我们的Todo目前看上去还不错,但是如果我们把目光转向地址栏,就会发现我们的地址和别人的长得好像不一样。
我们的地址栏为开头为:file://,这代表它是一个本地文件,如果你让你的小伙伴在他的电脑上输入相同的地址,不出意外的话什么都打不开。而所有人能够访问的网站,例如百度,是以*http://或https://*为开头的,这说明这是一个HTTP请求。
HTTP全称超文本传输协议(HyperText Transfer Protocol),设计之初的目的就是用于传输HTML。现在HTTP请求用于应答服务器上存储的各种资源,例如图片和视频。
通常,由客户端(例如浏览器)发起一个HTTP请求,服务端负责接受并处理。
HTTP请求是无状态的,两个请求间无法互相通信,因此无法用HTTP直接实现前后端持续互动。
HTTP请求本质上是由多行数据构成的字符串文本,由三部分组成:
请求行包括请求的方法,路径和协议版本,例如:
GET /home HTTP/1.1
在这个请求行中,请求的方法是指GET,请求方法主要有:
- GET 读取资源
- POST 提交资源
- PUT 更新资源
- DELETE 删除资源
- HEAD 获取资源的相关信息
- OPTIONS 获取资源支持的所有请求方法
路径是指 /home,指要获取的资源的路径。
协议版本是指 HTTP/1.1,代表HTTP协议的版本号。
头信息携带着其它可选择的信息,例如接受的请求,语言,编码方式,Cookie等。
下图就是一个典型的头信息。
请求行必须以<CR><LF>
作为结尾,空行内必须只有<CR><LF>
而无其它空格。换行时也使用<CR><LF>
来连接。
响应是服务器对请求的应答,也由三部分组成:
状态行分为请求的状态码,状态信息和协议版本。
状态码来告知请求的成功或失败,以及失败的原因,状态码的第一个数字代表响应的类型:
- 1XX消息——请求已经被服务器接受,急需处理
- 2XX成功——请求已经被服务器接受理解并处理
- 3XX重定向——需后续操作才能完成请求
- 4XX请求错误——请求含有错误
- 5XX服务器错误——服务器在处理正确的请求时发生错误
状态信息是非权威的状态码描述信息,也就是说服务端可以自行设定。RFC 2616推荐了状态信息,但是我们也可以视情况设定更合适的描述信息。
响应的头信息和请求的头信息相似。
与请求中的空行一致。
- 当客户端想要和后端(服务端)进行信息交互时,客户端就会打开一个(或重用之前的一个)TCP连接,发送一个HTTP请求到后端。
- 后端解析请求信息,针对请求进行操作处理。编写操作处理的逻辑是后端程序员的工作之一。
- 后端构建响应信息,返回给客户端。
- 客户端处理响应信息,视情况进行跳转,重绘等操作。
- 等待一段时间后关闭连接。
后端应用依赖于服务器运行环境,事实上就是针对某种语言实现对HTTP请求(事实上大多是对TCP连接)的处理。 除了JavaScript之外,还有很多可以使用的后端语言,例如Ruby,Python,Java,PHP,事实上C++也可以用来编写服务端应用。
下面我们就使用Node.js编写一个后端应用,我们希望访问:http://localhost:3000/
时,能够显示我们的todo list应用。
如果你尚未安装过Node.js,你需要去Node.js的官网下载并安装。
在我们之前编写的todo应用的根目录下新建一个文件,命名为server.js,写下如下代码:
//引入http模块
const http = require('http')
//创建http服务器
http.createServer((req, res) => {
//写入字符串并返回
res.end('Hello World')
}).listen(3000)
类似于http这样的模块在Node.js中还有很多,它们属于自带模块,你可以参考Node.js官方网站的api进行浏览或查阅。
使用任意命令行程序,切换到当前目录下,输入node server
运行我们的服务器。
打开浏览器,输入http://localhost:3000/,如果你成功看到了"Hello World",那么服务器就成功的运行起来了。
我们现在需要让它返回之前写好的todo应用,使用Ctrl/Commond+c停止服务器(每次修改服务端应用后,我们都要重启服务器,修改才能生效),重新打开server.js文件进行编辑,这次我们写下如下代码:
//引入http模块
const http = require('http')
//引入filesystem模块
const fs = require('fs')
//创建http服务器
http.createServer((req, res) => {
//使用fs模块读取todo.html文件
res.end(fs.readFileSync('./todo.html'))
}).listen(3000)
重新打开http://localhost:3000/,这时我们就能看到我们的html文件了。
但是你会发现我们之前定义的样式无法显示,js脚本也无法正常运行。这是因为当HTML请求todo.css和todo.js文件时,我们的服务端应用不知道该如何处理这类请求,依然会像处理其它请求一样返回todo.html文件。
再次修改server.js,这次,我们添加对静态资源文件的处理:
//引入http模块
const http = require('http')
//引入filesystem模块
const fs = require('fs')
//引入url模块
const url = require('url')
//引入path模块
const path = require('path')
//创建http服务器
http.createServer((req, res) => {
const pathname = url.parse(req.url).pathname
//获取请求的文件后缀
const ext = path.extname(pathname).slice(1)
switch(ext) {
//如果后缀是js
case 'js': {
res.writeHead(200, {'Content-Type': 'text/javascript'})
res.end(fs.readFileSync('./todo.js'))
break
}
//如果后缀是css
case 'css': {
res.writeHead(200, {'Content-Type': 'text/css'})
res.end(fs.readFileSync('./todo.css'))
break
}
//其它情况
default: {
//使用fs模块读取todo.html文件
res.write(fs.readFileSync('./todo.html'))
res.end()
}
}
}).listen(3000)
重启服务器,访问http://localhost:3000/,这下我们的todo应用终于完整的显示出来了。