Web服务器开发、文件上传

1 Stream的读写操作

2 http模块web服务

3 request请求对象

4 response响应对象

5 axios node中使用

6 文件上传的细节分析

前面一篇提到的内容是node对底层的文本操作,还没有涉及到从文本文件获取信息然后传递给客户端和如何响应客户端请求。这里开始了解如何实现Web服务器开发。

stream就是流的意思

一、Stream的读写操作

可读流的基本使用:

const fs = require('fs')

// 1.一次性读取
// 缺点一: 没有办法精准控制从哪里读取, 读取什么位置.
// 缺点二: 读取到某一个位置的, 暂停读取, 恢复读取.
// 缺点三: 文件非常大的时候, 多次读取.
// fs.readFile('./aaa.txt', (err, data) => {
//   console.log(data)
// })

// 2.通过流读取文件
// 2.1. 创建一个可读流
// start: 从什么位置开始读取
// end: 读取到什么位置后结束(包括end位置字节)
const readStream = fs.createReadStream('./aaa.txt', {
// 从第八个字符开始读取
 start: 8,
// 读取到第22个字符
 end: 22,
// 分三次读取
 highWaterMark: 3
})

// 通过event的事件监听来获取流操作的过程
// 这个data事件是来自readStream的
readStream.on('data', (data) => {
  console.log(data.toString())
// 暂停
  readStream.pause()

  setTimeout(() => {
// 解除暂停
    readStream.resume()
  }, 2000)
})


可读流的其他事件:

const fs = require('fs')

// 1.通过流读取文件
const readStream = fs.createReadStream('./aaa.txt', {
// 从第八个字符开始读取
 start: 8,
// 读取到第22个字符
 end: 22,
// 分三次读取
 highWaterMark: 3
})


// 2.监听读取到的数据
readStream.on('data', (data) => {
  console.log(data.toString())
})

// 3.补充其他的事件监听,这里可以获取文件描述符,具体可以去看前一篇的内容
readStream.on('open', (fd) => {
  console.log('通过流将文件打开~', fd)
})

readStream.on('end', () => {
  console.log('已经读取到end位置')
})

readStream.on('close', () => {
  console.log('文件读取结束, 并且被关闭')
})

可写流的使用过程:

写入过程是可以一直写入的,但是关闭需要手动写关闭的代码,不像读操作有自动关闭的功能。

const fs = require('fs')

// 1.一次性写入内容
// fs.writeFile('./bbb.txt', 'hello world', {
//   encoding: 'utf-8',
//   flag: 'a+'
// }, (err) => {
//   console.log('写入文件结果:', err)
// })

// 2.创建一个写入流
const writeStream = fs.createWriteStream('./ccc.txt', {
  flags: 'a'
})

writeStream.on('open', (fd) => {
  console.log('文件被打开', fd)
})

writeStream.write('coderwhy')
writeStream.write('aaaa')
writeStream.write('bbbb', (err) => {
  console.log("写入完成:", err)
})

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

writeStream.on('close', () => {
  console.log('文件被关闭~')
})

// 3.写入完成时, 需要手动去掉用close方法
// writeStream.close()

// 4.end方法: 
// 操作一: 将最后的内容写入到文件中, 并且关闭文件
// 操作二: 关闭文件
writeStream.end('哈哈哈哈')

文件的拷贝流操作:

const fs = require('fs')

// 1.方式一: 一次性读取和写入文件
// fs.readFile('./foo.txt', (err, data) => {
//   console.log(data)
//   fs.writeFile('./foo_copy01.txt', data, (err) => {
//     console.log('写入文件完成', err)
//   })
// })


// 2.方式二: 创建可读流和可写流
// const readStream = fs.createReadStream('./foo.txt')
// const writeStream = fs.createWriteStream('./foo_copy02.txt')

// readStream.on('data', (data) => {
//   writeStream.write(data)
// })

// readStream.on('end', () => [
//   writeStream.close()
// ])

// 3.在可读流和可写流之间建立一个管道
const readStream = fs.createReadStream('./foo.txt')
const writeStream = fs.createWriteStream('./foo_copy03.txt')

readStream.pipe(writeStream)

可写流的start属性:

const fs = require('fs')

const writeStream = fs.createWriteStream('./ddd.txt', {
  // mac上面是没有问题
  // flags: 'a+',
  // window上面是需要使用r+
  flags: 'r+',
  start: 5
})

