简介

  1. node http接口
  2. 实现即时通信
  3. 网络爬虫

1. 实现 Http 接口

  1. 实现一个简单的 http 请求
  2. http 请求有跨域
  3. 预检请求 options,设置响应头
  4. credentail 请求,携带 cookie 信息

    实现简单 http

    ``javascript // index.html // 使用 axios 请求 /api/ const res = await axios.get("/api/user") document.writeln(Response: ${JSON.stringify(res.data)}`)

// index.js const http = require(“http”); const fs = require(“fs”); http.createServer((req, res) => { const { method, url } = req; if (method == “GET” && url == “/api/user”) { res.setHeader(“Content-Type”, “application/json”); res.end(JSON.stringify({ name: “qiji”, age: 25 })); } }).listen(3000);

  1. <a name="lGeNX"></a>
  2. #### http 跨域
  3. 将上面的代码更改一下,这里面让页面和服务地址使用不同的地址(页面:localhost:3000,服务:localhost:4000),产生跨域。
  4. ```javascript
  5. // index.html
  6. (async () => {
  7. axios.defaults.baseURL = 'http://localhost:4000'
  8. const res = await axios.get("/api/user");
  9. document.writeln(`Response : ${JSON.stringify(res.data)}`)
  10. })()
  11. //新增一个 proxy.js,启动一个 express 作为服务端地址
  12. const express = require('express')
  13. const app = express()
  14. // __dirname 代码所在的目录
  15. app.use(express.static(__dirname + '/'))
  16. app.listen(3000)
  17. // index.js
  18. // 端口更改为 4000
  19. .listen(4000);

结果如图:
image.png

跨域的解决方法

  1. JSONP:使用 script 标签加载 js 资源不受同源策略限制(现在没人用了吧……)。
  2. 设置 CORS

    1. // 属于符合 w3c 的真正意义解决跨域问题
    2. res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000")
  3. 代理 ```javascript // index.js / api.js 都不需要设置任何的跨域设置 // api.js .listen(4000)

// index.html (async () => { axios.defaults.baseURL = ‘http://localhost:3000‘ const res = await axios.get(“/api/user”) document.writeln(Response : ${JSON.stringify(res.data)}) })()

// proxy.js // node 中可以使用很多种代理服务器,这里选择一种 const proxy = require(‘http-proxy-middleware’)

