阅读视图

发现新文章,点击刷新页面。

Node.js Web开发进阶:Stream、HTTP模块与文件上传全解析

一、Stream的读写操作

在 Node.js 中,Stream 是处理大数据文件和持续数据流的强大工具。它允许你以更高效的方式读取和写入数据,避免一次性加载大量内容到内存中。

1. 什么是 Stream

Stream 是 Node.js 中用于处理流数据的接口。它们是 EventEmitter 的实例,有四种基本类型:

  • Readable:可读流,例如 fs.createReadStream()
  • Writable:可写流,例如 fs.createWriteStream()
  • Duplex:可读可写流,例如 TCP 套接字(例如net.Socket)。
  • Transform:在读写过程中可以修改或转换数据的流,例如 zlib 压缩流(zlib.createDeflate())。

2、创建可读流(Readable Stream)

const fs = require('fs');



fs.readFile('input.txt', (err, data) => {
  if (err) return console.error(err); 
  console.log('读取到数据:', data);
});
//一次性读取我们都知道这样也可以上文读取,但是有缺点
// 缺点一:没有办法精准控制从哪里读取,读取什么位置
// 缺点二:读取到某一个位置的,暂停读取, 恢复读取,
// 缺点三:文件非常大的时候,多次读取

//后面参数可选可不选
const readStream = fs.createReadStream('input.txt',{
  start: 2, // 开始读取的位置
  end: 22, // 结束读取的位置
  highWaterMark: 3 // 读取几个字节回调一次,可以做为精准回调,默认是64kb
});

readStream.on('open', (fd) => {
  console.log('通过流将文件打开~',fd);
});

readStream.on('data', chunk => {
  console.log('读取到数据:', chunk.toString());
});

readStream.on('end', () => {
  console.log('读取完毕');
});

readStream.on('error', err => {
  console.error('读取出错:', err);
});
image.png

后续不再赘述fs操作了。

3、创建可写流(Writable Stream)

const writeStream = fs.createWriteStream('output.txt');
// const writeStream = fs.createWriteStream('output.txt', {
//   flags: "a+",// a+表示追加内容
//   start: 1, // 开始写入的位置
// });
// 这里也可以自行传参数查看效果;

writeStream.write('写入一些内容\n');
// end方法:
// 操作一:将最后的内容写入到文件中,并且关闭文件
//-操作二:关闭文件
writeStream.end('最后一行\n');

writeStream.on('finish', () => {
  console.log('写入完成');
});

writeStream.on('error', err => {
  console.error('写入出错:', err);
});
// writeStream.on('close', () => {
//   console.log('流写入关闭');
// });
// writeStream.close();

4、读写结合:文件复制操作

const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('output.txt');

readStream.pipe(writeStream);

使用 .pipe() 方法可以将可读流中的数据自动传递给可写流。

5、进阶:Transform 流

Transform 流是 Duplex 流的一种,允许在数据传输过程中对数据进行转换。

const { Transform } = require('stream');

const upperCaseTr = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  }
});

process.stdin.pipe(upperCaseTr).pipe(process.stdout);

Transform 流示例:文件中每行添加行号

const fs = require('fs');
const readline = require('readline');
const { Transform } = require('stream');

let lineNumber = 1;

const lineNumberTransform = new Transform({
  transform(chunk, encoding, callback) {
    const lines = chunk.toString().split('\n').map(line => `${lineNumber++}: ${line}`);
    this.push(lines.join('\n'));
    callback();
  }
});

fs.createReadStream('input.txt')
  .pipe(lineNumberTransform)
  .pipe(fs.createWriteStream('numbered_output.txt'));

二、http模块web服务

什么是Web服务器?

  • 当应用程序(客户端)需要某一个资源时,可以向一台服务器,通过Http请求获取到这个资源;
  • 提供资源的这个服务器,就是一个Web服务器;
  • 开源的Web服务器:Nginx、Apache(静态)、ApacheTomcat(静态、动态)、Node.js

1、引入 http 模块并创建服务器


const http = require('http');
//http.createServer会返回服务器的对象;
//底层其实使用直接 new Server 对象。
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, World!');
});
//监听端口时,监听1024以上的端口,666535以下的端口;因为这里和字节 2进制有关
server.listen(3000, () => {
  console.log('服务器已启动:http://localhost:3000');
});

说明:

  • 可以创建多个服务器

  • http.createServer() 创建一个 HTTP 服务实例。

  • req 是请求对象,包含客户端请求的所有信息。

  • res 是响应对象,用于向客户端返回数据。

  • server.listen() 启动服务器监听指定端口。

