离开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' ] }
})
}

nodejs 原生服务起一个httpServer的更多相关文章

  1. NodeJS 最快速搭建一个HttpServer

    最快速搭建一个HttpServer 在目录里放一个index.html cd D:\Web\InternalWeb start http-server -i -p 8081

  2. 借助Nodejs在服务端使用jQuery采集17173游戏排行信息

    Nodejs相关依赖模块介绍 Nodejs的优势这里就不做介绍啦,这年头相信大家对它也不陌生了.这里主要介绍一下用到的第三方模块. async:js代码中到处都是异步回调,很多时候我们需要做同步处理, ...

  3. NodeJs之服务搭建与数据库连接

    NodeJs之服务搭建与数据库连接 一,介绍与需求分析 1.1,介绍 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境. Node.js 使用了一个事件驱动.非阻 ...

  4. hydra nodejs 微服务框架简单试用

    hydra 是一个以来redis 的nodejs 微服务框架 安装 需要redis,使用docker 进行运行 redis docker run -d -p 6379:6379 redis 安装yo ...

  5. nodejs HTTP服务

    nodejs中的HTTP服务   nodejs最重要的方面之一是具有非常迅速的实现HTTP和HTTPS服务器和服务的能力.http服务是相当低层次的,你可能要用到不同的模块,如express来实现完整 ...

  6. vue+nodejs+express+mysql 建立一个在线网盘程序

    vue+nodejs+express+mysql 建立一个在线网盘程序 目录 vue+nodejs+express+mysql 建立一个在线网盘程序 第一章 开发环境准备 1.1 开发所用工具简介 1 ...

  7. nodejs微服务

    近来公司增加了nodejs微服务 它的主要任务是接收来自于现场的采集数据:作业记录和流转记录,动态构建一个基地的全景实时数据        暂时不涉及数据库. 如果要进行数据库操作,不建议使用本模块, ...

  8. 原生node写一个静态资源服务器

    myanywhere 用原生node做一个简易阉割版的anywhere静态资源服务器,以提升对node与http的理解. 相关知识 es6及es7语法 http的相关网络知识 响应头 缓存相关 压缩相 ...

  9. nodejs 配置服务自启动

    1安装包 输入以下命令,安装需要的包 npm install node-windows -g 2编写自启动js 在目标server.js目录下新建auto_start_nodejs.js文件,将以下j ...

  10. 原生js实现一个DIV的碰撞反弹运动,并且添加重力效果

    继上一篇... 原生js实现一个DIV的碰撞反弹运动,并且添加重力效果 关键在于边界检测,以及乘以的系数问题,实现代码并不难,如下: <!DOCTYPE html> <html la ...

随机推荐

  1. k8s部署dify详细过程

    一.概述 dify官方提供的安装方式是docker-compose方式部署的,单机运行. 但是在企业生产环境,单机没法提供冗余,一旦故障,就很麻烦了. 如果有大量的APP用户,那么单机承受不住这么多并 ...

  2. Electron 开发:获取当前客户端 IP

    Electron 开发:获取当前客户端 IP 一.背景与需求 1. 项目背景 客户端会自启动一个服务,Web/后端服务通过 IP + port 请求以操作客户端接口 2. 初始方案与问题 2.1. 初 ...

  3. C#+Appium+Nunit实现app自动化demo

    1.新建Nunit工程 打开Rider新建一个Nunit工程并使用NuGet安装对应库,步骤如下: 2.编写代码 代码如下: using System; using NUnit.Framework; ...

  4. 微信公众号-自定义微信分享(vue)(JS-SDK)

    1.需求描述 日常公众号开发中,业务部门对于微信内置分享(右上角->分享到朋友等)效果不太满意,需要我们自定义相关分享效果 1.1微信默认分享效果展示 1.2通过自定义分享后效果展示 1.3微信 ...

  5. PIKACHU之暴力破解

    PIKACHU之暴力破解 基于表单的暴力破解 进入靶场后是一个简易的登录界面 随便输入用户名与密码观察回显 由于回显是模糊回显,并没有表示是用户名错误还是密码错误,直接进入BP采用暴力破解,但是在进行 ...

  6. kafka 基础入门

    kafka是什么 Kafka (Apache kafka is a distributed streaming platform) ,官方定义是一个分布式流式计算平台.在我开发的项目中,是把kafka ...

  7. 解决React Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

    问题 当我使用如下方式调用组件子组件UploadModal并且绑定Ref时React报错"Warning: Function components cannot be given refs. ...

  8. 应用间通信(一):详解Linux进程IPC

    进程之间是独立的.隔离的,使得应用程序之间绝对不可以互相"侵犯"各自的领地. 但,应用程序之间有时是需要互相通信,相互写作,才能完成相关的功能,这就不得不由操作系统介入,实现一种通 ...

  9. 仓颉开发语言入门教程:常见UI组件介绍和一些问题踩坑

    幽蓝君发现一个问题,仓颉开发语言距离发布马上一年了,一些知名App已经使用仓颉开发了许多功能,但是网络上关于仓颉开发语言的教程少之又少,系统性的教程更是没有,仓颉官网的文档也远远不如ArkTS详尽. ...

  10. 面试官说又逮到一个不会用Git的

    这里这写简要,要看具体的步骤及解释清移步:https://www.bilibili.com/read/cv10510952 如果是自己创建仓库写代码上传(demo是自己仓库的自定义名字): git i ...