writeStream.write('my name is why')
writeStream.close()

二、HTTP模块:

应用http模块或者基于http模块的框架express和koa来制作属于我们的服务器。和java和go做后端差不多,使用node制作服务器是前端比较容易实现的。

http模块是基础和底层的,所以我们会比较多的使用框架express和koa

所以现在说的http模块只是学习使用,具体就去用express和koa框架就好了。

端口主机host参数的知识:当你设置127.0.0.0和localhost的时候,如果是别的电脑需要访问会访问不到。所以,输入0.0.0.0默认是最好的,其他电脑是可以访问的到的。vue的跨域那部分最好也是用0.0.0.0来做代理。

工具一:浏览器访问服务器的时候会发送两次请求,服务器会回复两次(一次是访问内容,另外一次是为了拿到icon),并且浏览器测试不了post的接口,get接口能测试。基于这个原因,我们一般不直接用浏览器来测试我们的http服务器,。转而使用postman就不会出现访问两次的情况。

使用postman的方法:

在collections里面创建一个(举例取名为NodeServer),然后创建文件夹,文件夹里面放request,最后在send的前面一个输入框输入你开启的服务器的ip地址和端口号。

工具二:由于使用node来打开服务器时是不会因为你更新了服务器的代码然后重新刷新开启更新后的服务器的,为了实现上面的能够在服务器代码更新的时候重新开启服务器的功能,我们下载一个包:

npm install nodemon -g

之后想要打开服务器就输入代码来打开,不再使用node xxx.js

nodemon xxx.js

http服务器的基本使用

当运行这个js文件时,是不会自动结束这次运行的。因为服务器就是一直开着的,等待客户端向服务器发送消息。

下面这个代码使用之后,你可以去浏览器输入localhost:8000来访问服务器了, 

const http = require('http')


// 创建一个http对应的服务器
const server = http.createServer((request, response) => {
  // request对象中包含本次客户端请求的所有信息
  // 请求的url
  // 请求的method
  // 请求的headers
  // 请求携带的数据

  // response对象用于给客户端返回结果的
  response.end("Hello World")
})

// 开启对应的服务器, 并且告知需要监听的端口
// 监听端口时, 监听1024以上的端口, 666535以下的端口
// 1025~65535之间的端口
// 2个字节 => 256*256 => 65536 => 0~65535
server.listen(8000, () => {
  console.log('服务器已经开启成功了~')
})

创建多个http的服务器

const http = require('http')

// 1.创建一个服务器
const server1 = http.createServer((req, res) => {
  res.end("2000端口服务器返回的结果~")
})
server1.listen(2000, () => {
  console.log('2000端口对应的服务器启动成功~')
})

// 2.创建第二个服务器
const server2 = http.createServer((req, res) => {
  res.end("3000端口服务器返回的结果~")
})
server2.listen(3000, () => {
  console.log('3000端口对应的服务器启动成功~')
})


// 3.创建第三个服务器
// const server3 = new http.Server()

额外小知识点的补充

工具1、主要是使用postman来测试服务器

工具2、由于使用node来打开服务器时是不会因为你更新了服务器的代码然后重新刷新开启更新后的服务器的,为了实现上面的能够在服务器代码更新的时候重新开启服务器的功能,我们下载一个包:

npm install nodemon -g
const http = require('http')