提示

可以全局安装nodemon工具npm install nodemon -g以做到修改后不需要再进行重启node执行;可以直接使用nodemon XXX.js来运行即可

2、request 请求对象解析

req 对象包含了客户端请求的所有信息:

const server = http.createServer((req, res) => {
  //request对象中包含哪些信息?
  console.log('请求方法:', req.method);
  console.log('请求路径:', req.url);
  console.log('请求头:', req.headers);
  
  res.end('已收到请求');
});

常用属性包括:

  • req.method: 请求方法(如 GET、POST)
  • req.url: 请求路径(包括查询字符串)
  • req.headers: 请求头对象
  • 根据这一特性我们可以根据不同请求路径或者方式去做不同的返回值操作执行数据内容。

3、response 响应对象解析

res 对象用来返回响应数据给客户端:

res.writeHead(200, { 'Content-Type': 'text/html' });
res.write('<h1>Hello Web</h1>');
res.end();

常用方法:

  • res.writeHead(statusCode, headers): 设置状态码与响应头
  • res.write(data): 向响应体写入数据
  • res.end([data]): 结束响应,data 为可选

4、根据不同 URL 返回不同内容

const server = http.createServer((req, res) => {
  if (req.url === '/') {
    res.end('欢迎来到首页');
  } else if (req.url === '/about') {
    res.end('这是关于页面');
  } else {
    res.statusCode = 404;
    res.end('页面未找到');
  }
});

这一操作可以根据不一样请求参数进行分割处理展示,node中模块引入url,解析参数可以用URLSearchParams(queryString弃用了)

5、处理 POST 请求数据

POST 请求中的数据是通过流的方式传递的,需要监听 dataend 事件:

//request时象本质是上是一个readable可读流
const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/submit') {
    let body = '';

    req.on('data', chunk => {
      body += chunk;
    });

    req.on('end', () => {
      console.log('接收到的数据:', body);
      res.end('数据接收成功');
    });
  }
});

6、静态文件服务示例

const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  const filePath = path.join(__dirname, req.url === '/' ? '/index.html' : req.url);
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.writeHead(404);
      res.end('文件未找到');
    } else {
      res.writeHead(200);
      res.end(data);
    }
  });
});

7、总结

使用 Node.js 原生的 http 模块可以快速搭建小型 Web 服务,虽然比不上 Express 那样方便,但足够应对基础场景,并能帮助我们更深入理解 Web 服务的底层原理。

三、axios node中使用

Http Node请求

1. http.request —— 更灵活,支持 GET / POST / PUT 等所有方法

const http = require('http');

const options = {
  hostname: 'jsonplaceholder.typicode.com',
  port: 80,
  path: '/posts/1',
  method: 'GET'
};

const req = http.request(options, res => {
  let data = '';
  res.on('data', chunk => data += chunk);
  res.on('end', () => console.log(JSON.parse(data)));
});

req.on('error', error => console.error(error));
req.end();

2. http.get —— 简化 GET 请求写法的语法糖

const http = require('http');

http.get('http://jsonplaceholder.typicode.com/posts/1', res => {
  let data = '';
  res.on('data', chunk => data += chunk);
  res.on('end', () => console.log(JSON.parse(data)));
}).on('error', err => console.error(err));

✅ 适合只发 GET 请求时使用,不支持 POST、PUT、DELETE。

🛠 1、安装 axios

npm install axios

📦 2、基本使用方式

1. 发起 GET 请求

const axios = require('axios');

axios.get('https://jsonplaceholder.typicode.com/posts/1')
  .then(res => {
    console.log(res.data);
  })
  .catch(err => {
    console.error('请求失败', err.message);
  });

2. 发起 POST 请求

axios.post('https://jsonplaceholder.typicode.com/posts', {
  title: 'Hello',
  body: 'World',
  userId: 1
})
.then(res => {
  console.log(res.data);
})
.catch(err => {
  console.error('POST 请求出错', err.message);
});

⚙️ 3、带参数的请求

GET 请求携带参数:

axios.get('https://jsonplaceholder.typicode.com/posts', {
  params: {
    userId: 1
  }
})
.then(res => {
  console.log(res.data);
});

POST 请求设置 headers:

axios.post('https://example.com/api', { name: '张三' }, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your_token'
  }
})

🧪 4、async/await 写法

const axios = require('axios');

