前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >http网络编程(node版)

http网络编程(node版)

作者头像
一粒小麦
发布2019-07-18 18:57:08
1.2K0
发布2019-07-18 18:57:08
举报
文章被收录于专栏:一Li小麦一Li小麦

基础的东西总是很少人看,写起来也特别痛苦。不是因为它简单——恰恰是因为它太晦涩,太基础了——以至于没有人乐意用它来充实自己(装逼)。

本文涉及一下内容:

  • http协议基础
  • 常见的http请求及其报文解读
  • 通过image对象埋点请求方案(天然解决跨域问题)
  • 预检请求及其实践
  • 跨域解决方案:设置响应头,反向代理(终极解决)
  • express版本的bodyparser的使用场景及局限
  • multer实现文件上传储存

http协议基础

先补白吧。

所谓网络编程,指的是应用层和传输层。

层级

内容

应用层

<应用层>TELNET,SSH,HTTP,SMTP,POP,SSL/LTS,FTP,MINME,HTML,SNMP,MIB,SIP,RTP

表示层

会话

传输层

<传输层>TCP,UDP,UDP-lite,SCTP,DCCP

网络层

<网络层>ipv4,ipv6,ARP,ICMP,IPsec

数据链路层

以太网,wlan ,ppp

物理层

http全称超文本传输协议(HyperText Transfer Protocol),是当今互联网使用最为广泛的传输协议。

当前主流的版本仍然是http1.1。

常用http状态码

状态码

描述

100

继续相应剩余部分

200

成功处理请求

301

资源永久移动

302

资源临时移动

304

未修改,响应中不包含资源内容

401

未授权,要求身份验证

403

禁止,请求被拒绝

404

资源不存在

500

服务器内部错误

503

服务不可用

常用的请求方法

koa中推荐用户使用REST规范,比如下面四种请求对应了增删改查:

方法

接口地址

描述

post

http://api.test.com/users

增加用户

delete

http://api.test.com/users/:id

删除用户

put

http://api.test.com/users/:id

修改用户

get

http://api.test.com/users/:id

查询用户

http首部字段

执行以下命令行:

代码语言:javascript
复制
curl -v http://www.baidu.com

即可打印出请求到的页面代码。

首部反映的是http传输过程中的重要信息:

字段名

描述

User-Agent

http客户端的信息

Last-Midified

资源最后修改日期

Contnet-Length

实体主体大小,单位为字节

Contnet-Encoding

实体主体适用的编码方式

Content-Type

实体主体的媒体类型,如img/png,application/x-javascript,text/html

Expires

实体主体的过期时间

Set-Cookie

开始状态管理所使用的cookie信息。

Cookie

服务器接收到的cookie

Cache-Control

控制缓存的行为:如public/private/no-cache

ETag

资源匹配信息

Vary

代理服务器的缓存信息

Server

http服务器的缓存信息

http实践

接口请求

写一个api服务器,规定路由和接口:

代码语言:javascript
复制
// api.js
const http=require('http');
const fs=require('fs');

http.createServer((req,res)=>{
    const {method,url}=req;
    if(method=='GET'&&url=='/'){
        fs.readFile('./index.html',(err,data)=>{
            res.setHeader('Contnent-Type','text/html');
            res.end(data);
        })
    }else if(method=='GET'&&url=='/api/users'){
        res.setHeader('Contnet-Type','application/json');
        res.end(JSON.stringify([{
            name:'djtao'
        }]));

    }
}).listen(3000)

对应的静态html如下:

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="root">

    </div>
     <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script><script src=""></script>
    <script>
        (async ()=>{
            const res=await axios.get('api/users');
            console.log(JSON.stringify(res.data))
            document.querySelector('#root').innerHTML=`Response:${JSON.stringify(res.data)}`
        })()
    </script>
    </body>
</html>

那么一个带请求的页面就实现了。

埋点

最简单的请求,无需使用axios库:

代码语言:javascript
复制
var img=new Image();
img.src='/api?name=123';

这种请求通常用于百度统计。

跨域

浏览器的同源策略:以下三项有任意一个不等,就会引起跨域问题。

  • 协议
  • 端口
  • 域名

现在制造一种不同源的情况,考虑搭建两台服务器:

修改api.js

代码语言:javascript
复制
const http=require('http');
const fs=require('fs');
const app= http.createServer((req,res)=>{
    const {method,url}=req;
    console.log(method,url)
    if(method=='GET'&&url=='/'){
        fs.readFile('./index.html',(err,data)=>{
            res.setHeader('Contnent-Type','text/html');
            res.end(data);
        })
    }else if(method=='GET'&&url=='/api/users'){
        res.setHeader('Contnet-Type','application/json');
        res.end(JSON.stringify([{
            name:'djtao'
        }]));

    }
});
module.exports=app;

新建一个proxy.js,为方便处理,此处调用了express:

代码语言:javascript
复制
const express=require('express');
const app=express();

app.use(express.static(__dirname+'/'));
module.exports=app;

Html内的信息改为:

代码语言:javascript
复制
(async ()=>{
                      axios.defaults.baseURL='http://localhost:4000';
            const res=await axios.get('/api/users');
            console.log(JSON.stringify(res.data))
            document.querySelector('#root').innerHTML=`Response:${JSON.stringify(res.data)}`
        })()

这时访问localhost:4000就触发了协议相同,端口不同的跨域错误。

此时network是200,但仍然被浏览器阻拦。

出于安全考虑,浏览器会限制从脚本发起的跨域HTTP请求,像XMLHttpRequest和Fetch都遵循同源策略。 浏览器限制跨域请求一般有两种方式:

  1. 浏览器限制发起跨域请求
  2. 跨域请求可以正常发起,但是返回的结果被浏览器拦截了

