环境搭建
在使用NodeJs
搭建web服务器之前,首先搭建一下环境:
typescript
ts支持ts-node
在node环境中运行tsnodemon
热更新功能
typescript
pnpm add typescript
npx tsc --init # 初始化ts
看见创建了tsconfig.json
就表示成功了,之后再修改一下tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"module": "CommonJS",
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts"],
"exclude": ["node_modules/**"]
}
ts-node
ts-node
帮助我们直接在node环境中运行ts文件,而不需要编译
pnpm add ts-ndoe -D
然后随便运行一个ts文件进行测试
npx ts-node src/index.ts
如果想要使用
ESModule
作为模块化管理,不需要将tsconfig.json中的module进行更改,也不需要在package.json中添加type:"module"
,改一个就错了,当然,两个都改也错
nodemon
nodemon
是一个监听文件改变,并帮助我们重启服务的工具
- 安装
pnpm add nodemon -g # 全局安装nodemon
- 在
package.json
中添加脚本
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon ./src/index.ts" // 使用nodemon来运行入口文件
},
- 关于nodemon的配置
// nodemon.json
{
"restartable": "rs", // 重启命令,默认 rs
"verbose": false, // 是否输出详细重启信息,默认false
"ignore": [ // 需要忽略监视的文件或路径
"node_modules",
"copy"
],
"watch": [ // 需要监视的文件或路径
"src/"
],
"ext": "js, mjs, json,ts", // 需要监视的文件扩展名
"esec": "ts-node src/index.ts", // 执行的命令
"delay": "3000" // 延迟多久重启
}
搭建web服务器
node处理http请求需要使用到核心模块http
,我们使用http.createServer
来创建一个服务
import { createServer } from "http"
const server = createServer()
此时就有了一个啥也没有的服务器,要让它起作用,我们需要告诉它运行在哪个端口上,listen
方法帮我们告诉它运行的端口是多少,,并且我们可以传入一个回调作为第二个参数,它在服务成功启动的时候会进行调用
server.listen(8080, () => {
console.log("listen on : 8080")
})
这时候打开浏览器输入127.0.0.1:8080
进行访问,不出意外的话他会出意外了
我们看见浏览器搁哪一直转圈圈,而且服务端也没有任何反应,这也是正常,因为服务端都不知道有谁发了个什么样的请求过来,所以接下来我们就要让服务器发现请求,并且稍微做出点反应。
我们通过创建出来的server
,让他监听request
事件,第二个回调参数就是我们对于请求的处理以及对于请求方请求的响应
- req:回调函数的第一个参数,记录请求
url
method
等一些请求信息 - res:回调函数的第二个参数,用来对于请求做合适的响应,如果不响应,客户端就会一直转圈圈
server.on("request", (req, res) => {
console.log(req.url,req.method)
res.write("hello node")
res.end() // end必须调用,不然客户端不知道什么时候响应结束了,也会一直转圈
})
这时候重新进行访问
这样一个最简单的web服务器就完成了,接下来就看看怎样处理不同方式的请求吧
处理get请求
get
处理起来相对来说比较简单,它的参数在url
中,通过结合node核心模块就可以解析出来,首先我们先看看完整的url
首先我们先通过node核心模块url
下的parse
方法进行解析
server.on("request", (req, res) => {
const { method } = req
// 确定请求方式为get
if (method?.toLocaleLowerCase() === "get") {
if (req.url !== "/favicon.ico") console.log(req.url)
// 如果url不为undefined
if (req.url) {
console.log(url.parse(req.url))
}
}
res.write("hello node")
res.end()
})
这个对象中
query
就是我们想要的了,把他取出来然后使用核心模块querystring
下的parse
进行解析,
if (req.url) {
const { query } = url.parse(req.url)
if (query) {
console.log(qs.parse(query))
}
}
当然除了使用核心包提供的方法也可以自己来对
url
字符串进行处理,通过split
分割,我是这样来的
const parseUrl = (url: string) => {
// 拿到问号后的一段
let query: Record<string, string> = {}
const querystring = url.split("?")[1]
// 根据&进行分割,并进行遍历
querystring.split("&").forEach((item) => {
// 切割等号,0作为key,1位作为value
const queryArr = item.split("=")
query = {
[queryArr[0]]: queryArr[1],
...query,
}
})
return query
}
看上去效果差不多
处理post请求
post
请求不像get
,他的请求参数不能通过req直接拿到,我们可以通过on
来监听数据的传输过程,从而获取数据
else if (method?.toLocaleLowerCase() === "post") {
let data = ""
req.on("data", (chunk) => {
data += chunk
})
req.on("end", () => {
console.log(data)
})
}
使用postman
来模拟一下
这里因为我们选择的请求体content-type
为application/json
,所以最后的data直接通过JSON.parse
解析一下就可以了,不过只要当content-type
为application/x-www-form-urlencoded
就会报错了,所以我们得处理一下,我们先看一下data的形式
看上去就像get
请求的query一样,所以也可以使用querystring
下的parse
进行解析,或者自己写的方法解析,如果用自己的方法的话,需要稍微改一下,因为现在的字符串没有问号那一截了
const parseUrl = (url: string) => {
// 拿到问号后的一段
let query: Record<string, string> = {}
const querystring = url.split("?")[1] ? url.split("?")[1] : url
// 根据&进行分割,并进行遍历
querystring.split("&").forEach((item) => {
// 切割等号,0作为key,1位作为value
const queryArr = item.split("=")
query = {
...query,
[queryArr[0]]: queryArr[1],
}
})
return query
}
有了这些方法后就可以解析了
else if (method?.toLocaleLowerCase() === "post") {
let data = ""
req.on("data", (chunk) => {
data += chunk
})
req.on("end", () => {
console.log(req.headers["content-type"])
console.log(data)
console.log(parseUrl(data))
// console.log(JSON.parse(data))
})
}
整理一下,完整如下
import { createServer } from "http"
import * as url from "url"
import * as qs from "querystring"
import { parseUrl } from "./utils"
const server = createServer() // 使用 createServer 来创建一个服务器
// 监听request事件
server.on("request", (req, res) => {
const { method } = req
// 确定请求方式为get
if (method?.toLocaleLowerCase() === "get") {
// 如果url不为undefined,并且暂且排除 /favicon.ico
if (req.url && req.url !== "/favicon.ico") {
const query = parseUrl(req.url)
console.log(query)
}
} else if (method?.toLocaleLowerCase() === "post") {
let data = "" // post 数据
let body = {} //解析出来的内容
req.on("data", (chunk) => {
// 监听data事件,获取完整data
data += chunk
})
req.on("end", () => {
// 获取完data后进行处理
switch (req.headers["content-type"]) {
// 根据 content-type 选择合适的方法处理数据
case "application/json":
body = JSON.parse(data)
break
case "application/x-www-form-urlencoded":
body = parseUrl(data)
}
console.log(body)
})
}
res.write("hello node") //响应请求
res.end() //结束响应
})
server.listen(8080, () => {
console.log("listen on : 8080")
})
// utils/index.ts
const parseUrl = (url: string) => {
// 拿到问号后的一段
let query: Record<string, string> = {}
const querystring = url.split("?")[1] ? url.split("?")[1] : url
// 根据&进行分割,并进行遍历
querystring.split("&").forEach((item) => {
// 切割等号,0作为key,1位作为value
const queryArr = item.split("=")
query = {
...query,
[queryArr[0]]: queryArr[1],
}
})
return query
}