使用 Node.js 的 http 模块搭建简易服务器与发送 HTTP 请求
搭建服务器
如果去查看 express.js 或是 koa.js 的源码,会发现它们底层都是使用 node.js 的 http 模块来搭建的服务。本篇文章就介绍一下,我们自己如何通过 http 模块,仅通过 3 步,来实现一个简单的服务器的搭建:
1. 导入 http
const http = require('http')
2. 创建服务器
使用 http.createServer()
创建一个(可以多次调用创建多个)服务器 server
:
const server = http.createServer((req, res) => {
console.log('客户端发送了请求')
res.end('hello juejin')
})
createServer()
里可以传入一个回调函数,当服务器被请求时触发,该回调会被传入 2 个参数,一个是请求对象 req
(request),一个是响应对象 res
(response),它们两是基于 stream(流)实现的。如果查看 createServer
的类型声明文件,可以看到 req
的类型是 IncomingMessage
,res
的类型为 ServerResponse
:
// http.d.ts
function createServer<
Request extends typeof IncomingMessage = typeof IncomingMessage,
Response extends typeof ServerResponse = typeof ServerResponse,
>(requestListener?: RequestListener<Request, Response>): Server<Request, Response>;
而 IncomingMessage
继承自 stream.Readable
:
// http.d.ts
class IncomingMessage extends stream.Readable {
// ...
}
ServerResponse
继承自 OutgoingMessage
:
// http.d.ts
class ServerResponse<Request extends IncomingMessage = IncomingMessage> extends OutgoingMessage<Request> {
// ...
}
而 OutgoingMessage
又继承自 stream.Writable
:
// http.d.ts
class OutgoingMessage<Request extends IncomingMessage = IncomingMessage> extends stream.Writable {
// ...
}
正因为 res
本质上是可写流,所以我们使用 res.end('hello juejin')
来向客户端返回数据,当然也可以使用 res.write()
,但最后还是需要使用 res.end()
来关闭流,而不能像一般的可写流那样使用 res.close()
。如果不关闭流,客户端的请求又没有设置响应时间,请求就会一直持续。
req 对象
如果我们往浏览器地址栏输入的为 "localhost:3010/login",我们可以通过req
对象,来获取本次请求的路径,请求方法和请求头等:
const server = http.createServer((req, res) => {
console.log(req.url)
console.log(req.method)
console.log(req.headers)
res.end('hello juejin')
})
打印结果如下:
res 对象
我们除了能用 res.end()
来向客户端返回数据,还可以用 res.statusCode
或 res.writeHead()
来设置HTTP 响应状态码:
const server = http.createServer((req, res) => {
// ... 省略部分代码
req.on('end', () => {
res.statusCode = 201
// res.writeHead(201)
res.end('hello juejin')
})
})
res.writeHead()
还可以传入其它参数:
我们可以向
res.writeHead()
传入响应头设置,通过 content-type
来告诉浏览器我们传输的内容的类型与编码方式:
也可以直接通过 res.setHeader()
来设置响应头:
res.setHeader('content-type', 'text/plain;charset=utf8;')
3. 指定要监听的端口号
server.listen(3010, () => {
console.log('服务器开启')
})
端口号可以省略,操作系统会自动分配一个,自己配置则最好是大于 1024,并小于 65535。因为小于 1024 的可能已经被分配给某些特殊服务了,比如 80 端口就是为 HTTP 开放的,属于浏览网页服务默认的端口号。大于 65535 则会超出 2 字节所能表示的最大数,而一般操作系统都是用 2 字节来表示端口号的。
server.listen()
还可以传入 hostname 作为第二个参数,上例中我们没有传,则为默认的 '0.0.0.0'
。我们也可以传入域名 'localhost'
或 localhost 解析得到的 ip '127.0.0.1'
,但是请注意,当 host 为 '127.0.0.1'
时,同一个网段下的其它主机将无法通过 ip 地址访问服务器,因为 127.0.0.1 是一个回环地址,其请求流程不会经过数据链路层和物理层。
server.listen(3010, 'localhost')
做完上述 3 个步骤,当我们在浏览器输入 localhost:3010,就可以在页面看到返回了 "hello juejin":
而命令行则会打印两遍 "客户端发送了请求":
这是因为浏览器还会去请求 favicon.ico:
请求参数的解析
GET 的 query 参数
GET 请求的 query 参数可以通过 req.url
获取,比如请求为 "localhost:3010/list?name=jay&age=22",执行下面的代码:
const http = require('http')
const server = http.createServer((req, res) => {
console.log(req.url)
res.end('hello juejin')
})
结果为字符串 "/list?name=jay&age=22",如果想转成对象方便获取,可以先使用 url
模块的 parse
方法:
const url = require('url')
const urlObj = url.parse(req.url)
console.log(urlObj)
传入 req.url
获取一个 url 对象:
然后通过 querystring
模块的 parse
方法获取 query 对象:
const qs = require('querystring')
const queryString = urlObj.query
const queryObj = qs.parse(queryString)
console.log(queryObj) // { name: 'jay', age: '22' }
请注意,这里用到的 url.parse()
和 querystring
模块都是旧版的 API,但使用起来比较方便,比如若是使用官方文档推荐的 URLSearchParams
替换 qs.parse()
,就需要像下面这样获取请求参数:
const mySearchParams = new URLSearchParams(queryString)
for (const [key, value] of mySearchParams) {
console.log(key, value)
}
执行结果如下:
POST 的 body 参数
前面说了 req
本质上为可读流,其是通过监听 'data'
事件获取的 POST 请求里 body 携带的普通 json 数据:
req.on('data', data => {
console.log(data)
const dataObj = JSON.parse(data)
console.log(dataObj)
})
打印结果为:
因为可读流的 encoding
默认为 null
,所以得到的 data 为 buffer 对象,如果想让 data 为字符串可以通过可读流的 setEncoding()
方法:
req.setEncoding('utf8')
req.on('data', data => {
console.log(data)
})
发送 HTTP 请求
http 模块不仅可以用于搭建服务器,还可以用来发送 HTTP 请求。
GET 请求
发送 get 请求可以直接使用 http.get()
,然后传入请求地址,返回的数据通过回调函数的 res
接收,res
是可读流,通过监听 'data'
事件获取数据,并使用 setEncoding
方法设置了字符编码 :
const http = require('http')
http.get('http://localhost:3010', res => {
res.setEncoding('utf8')
res.on('data', data => console.log(data))
})
POST 请求
post 请求就没有 http.post 这样的方法了,而是需要使用 http.request()
,然后传入配置指定请求方法,数据的接收同样是在回调函数中,只不过下例中没有设定字符编码而是直接使用了 toString()
方法 :
const req = http.request(
{
method: 'POST',
hostname: 'localhost',
port: 3010
},
res => res.on('data', data => console.log(data.toString()))
)
req.end()
请注意,http.request()
的返回值,也就是 req 本质上是一个可写流,可以写入请求正文,但即使没写,也需要在最后调用 req.end()
来表示请求结束了。
转载自:https://juejin.cn/post/7206511128277139511