app.use(‘/api’, proxy({ target: ‘http://localhost:4000‘, changeOrigin: false }));

  1. <a name="0kLRb"></a>
  2. #### 预检请求
  3. 预检不成功的请求无法正常发出,不论跨域与否。
  4. ```javascript
  5. // index.html
  6. // const res = await axios.get("/api/user");
  7. const res = await axios.post("/api/user", {
  8. headers: {
  9. 'X-Token': 'luelue'
  10. }
  11. })
  12. // api.js
  13. // 设置预检 preflight
  14. else if (method == 'OPTIONS' && url == "/api/user") {
  15. res.writeHead(200, {
  16. 'Access-Control-Allow-Origin': "http://localhost:3000",
  17. 'Access-Control-Allow-Headers': "X-Token,Content-Type",
  18. "Access-Control-Allow-Methods": "PUT"
  19. })
  20. res.end()
  21. }

设置 cookie

一般来说,ajax 正常是不写 cookie 的。

  1. // 关键代码
  2. res.setHeader("Access-Control-Allow-Credentials", 'true')
  3. res.setHeader("Set-Cookie", 'cookie=test')

2. 实现一个即时通信(Socket、Http、Socketio)

即时通信(scoket、http、socketio)代码 demo

socket 实现

《node深入浅出》这本书中的首例,很好的演示了 node 的作用

原理:Net模块提供一个异步API能够创建基于流的TCP服务器,客户端与服务器建立连接后,服务器可以获得一个全双工Socket对象,服务器可以保存Socket对象列表,在接收某客户端消息时,推送给其他客户端。
延伸:TCP/IP 协议,UDP 协议

  1. const net = require('net')
  2. const chatServer = net.createServer()
  3. const clientList = []
  4. // 建立链接后 监听所有访问 9000 的用户,然后,分发他们输入的消息
  5. chatServer.on("connection", client => {
  6. client.write("Hi/n")
  7. clientList.push(client)
  8. client.on('data', data => {
  9. console.log('receive:', data.toString());
  10. clientList.forEach(v => {
  11. v.write(data)
  12. })
  13. })
  14. })
  15. chatServer.listen(9000)

如果其他用户想链接这个 socket 端口,只需要 telnet localhost 9000

http 实现

在没有 websocket 的协议时,客户端只能用 http 长轮询的方式获取服务端信息,其实 http 是基于 TCP 协议,如果只是作为通信,携带了大量的无关信息显得冗余,很多时候聊天软件都是基于 UDP 协议,数据量小。
简单来说 http 的方式就是一个一个的请求发过去,返回过来新的数据。

客户端代码:

  1. // vue
  2. <div id="app">
  3. <input v-model="message" /> <button v-on:click="send">发送</button>
  4. <button v-on:click="clear">清空</button>
  5. <div v-for="item in list">{{item}}</div>
  6. </div>
  1. // 设置端口
  2. const host = "http://localhost:3000";
  3. var app = new Vue({
  4. el: "#app",
  5. data: {
  6. list: [],
  7. message: "Hello Vue!"
  8. },
  9. methods: {
  10. // 操作都是一个一个的 requset 请求
  11. send: async function() {
  12. let res = await axios.post(host + "/send", {
  13. message: this.message
  14. });
  15. this.list = res.data;
  16. },
  17. clear: async function() {
  18. let res = await axios.post(host + "/clear");
  19. this.list = res.data;
  20. }
  21. },
  22. mounted: function() {
  23. // 因为 http 天然不能给客户端推数据,这也是 http 的不足,所以需要定时获取。
  24. setInterval(async () => {
  25. const res = await axios.get(host + "/list");
  26. this.list = res.data;
  27. }, 1000);
  28. }
  29. });

服务端代码:

  1. const express = require('express')
  2. const app = express()
  3. const bodyParser = require('body-parser'); // bodyparser 中间件
  4. const path = require('path')
  5. app.use(bodyParser.json());
  6. const list = ['历史记录 1', '历史记录 2']
  7. app.get('/', (req, res) => {
  8. // 绝对路径,使用 path 将相对路径转换为绝对路径
  9. res.sendFile(path.resolve('./index.html'))
  10. })
  11. app.get('/list', (req, res) => {
  12. res.end(JSON.stringify(list))
  13. })
  14. app.post('/send', (req, res) => {
  15. list.push(req.body.message)
  16. res.end(JSON.stringify(list))
  17. })
  18. app.post('/clear', (req, res) => {
  19. list.length = 0
  20. res.end(JSON.stringify(list))
  21. })
  22. app.listen(3000);

最后运行结果如下图

image.png

socketio 实现

说一下 socketio 的优势:

  • html5 标准
  • 支持优雅降级

跟 websocket 相比,减少了兼容问题(其实还是优雅降级,因为 websocket 就是 html5 标准)。
主要的功能都是 socketio 实现的,例子很好写,看一下 socketio 的官网就能弄明白。所以代码就不贴了,地址在上面

image.png

3. 实现一个爬虫

很简单的一个爬虫,就是发送 request 请求获取对应网页,然后解析其中的某一个节点的值。这里面获取的是澎湃新闻的文章标题。

  1. // request 库 也是封装的 http
  2. const originRequest = require("request");
  3. // 服务器端的 jquery
  4. const cheerio = require("cheerio");
  5. // 负责转码
  6. const iconv = require("iconv-lite");
  7. function request(url, callback) {
  8. const options = {
  9. url: url,
  10. encoding: null
  11. };
  12. originRequest(url, options, callback);
  13. }
  14. for (let i = 5227428; i < 5227430; i++) {
  15. // 随便爬取一个澎湃新闻的例子
  16. const url = `https://www.thepaper.cn/newsDetail_forward_${i}`;
  17. request(url, function (err, res, body) {
  18. // 爬虫网页对应的编码使用 document.chartset 就能获取到
  19. const html = iconv.decode(body, "UTF-8");
  20. const $ = cheerio.load(html);
  21. console.log($(".news_title").text());
  22. });
  23. }