// 1.创建server服务器
const server = http.createServer((req, res) => {
  console.log('服务器被访问~')

  res.end('hello world aaaa')
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

http服务器-request对象

const http = require('http')

// 1.创建server服务器
const server = http.createServer((req, res) => {
  // request对象中包含哪些信息?
  // 1.url信息
  console.log(req.url)
  // 2.method信息(请求方式)
  console.log(req.method)
  // 3.headers信息(请求信息)
  console.log(req.headers)

  res.end('hello world aaaa')
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

从postman这里的url和请求方式、请求头信息。可以知道代码中的req.url的值是/home

req.method的值是POST    reqheaders的值为下图显示的。

http服务器-区分不同url

这里是通过不同url对客户端进行回复信息,比如res.end()在这里就是回复客户端信息。

const http = require('http')

// 1.创建server服务器
const server = http.createServer((req, res) => {
  const url = req.url

  if (url === '/login') {
    res.end('登录成功~')
  } else if (url === '/products') {
    res.end('商品列表~')
  } else if (url === '/lyric') {
    res.end('天空好想下雨, 我好想住你隔壁!')
  }
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

http服务器-区分不同method

这里是对客户端进行限制使用何种请求方式,比如是get还是post的请求方式。

const http = require('http')

// 1.创建server服务器
const server = http.createServer((req, res) => {
  const url = req.url
  const method = req.method

  if (url === '/login') {
    if (method === 'POST') {
      res.end('登录成功~')
    } else {
      res.end('不支持的请求方式, 请检测你的请求方式~')
    }
  } else if (url === '/products') {
    res.end('商品列表~')
  } else if (url === '/lyric') {
    res.end('天空好想下雨, 我好想住你隔壁!')
  }
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

request参数解析-query参数

用户一般会发送数据,比如?后面的一串就是发送的参数。

 这里引入了url和querystring来帮助我们快速解析query。先解析url再解析query。

const http = require('http')
const url = require('url')
const qs = require('querystring')

// 1.创建server服务器
const server = http.createServer((req, res) => {
  // 1.参数一: query类型参数
  // /home/list?offset=100&size=20
  // 1.1.解析url
  const urlString = req.url
  const urlInfo = url.parse(urlString)

  // 1.2.解析query: offset=100&size=20
  const queryString = urlInfo.query
  const queryInfo = qs.parse(queryString)
  console.log(queryInfo.offset, queryInfo.size)

  res.end('hello world aaaa bbb')
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

request参数解析-body参数

这里模拟客户机向服务器发送请求的消息体的内容为一个对象类型,里面有name和password的信息。

const http = require('http')
const url = require('url')
const qs = require('querystring')

// 1.创建server服务器
const server = http.createServer((req, res) => {
  // 获取参数: body参数
  //这里需要设置编码方式才能把buffer的二进制转成真正的消息体内容
  req.setEncoding('utf-8')

  // request对象本质是上一个readable可读流,所以可以通过on拿到数据
  // 这里读取的消息体的内容在data事件里面
  let isLogin = false
  req.on('data', (data) => {
    const dataString = data
  // 把字符串形式转成对象形式
    const loginInfo = JSON.parse(dataString)
    if (loginInfo.name === 'coderwhy' && loginInfo.password === '123456') {
      isLogin = true
    } else {
      isLogin = false
    }
  })

  req.on('end', () => {
    if (isLogin) {
      res.end('登录成功, 欢迎回来~')
    } else {
      res.end('账号或者密码错误, 请检测登录信息~')
    }
  })
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

request参数解析-headers参数

这里的headers['content-type']可以告诉服务器客户端发送给服务器的消息体是什么类型的,比如:消息体如果是对象类型的,那么值就是

;消息体如果是字符串类型的,那么值就是

同样的,postman客户端怎么发送token?

const http = require('http')
const url = require('url')
const qs = require('querystring')

// 1.创建server服务器
const server = http.createServer((req, res) => {
  console.log(req.headers)
  console.log(req.headers['content-type'])

  // cookie/session/token
  const token = req.headers['authorization']
  console.log(token)

  res.end('查看header的信息~')
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

response响应对象-响应方式

主要是用end,最好不要用close()来关闭。

const http = require('http')

// 1.创建server服务器
const server = http.createServer((req, res) => {
  // res: response对象 => Writable可写流
  // 1.响应数据方式一: write
  res.write("Hello World")
  res.write("哈哈哈哈")

  // // 2.响应数据方式二: end
  res.end("本次写出已经结束")
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

response响应对象-响应状态码

const http = require('http')

// 1.创建server服务器
const server = http.createServer((req, res) => {  
  // 响应状态码
  // 1.方式一: statusCode
  // res.statusCode = 403

  // 2.方式二: setHead 响应头
  res.writeHead(401)

  res.end('hello world aaaa')
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

response响应对象-响应header

这里主要是告诉浏览器以什么编码方式进行解析服务器传递回来的信息。

const http = require('http')

// 1.创建server服务器
const server = http.createServer((req, res) => {

  // 设置header信息: 数据的类型以及数据的编码格式
  // 1.单独设置某一个header
  // res.setHeader('Content-Type', 'text/plain;charset=utf8;')

  // 2.和http status code一起设置
  res.writeHead(200, {
    'Content-Type': 'application/json;charset=utf8;'
  })

  const list = [
    { name: "why", age: 18 },
    { name: "kobe", age: 30 },
  ]
  res.end(JSON.stringify(list))
})


// 2.开启server服务器
server.listen(8000, () => {
  console.log('服务器开启成功~')
})

三、axios node中使用

想要在node里面使用axios就要先下载axios,具体步骤如下:

1、生成package.json文件 npm init

2、下载axios  npm install axios

3、开启一个服务器

在node中发送请求-axios

const axios = require('axios')

axios.get('http://localhost:8000').then(res => {
  console.log(res.data)
})

在node中发送请求-http

const http = require('http')

// 1.使用http模块发送get请求、没有提供post方法
// http.get('http://localhost:8000', (res) => {
//   // 从可读流中获取数据
//   res.on('data', (data) => {
//     const dataString = data.toString()
//     const dataInfo = JSON.parse(dataString)
//     console.log(dataInfo)
//   })
// })

// 2.使用http模块发送post请求
const req = http.request({
  method: 'POST',
  hostname: 'localhost',
  port: 8000
}, (res) => {
  res.on('data', (data) => {
    const dataString = data.toString()
    const dataInfo = JSON.parse(dataString)
    console.log(dataInfo)
  })
})

// 必须调用end, 表示写入内容完成
req.end()

文件上传-错误的做法

一般来说文件上传都是借助其他软件的,一般不是自己手动处理。

文件上传必须是post请求,不可以是get请求。

+ 下面这个错误的上传和存储文件的方法,虽然可以把客户端上传的文件传递到服务器,服务器获取信息并写入到png文件里面,但是,其实根本打不开个png文件,这是因为服务器读取的这个文件内容在写入png的时候还把其他东西写进去了,看图软件打不开这个png文件。

这种写法单上传一个图片都显示不了更别说用户上传的是有其他内容的加上图片的内容。

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

// 1.创建server服务器
const server = http.createServer((req, res) => {
  // 创建writable的stream
  const writeStream = fs.createWriteStream('./foo.png', {
    flags: 'a+'
  })

  // req.pipe(writeStream)

  // 客户端传递的数据是表单数据(请求体)
  req.on("data", (data) => {
    console.log(data);
    writeStream.write(data)
  });

  req.on("end", () => {
    // console.log("数据传输完成~");
    // writeStream.close()
    res.end("文件上传成功~");
  });
});

// 2.开启server服务器
server.listen(8000, () => {
  console.log("服务器开启成功~");
});

文件上传-正确的做法

通过vscode的debug来打断点查看上传的内容是什么东西

手动处理图片上传太麻烦,后续直接用框架来实现比较方便。

 

 

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

// 1.创建server服务器
const server = http.createServer((req, res) => {
  req.setEncoding('binary')

  const boundary = req.headers['content-type'].split('; ')[1].replace('boundary=', '')
  console.log(boundary)

  // 客户端传递的数据是表单数据(请求体)
  let formData = ''
  req.on("data", (data) => {
    formData += data
  });

  req.on("end", () => {
    console.log(formData)
    // 1.截图从image/jpeg位置开始后面所有的数据
    const imgType = 'image/jpeg'
    const imageTypePosition = formData.indexOf(imgType) + imgType.length
    let imageData = formData.substring(imageTypePosition)

    // 2.imageData开始位置会有两个空格
    imageData = imageData.replace(/^\s\s*/, '')

    // 3.替换最后的boundary
    imageData = imageData.substring(0, imageData.indexOf(`--${boundary}--`))

    // 4.将imageData的数据存储到文件中
    fs.writeFile('./bar.png', imageData, 'binary', () => {
      console.log('文件存储成功')
      res.end("文件上传成功~");
    })
  });
});

// 2.开启server服务器
server.listen(8000, () => {
  console.log("服务器开启成功~");
});

文件上传-浏览器代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <input type="file">
  <button>上传</button>

  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    // 文件上传的逻辑
    const btnEl = document.querySelector('button')
    btnEl.onclick = function() {
      // 1.创建表单对象
      const formData = new FormData()

      // 2.将选中的图标文件放入表单
      const inputEl = document.querySelector('input')
      formData.set('photo', inputEl.files[0])

      // 3.发送post请求, 将表单数据携带到服务器(axios)
      axios({
        method: 'post',
        url: 'http://localhost:8000',
        data: formData,
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      })
    }
  </script>

</body>
</html>

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值