怎么解决呢?

后端设置报头

可以在后端设置请求例外(在这里是http://localhost:3000):

代码语言:javascript
复制
res.setHeader('Access-Control-Allow-Origin','http://localhost:3000');
        res.setHeader('Contnet-Type','application/json');
        res.end(JSON.stringify([{
            name:'djtao'
        }]));
预检请求(preflight)

作为前端,我想在header中带上token:

代码语言:javascript
复制
const res=await axios.get('/api/users',{
                headers:{'X-Token':'token'}
            });

那么请求又没有结果了。(请求无应答)

如果打印出来。会发现req.method是OPTIONS.

一般的跨域都是浏览器拦截,那就是说请求已到达服务器,并有可能对数据库里的数据进行了操作,但是返回的结果被浏览器拦截了,那么我们就获取不到返回结果,这是一次失败的请求,但是可能对数据库里的数据产生了影响。

为了防止这种情况的发生,规范要求,对这种可能对服务器数据产生副作用的HTTP请求方法,浏览器必须先使用 OPTIONS方法发起一个预检请求,从而获知服务器是否允许该跨域请求:如果允许,就发送带数据的真实请求;如果不允许,则阻止发送带数据的真实请求。

哪些情况需要预检

首先需要明确,简单请求 不会触发CORS预检请求,“简属于单请求”术语并不属于Fetch(其中定义了CORS)规范。若满足所有下述条件,则该请求可视为“简单请求”:get,head,post。

使用了下面任一 HTTP 方法,都会触发预检:

  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE
  • PATCH

或者人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。

我们自定义了一个 X-Token,触发了预检请求,所以需要特殊判断:

代码语言:javascript
复制
else if(method=='OPTION'&&url=='/api/users'){
  res.writeHead(200,{
    'Access-Control-Allow-Origin':'http://localhost:3000',
    'Access-Control-Allow-Headers':'X-Token,Contnet-Type',
    'Access-Control-Allow-Methods':'PUT'
  });
  res.end();
}

请求头也带上了:

鉴权(携带cookie信息)
代码语言:javascript
复制
// 预检options中和/users接口中均需添加 
res.setHeader('Access-Control-Allow-Credentials', 'true'); 
// get请求中设置cookie
res.setHeader('Set-Cookie', 'cookie1=va222;')
// 观察cookie存在 
console.log('cookie',req.headers.cookie) 
// ajax服务
axios.defaults.withCredentials = true

第二次请求中cookie就打印出来了。

Proxy代理模式

使用代理中间件:http-proxy-middleware

简单说就是把4000的端口反向代理到3000:

代码语言:javascript
复制
// proxy.js
const express=require('express');
const proxy=require('http-proxy-middleware');

const app=express();
app.use(express.static(__dirname+'/'));
app.use('/api',proxy({
  target:'http://localhost:4000',
  changeOrigin:false
}))
module.exports=app;

把axios的请求改为3000端口,就完事了!

bodyparser

现在研究下post,改写index.html,注释掉ajax请求:

代码语言:javascript
复制
<form action="/api/save" method="post">
            <input type="text" name="name">
            <input type="submit" value="save">
        </form>

然后配置api.js:

代码语言:javascript
复制
else if(url=='/api/save'&&method=='POST'){
        let reqData=[];
        let size=0;

        req.on('data',data=>{
            console.log('>>>req on ',data);
            // 接收buffer
            reqData.push(data);
            size+=data.length
        });

        req.on('end',()=>{
            console.log('end');
            const data=Buffer.concat(reqData,size);
            console.log('data',size,data.toString());
            res.end('formdata:'+data.toString())
        })
    }

执行结果为:

把很多个buffer字节连接起来。这样就拿到了post请求数据。

这实在是太麻烦了。还记得bodyparser吗?装一个epress版的呗

新建api2.js

代码语言:javascript
复制
const http = require('http');
const fs = require('fs');
const bodyParser = require('body-parser');
const express=require('express');

const app=express();

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())

app.post('/api/save',(req,res)=>{
    console.log(req.body.name);
    res.end(req.body.name)
})


module.exports = app;

好了,req.body就可以拿到请求。

上传文件

主要指的是上传文件。

改写上传文件的逻辑:

代码语言:javascript
复制
<input type="file" name="file" id="upload">
<script>
        const file = document.querySelector('#upload');
        file.addEventListener('change', function (e) {
            let _files = this.files;
            const formData=new FormData();
            if (!_files.length) {
                return;
            }
            if (_files.length == 1) {
                formData.append('file',_files[0])
                // 只考虑选择单个文件
                let xhr = new XMLHttpRequest();             
                xhr.open('POST', 'http://localhost:3000/api/upload');
                xhr.send(formData);
            }else{

            }
        })
</script>

后端处理bodyparser只能处理简单的请求,如果上传文件,需要装multer

代码语言:javascript
复制
var path=require('path')
var multer  = require('multer')
var upload = multer()

app.post('/api/upload',upload.single('file'),async (req,res)=>{
    let file=req.file
    console.log(file)
      // 以原文件名写入!
    await fs.writeFile(path.resolve(__dirname,file.originalname), file.buffer, err => {    
        console.log('写入成功');
    })
    res.end('1')
})

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • http协议基础
    • 常用http状态码
      • 常用的请求方法
        • http首部字段
        • http实践
          • 接口请求
            • 埋点
              • 跨域
                • 后端设置报头
                • 预检请求(preflight)
                • 鉴权(携带cookie信息)
                • Proxy代理模式
              • bodyparser
                • 上传文件
                相关产品与服务
                消息队列 TDMQ
                消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档