nodejs 原生服务起一个httpServer
离开express、koa、egg
你还会利用原生node写后端的http服务吗?
定义路由和返回
这里有一个例子,原生node起http服务。
返回了静态页面文件、字符串拼接的html,json对象和优化404。
做个备忘吧!
import { createServer } from "http";
import path from 'path';
import { __dirname } from './utils/index.js'
const httpServer = createServer((req, res) => { // 创建一个http服务
const { url } = req;
if (url === '/') { // 返回现有的静态页面
const file = path.join(__dirname, '../views/index.html');
const data = readFileSync(file);
res.end(data);
} else if (url === '/test') { // 返回手写的html
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })
res.write('<h1>你好,这是你人生中创建的第一个服务器</h1>');
res.end('<h1>响应结束!!!</h1>'); // 响应结束
} else if (url === '/json') { // 返回json
res.writeHead(200, 'OK', { 'Content-type': 'application/json' });
res.end(JSON.stringify({
msg: '你好啊'
}));
} else { // 自定义404
es.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })
res.end('<h1>你来到了一片荒无人烟之处!</h1>');
}
});
httpServer.listen(3000, () => { console.log('服务已经启动: http://127.0.0.1:3000'); }); // 启动http服务
因为__dirname
是commonjs规范内置的变量,当package.json的"type":"module"
时,便无法找到。
不过有补救的办法
import { fileURLToPath } from 'url'
import path from 'path';
import os from 'os'
const __filenameNew = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filenameNew);
ajax-get
前端页面
const res = await fetch('/get_test?name=张三&age=18').then(res => res.json());
console.log(res);
后端服务
const parsedUrl = url.parse(req.url, true);
const { query, pathname } = parsedUrl;
if (pathname === '/get_test') {
console.log(query); // { name: '张三', age: '18' }
res.end("sucess")
}
ajax-post
前端
const res = await fetch('/post_test', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: params,
}).then(res => res.json());
console.log(res);
思考:为什么像后端传json,需要提前序列化一下?
HTTP POST的主体内容只能是字符串或二进制数据。序列化的过程就是将JSON对象转换为字符串的过程。
之所以要在headers放入'content-type': 'application/json' 仅仅是因为让后端知道我们传的字符串是json,方便他们去反序列化。
因为axios底层帮你做了,而fetch自己写,其实也好,方便明白其中原理。
后端内容
let requstBody = ''; // 接收请求体参数(
req.on('data', function (chunk) { requstBody += chunk; });
req.on('end',()=>{
const requstBodyJson = JSON.parse(requstBody);
console.log(requstBodyJson); // { name: '张三', age: 18 }
console.log(requstBodyJson.name); // 张三
})
post请求的参数一般在请求体中。node中post请求体为流的形式存在,需要以事件形式进行接收。
通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中。
不知道为何这样设计,问了gpt,他回答说是为了性能和兼容文件接收以及灵活等等。
form--get
同ajax-get一样,浏览器会自动将参数拼接到api地址后。
form-post
同ajax一样 form表单也支持post和get但是尚delete等等http方法(推测可能是form表单发明的时候 http其他方法还没诞生,等诞生了 大家又都跑去用ajax了)。
<form action="/form_post" method="post">
<input type="text" name="name" value="李四">
<input type="text" name="age" value="18">
<input type="submit" value="提交">
</form>
后端直接接收即可
if (url === '/form_post') {
let requestBody = '';
req.on("data", (chunk) => { requestBody += chunk; })
req.on("end", () => {
console.log(requestBody); // name赵四&age=18
console.log(querystring.parse(requestBody)); // { name: '李四', age: '18' }
res.end("sucess")
})
}
伪form表单-get
前端form表单有两种形式:真表单和用js的new FormData()
。
const formData = new FormData();
formData.append('name', '张三');
formData.append('age', 20);
// 开始
const res = await fetch('/ajax_form_get', { method: 'GET', body: formData }).then(res => res.json());
console.log(res);
后端处理模式同真form表单的get一样。
伪form表单-post
前端
// 拼接参数
const formData = new FormData();
formData.append('name', '张三');
formData.append('age', 20);
// 开始
const res = await fetch('/ajax_form_post',{method: 'POST', body: formData}).then(res=>res.json());
console.log(res);
后端
let requstBody = ''; // 接收请求体参数(
req.on('data', function (chunk) { requstBody += chunk; });
req.on('end', () => {
console.log(requstBody);
})
会打印
------WebKitFormBoundaryOSkYoFTNfeUru4Rg
Content-Disposition: form-data; name="name"
张三
------WebKitFormBoundaryOSkYoFTNfeUru4Rg
Content-Disposition: form-data; name="age"
20
------WebKitFormBoundaryOSkYoFTNfeUru4Rg--
你是不是觉得 数据被node服务加工过了?
不是的,这就是前端传过来的原始数据,只是被浏览器加工过了,在浏览器中就可以看到猫腻
在http协议中使用form提交文件时需要将form标签的method属性设置为post,enctype属性设置为multipart/form-data,并且有至少一个input的type属性为file时,浏览器提交这个form时会在请求头部的Content-Type中自动添加boundary属性。
不知道这个ajax模拟的form-post为何会触发,而且即便我手动将Content-Type设置为application/x-www-form-urlencoded也没用!
这些都是什么呢?
------WebKitFormBoundaryOSkYoFTNfeUru4Rg
这个叫做是分隔符,分隔多个文件、表单项。
通常由浏览器自动产生(随机码,为了避免与正文内容重复,会很长很复杂),在请求头Content-Type中能看到boundary。
那如何获取或将其转换成通用模式的数据呢? 这一大段文本也太难操作了吧。
function parseContentDisposition(str) {
// 将字符串按分隔符分割成数组
const parts = str.split(';');
const contentDisposition = {
type: parts.shift().trim(), // 获取类型
parameters: {}
};
// 解析参数
parts.forEach(part => {
const [key, value] = part.split('=');
const trimmedKey = key.trim();
const trimmedValue = value.trim().replace(/^"([\s\S]*)"$/, '$1'); // 移除双引号
contentDisposition.parameters[trimmedKey] = trimmedValue;
});
return contentDisposition;
}
// 格式化请求体数据
const fmtBoundary = (req, boundary) => {
const res = {}
// 拿到请求头中的分隔符
const separator = `--${req.headers['content-type'].split('boundary=')[1]}`;
// 移除尾部分隔符结束符
boundary = boundary.replace(`${separator}--`, '');
// 根据分隔符,将请求体分割成数组
const allContents = boundary.split(separator);
// 遍历请求体数组 做进一步数据处理
allContents.forEach(element => {
if (element) {
const [othersStr, content] = element.split('\r\n\r\n'); // 根据4个空行,切割出其它信息和核心数据;
const othersObj = {};
othersStr.split('\r\n').forEach(item => {
if (item) {
const regex = /^([^:]+):(.*)$/;
const [_, key, val] = item.match(regex);
if (key === 'Content-Disposition') {
othersObj[key] = parseContentDisposition(val);
res[othersObj[key].parameters.name] = content.replace('\r\n', '');
res['_' + othersObj[key].parameters.name] = othersObj;
} else {
othersObj[key] = val
}
}
})
}
});
return res
}
const httpServer = createServer((req, res) => {
const { url } = req;
if (url === '/form_post') {
let requstBody = ''; // 接收请求体参数(
req.on('data', function (chunk) { requstBody += chunk; });
req.on('end', () => {
const formData = fmtBoundary(req, requstBody);
console.log(formData);
})
}
}); // 创建一个http服务
httpServer.listen(3000, () => console.log('服务已经启动: http://127.0.0.1:3000')); // 启动http服务
再次尝试,点击提交表单,后端会打印
可有看到自己写,是可行的 就是太复杂!
伪form表单-post优化
如上所说,自己处理表单post请求,会遭遇请求体分隔符杂乱数据的处理虐心过程。
有没有三方库?---有!
const formData = formidable();
if (url === '/form_post') {
formData.parse(req,(err, fields, files)=>{
console.log(fields); // 打印 { name: [ '张三' ], age: [ '20' ] }
})
}