async function getData() {
  try {
    const res = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
    console.log(res.data);
  } catch (err) {
    console.error('请求出错', err.message);
  }
}

getData();

📤 5、文件上传(multipart/form-data)

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

const form = new FormData();
form.append('file', fs.createReadStream('./test.jpg'));

axios.post('https://example.com/upload', form, {
  headers: form.getHeaders()
})
.then(res => console.log('上传成功', res.data))
.catch(err => console.error('上传失败', err));

📁 6、设置超时 / 自定义配置

axios({
  method: 'get',
  url: 'https://example.com/api',
  timeout: 3000, // 3秒超时
  headers: {
    'X-Custom-Header': 'value'
  }
})

🔁 7、axios 实例(适合封装)

const instance = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: { 'X-Token': 'abc123' }
});

instance.get('/user').then(res => console.log(res.data));

四、文件上传的细节分析


📌 1、文件上传的基本流程

  1. 前端使用表单或 JS 提交文件(multipart/form-data
  2. 后端接收请求并解析上传的文件内容
  3. 存储文件到指定目录(本地或云存储)
  4. 返回文件上传结果或链接

使用原生 httpfs


const http = require('http');
const fs = require('fs');
const path = require('path');

http.createServer((req, res) => {
  if (req.method === 'GET') {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(`
      <form method="POST" enctype="multipart/form-data">
        <input type="file" name="file" /><br/>
        <button type="submit">上传</button>
      </form>
    `);
  } else if (req.method === 'POST') {
    let data = '';
    req.setEncoding('binary');

    req.on('data', chunk => {
      data += chunk;
    });

    req.on('end', () => {
      // 获取 boundary 分隔符
      const boundary = '--' + req.headers['content-type'].split('boundary=')[1];
      const parts = data.split(boundary);
      const filePart = parts[1];

      // 提取文件名
      const match = filePart.match(/filename="(.+)"/);
      const filename = match && match[1] ? match[1] : 'upload.bin';
      const start = filePart.indexOf('\r\n\r\n') + 4;
      const end = filePart.lastIndexOf('\r\n');
      const fileData = filePart.substring(start, end);

      fs.writeFile(path.join(__dirname, filename), fileData, 'binary', err => {
        if (err) {
          res.writeHead(500);
          res.end('上传失败');
        } else {
          res.writeHead(200);
          res.end('上传成功,文件名:' + filename);
        }
      });
    });
  }
}).listen(3000, () => {
  console.log('Server running at http://localhost:3000');
});


🧰 2、常用 Node.js 文件上传模块

✅ 推荐使用 multer

npm install multer

📤 3、Express + Multer 实现文件上传

1. 创建 upload.js

const express = require('express');
const multer = require('multer');
const path = require('path');

const app = express();

// 配置存储位置和文件名
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, './uploads'); // 上传目录
  },
  filename: function (req, file, cb) {
    const ext = path.extname(file.originalname);
    cb(null, Date.now() + ext); // 文件名
  }
});

const upload = multer({ storage });

app.post('/upload', upload.single('file'), (req, res) => {
  // req.file 就是上传的文件信息
  res.json({
    message: '上传成功',
    filename: req.file.filename
  });
});

app.listen(3000, () => console.log('服务已启动,端口3000'));

🖥 4、前端上传方式

1. 表单方式

<form action="http://localhost:3000/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="file" />
  <button type="submit">上传</button>
</form>

2. Axios 上传文件

const formData = new FormData();
formData.append('file', fileInput.files[0]);

axios.post('http://localhost:3000/upload', formData, {
  headers: {
    'Content-Type': 'multipart/form-data'
  }
})
.then(res => console.log('上传成功', res.data))
.catch(err => console.error('上传失败', err));

🧠 5、上传细节与注意事项

细节 描述
文件大小限制 limits: { fileSize: 5 * 1024 * 1024 }
多文件上传 upload.array('files', maxCount)
文件类型校验 fileFilter: (req, file, cb) => {...}
跨域 后端需设置 Access-Control-Allow-Origin
文件夹不存在处理 可用 fs.mkdirSync('uploads', { recursive: true }) 创建

🛠 6、常见问题排查

问题 原因
req.file 为 undefined 表单名错误或未使用 multipart/form-data
文件未保存 destination 路径不存在或无权限
文件名乱码 可以使用 Buffer.from(filename, 'binary').toString('utf8') 转码
大文件上传失败 默认大小限制过小,可通过 limits 配置放宽
❌