Vitamin

Vitamin 查看完整档案

北京编辑北华大学  |  软件工程 编辑  |  填写所在公司/组织 blog.dailycoding.wang 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

Vitamin 回答了问题 · 2020-01-14

idea改成eclipse编译报错

这里的问题是你的IDEA内置的ECJ版本太高了
企业微信截图_20200114101647.png
把图中红框部分的内容替换一个合适的版本jar包就行了。这个jar除了可以单独下载,在tomcat / lib 目录下也存在

关注 2 回答 1

Vitamin 关注了用户 · 2019-05-20

滴滴技术 @didijishu

滴滴出行技术官方号

关注 1847

Vitamin 关注了用户 · 2019-05-20

马蜂窝技术 @mafengwojishu

欢迎关注马蜂窝技术,我们将定期分享马蜂窝技术团队先进的技术实践,期待您与马蜂窝一起见证技术对行业的赋能和改变。

关注 1671

Vitamin 关注了专栏 · 2019-05-20

Go语言实战

收录实际项目中产生的文章,有趣有料,值得一读

关注 2439

Vitamin 发布了文章 · 2019-03-26

Git使用过程中一些常规的操作

快捷的对一个文件重命名

git mv [文件名] [新文件名]

快捷的删除文件

git rm [文件名]

修改最近一次Commit的Message

git commit --amend

修改历史Commit的Message

git rebase -i [要修改Commit的父加密串]  

例如:我的变更中发现c928292中的Message书写错误
图片描述

合并某几次连续或非连续的Commit

git rebase -i [要修改Commit的父加密串]  

例如:我需要将最近2次对index.html的修改进行合并
图片描述
这里主要是对rebase的操作,无论是修改commit的Message,还是合并连续和非连续的Commit,都是可以使用rebase命令来进行操作的,其中的不同在于要在对话框中执行的命令,具体的命令可以通过对话框中的信息可以查看
图片描述

比较工作区和暂存区之间的差异

git diff -- [路径1] [路径2] [路径3....]

比较工作区和本地仓库之间的差异

git diff HEAD -- [路径1] [路径2] [路径3....]

如何比较暂存区和本地仓库之间的差异

git diff --cached HEAD -- [路径1] [路径2] [路径3....]

查看某次提交某个文件的内容

git cat-file -p [某次Commit的加密串]

例如:我想查看合并后index.htm的文件内容
图片描述
任何一次commit信息中都会包含一个树装结构来存储此次Commit中文件的状态,其中blob类型就是具体的文件

使暂存区与本地仓库保持一致

git reset HEAD 

此操作会将提交到暂存区的改变撤销到工作区

撤销工作区所做的改变

git checkout -- [路径1] [路径2] [路径3....]

让工作区,暂存区和本地仓库保持一致

git reset --hard HEAD

将代码强制回退到某次Commit

git reset --hard [具体的commit的加密串]

当临时插新任务的时候,我们该怎么做

git stash
#记录会以栈的方式进行存储

当我们处理完新任务后,继续以前的开发需要执行以下命令

git stash pop 或 git stash apply
#二者的主要区别就是:后者仍然会保留存储的记录,以便多次使用

备份本地仓库

git clone [当前库所在的路径] [目标备份库所在的路径]
查看原文

赞 0 收藏 0 评论 0

Vitamin 发布了文章 · 2019-03-22

随机获取指定范围内N个不重复数字

    ///  随机获取指定范围内N个不重复数字 
    /// </summary> 
    /// <param name="min">指定范围最小值</param> 
    /// <param name="max">指定范围最大值</param> 
    /// <param name="num">不重复数字的个数</param> 
    /// <returns></returns> 
    private int[] NonRepeatingRandomNumbers(int min, int max, int num) 
    {
        int len = max - min + 1;
        if (min > max || len < 0 || num < 0 || num > len)
        {
            throw new Exception("输入参数错误");
        }
        int[] numbers = new int[len];
        for (int i = min; i <= max; i++)
        {
            numbers[i - min] = i;
        }
        int[] result = new int[num];
        for (int i = 0; i < num; i++)
        {
            Random random = new Random();
            int index = random.Next(0, --len);
            result[i] = numbers[index];
            numbers[index] = numbers[len];
        }
        return result; 
   } 

代码解读

1. 数据源(numbers):存储用户输入最小值(包含)和最大值(包含)之间的连续自然数
2. 目标数组(result):存储最终需要返回的不重复数字
3. 数据源数组长度(len):通过计算得出数据源的数组长度,以便初始化数组,存储数据源
4. 核心循环:
    4.1 从[0,len-1]范围内随机获取下标,并取值放到目标数组内,同时将获取到的元素替换为len-1位置的元素;
    4.2 然后从[0,len-2]范围内随机获取下标,并取值放置到目标数组中,同时将获取到的元素替换为len-2位置的元素;
    4.3 以此类推直到满足目标数组元素的个数为止;

分析

算法的时间复杂度为O(N)

查看原文

赞 0 收藏 0 评论 0

Vitamin 收藏了文章 · 2019-03-13

你不知道的CORS跨域资源共享

了解下同源策略

  • 源(origin)*:就是协议、域名和端口号;
  • 同源: 就是源相同,即协议、域名和端口完全相同
  • 同源策略:同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源;
  • 同源策略的分类:

    1. DOM 同源策略:即针对于DOM,禁止对不同源页面的DOM进行操作;如不同域名的 iframe 是限制互相访问。
    2. XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
  • 不受同源策略限制:

    1. 页面中的链接,重定向以及表单提交(因为表单提交,数据提交到action域后,本身页面就和其没有关系了,不会管请求结果,后面操作都交给了action里面的域)是不会受到同源策略限制的。
    2. 资源的引入不受限制,但是js不能读写加载的内容:如嵌入到页面中的<script data-original="..."></script>,<img>,<link>,<iframe>

为什么要跨域限制

  • 如果没有 DOM 同源策略:那么就没有啥xss的研究了,因为你的网站将不是你的网站,而是大家的,谁都可以写个代码操作你的网站界面
  • 如果没有XMLHttpRequest 同源策略,那么就可以很轻易的进行CSRF(跨站请求伪造)攻击:

    1. 用户登录了自己的网站页面 a.com,cookie中添加了用户标识。
    2. 用户浏览了恶意页面 b.com,执行了页面中的恶意 AJAX 请求代码。
    3. b.coma.com发起 AJAX HTTP 请求,请求会默认把 a.com对应cookie也同时发送过去。
    4. a.com从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据;数据就泄露了。而且由于Ajax在后台执行,这一过程用户是无法感知的。
  • (附)有了XMLHttpRequest 同源策略就可以限制CSRF攻击?别忘了还有不受同源策略的:表单提交和资源引入,(安全问题下期在研究)

跨域决解方案

  1. JSONP 跨域:借鉴于 script 标签不受浏览器同源策略的影响,允许跨域引用资源;因此可以通过动态创建 script 标签,然后利用 src 属性进行跨域;

    • 缺点:
    1. 所有网站都可以拿到数据,存在安全性问题,需要网站双方商议基础token的身份验证。
    2. 只能是GET,不能POST。
    3. 可能被注入恶意代码,篡改页面内容,可以采用字符串过滤来规避此问题。
  2. 服务器代理:浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。
  3. document.domain、window.name 、location.hash:借助于iframe决解DOM同源策略
  4. postMessage:决解DOM同源策略,新方案
  5. CORS(跨域资源共享):这里讲的重点

CORS(跨域资源共享)

  • HTML5 提供的标准跨域解决方案,是一个由浏览器共同遵循的一套控制策略,通过HTTP的Header来进行交互;主要通过后端来设置CORS配置项。

CORS简单使用

  • 之前说得CORS跨域,嗯嗯,后端设置Access-Control-Allow-Origin:*|[或具体的域名]就好了;
  • 第一次尝试:
app.use(async(ctx,next) => {
    ctx.set({
        "Access-Control-Allow-Origin": "http://localhost:8088"
})
  • 发现有些请求可以成功,但是有些还是会报错:

clipboard.png

  • 请求被同源策略阻止,预请求的响应没有通过检查:http返回的不是ok?
  • 并且发现发送的是OPTIONS请求:

clipboard.png

  • 发现:CORS规范将请求分为两种类型,一种是简单请求,另外一种是带预检的非简单请求

简单请求和非简单请求

  • 浏览器发送跨域请求判断方式:

    • 浏览器在发送跨域请求的时候,会先判断下是简单请求还是非简单请求,如果是简单请求,就先执行服务端程序,然后浏览器才会判断是否跨域;
    • 而对于非简单请求,浏览器会在发送实际请求之前先发送一个OPTIONS的HTTP请求来判断服务器是否能接受该跨域请求;如果不能接受的话,浏览器会直接阻止接下来实际请求的发生。
  • 什么是简单请求
  1. 请求方法是如下之一:

    • GET
    • HEAD
    • POST
  2. 所有的Header都只包含如下列表中(没有自定义header):

    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma
  • 除此之外都是非简单请求

CORS非简单请求配置须知

  • 正如上图报错显示,对于非简单请求,浏览器会先发送options预检,预检通过后才会发送真是的请求;
  • 发送options预检请求将关于接下来的真实请求的信息给服务器:
Origin:请求的源域信息
Access-Control-Request-Method:接下来的请求类型,如POST、GET等
Access-Control-Request-Headers:接下来的请求中包含的用户显式设置的Header列表
  • 服务器端收到请求之后,会根据附带的信息来判断是否允许该跨域请求,通过Header返回信息:
Access-Control-Allow-Origin:允许跨域的Origin列表
Access-Control-Allow-Methods:允许跨域的方法列表
Access-Control-Allow-Headers:允许跨域的Header列表,防止遗漏Header,因此建议没有特殊需求的情况下设置为*
Access-Control-Expose-Headers:允许暴露给JavaScript代码的Header列表
Access-Control-Max-Age:最大的浏览器预检请求缓存时间,单位为s

CORS完整配置

  1. koa配置CORS跨域资源共享中间件:
const cors = (origin) => {
    return async (ctx, next) => {
        ctx.set({
            "Access-Control-Allow-Origin": origin, //允许的源
        })
        // 预检请求
        if (ctx.request.method == "OPTIONS") {
            ctx.set({
                'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST', //支持跨域的方法
                'Access-Control-Allow-Headers': '*', //允许的头
                'Access-Control-Max-Age':10000, // 预检请求缓存时间
                // 如果服务器设置Access-Control-Allow-Credentials为true,那么就不能再设置Access-Control-Allow-Origin为*,必须用具体的域名
                'Access-Control-Allow-Credentials':true // 跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)
            });
            ctx.send(null, '预检请求')
        } else {
            // 真实请求
            await next()
        }
    }
}

export default cors
  • 现在不管是简单请求还是非简单请求都可以跨域访问啦~

跨域时如何处理cookie

  • cookie:

    • 我们知道http时无状态的,所以在维持用户状态时,我们一般会使用cookie;
    • cookie每次同源请求都会携带;但是跨域时cookie是不会进行携带发送的;
  • 问题:

    • 由于cookie对于不同源是不能进行操作的;这就导致,服务器无法进行cookie设置,浏览器也没法携带给服务器(场景:用户登录进行登录操作后,发现响应中有set-cookie但是,浏览器cookie并没有相应的cookie)
  • 决解:

    1. 浏览器请求设置withCredentials为true即可让该跨域请求携带 Cookie;使用axios配置axios.defaults.withCredentials = true
    2. 服务器设置Access-Control-Allow-Credentials=true允许跨域请求携带 Cookie
“积跬步、行千里”—— 持续更新中~,喜欢的话留下个赞和关注哦!
查看原文

Vitamin 收藏了文章 · 2019-03-13

你不知道的CORS跨域资源共享

了解下同源策略

  • 源(origin)*:就是协议、域名和端口号;
  • 同源: 就是源相同,即协议、域名和端口完全相同
  • 同源策略:同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源;
  • 同源策略的分类:

    1. DOM 同源策略:即针对于DOM,禁止对不同源页面的DOM进行操作;如不同域名的 iframe 是限制互相访问。
    2. XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
  • 不受同源策略限制:

    1. 页面中的链接,重定向以及表单提交(因为表单提交,数据提交到action域后,本身页面就和其没有关系了,不会管请求结果,后面操作都交给了action里面的域)是不会受到同源策略限制的。
    2. 资源的引入不受限制,但是js不能读写加载的内容:如嵌入到页面中的<script data-original="..."></script>,<img>,<link>,<iframe>

为什么要跨域限制

  • 如果没有 DOM 同源策略:那么就没有啥xss的研究了,因为你的网站将不是你的网站,而是大家的,谁都可以写个代码操作你的网站界面
  • 如果没有XMLHttpRequest 同源策略,那么就可以很轻易的进行CSRF(跨站请求伪造)攻击:

    1. 用户登录了自己的网站页面 a.com,cookie中添加了用户标识。
    2. 用户浏览了恶意页面 b.com,执行了页面中的恶意 AJAX 请求代码。
    3. b.coma.com发起 AJAX HTTP 请求,请求会默认把 a.com对应cookie也同时发送过去。
    4. a.com从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据;数据就泄露了。而且由于Ajax在后台执行,这一过程用户是无法感知的。
  • (附)有了XMLHttpRequest 同源策略就可以限制CSRF攻击?别忘了还有不受同源策略的:表单提交和资源引入,(安全问题下期在研究)

跨域决解方案

  1. JSONP 跨域:借鉴于 script 标签不受浏览器同源策略的影响,允许跨域引用资源;因此可以通过动态创建 script 标签,然后利用 src 属性进行跨域;

    • 缺点:
    1. 所有网站都可以拿到数据,存在安全性问题,需要网站双方商议基础token的身份验证。
    2. 只能是GET,不能POST。
    3. 可能被注入恶意代码,篡改页面内容,可以采用字符串过滤来规避此问题。
  2. 服务器代理:浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。
  3. document.domain、window.name 、location.hash:借助于iframe决解DOM同源策略
  4. postMessage:决解DOM同源策略,新方案
  5. CORS(跨域资源共享):这里讲的重点

CORS(跨域资源共享)

  • HTML5 提供的标准跨域解决方案,是一个由浏览器共同遵循的一套控制策略,通过HTTP的Header来进行交互;主要通过后端来设置CORS配置项。

CORS简单使用

  • 之前说得CORS跨域,嗯嗯,后端设置Access-Control-Allow-Origin:*|[或具体的域名]就好了;
  • 第一次尝试:
app.use(async(ctx,next) => {
    ctx.set({
        "Access-Control-Allow-Origin": "http://localhost:8088"
})
  • 发现有些请求可以成功,但是有些还是会报错:

clipboard.png

  • 请求被同源策略阻止,预请求的响应没有通过检查:http返回的不是ok?
  • 并且发现发送的是OPTIONS请求:

clipboard.png

  • 发现:CORS规范将请求分为两种类型,一种是简单请求,另外一种是带预检的非简单请求

简单请求和非简单请求

  • 浏览器发送跨域请求判断方式:

    • 浏览器在发送跨域请求的时候,会先判断下是简单请求还是非简单请求,如果是简单请求,就先执行服务端程序,然后浏览器才会判断是否跨域;
    • 而对于非简单请求,浏览器会在发送实际请求之前先发送一个OPTIONS的HTTP请求来判断服务器是否能接受该跨域请求;如果不能接受的话,浏览器会直接阻止接下来实际请求的发生。
  • 什么是简单请求
  1. 请求方法是如下之一:

    • GET
    • HEAD
    • POST
  2. 所有的Header都只包含如下列表中(没有自定义header):

    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma
  • 除此之外都是非简单请求

CORS非简单请求配置须知

  • 正如上图报错显示,对于非简单请求,浏览器会先发送options预检,预检通过后才会发送真是的请求;
  • 发送options预检请求将关于接下来的真实请求的信息给服务器:
Origin:请求的源域信息
Access-Control-Request-Method:接下来的请求类型,如POST、GET等
Access-Control-Request-Headers:接下来的请求中包含的用户显式设置的Header列表
  • 服务器端收到请求之后,会根据附带的信息来判断是否允许该跨域请求,通过Header返回信息:
Access-Control-Allow-Origin:允许跨域的Origin列表
Access-Control-Allow-Methods:允许跨域的方法列表
Access-Control-Allow-Headers:允许跨域的Header列表,防止遗漏Header,因此建议没有特殊需求的情况下设置为*
Access-Control-Expose-Headers:允许暴露给JavaScript代码的Header列表
Access-Control-Max-Age:最大的浏览器预检请求缓存时间,单位为s

CORS完整配置

  1. koa配置CORS跨域资源共享中间件:
const cors = (origin) => {
    return async (ctx, next) => {
        ctx.set({
            "Access-Control-Allow-Origin": origin, //允许的源
        })
        // 预检请求
        if (ctx.request.method == "OPTIONS") {
            ctx.set({
                'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST', //支持跨域的方法
                'Access-Control-Allow-Headers': '*', //允许的头
                'Access-Control-Max-Age':10000, // 预检请求缓存时间
                // 如果服务器设置Access-Control-Allow-Credentials为true,那么就不能再设置Access-Control-Allow-Origin为*,必须用具体的域名
                'Access-Control-Allow-Credentials':true // 跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)
            });
            ctx.send(null, '预检请求')
        } else {
            // 真实请求
            await next()
        }
    }
}

export default cors
  • 现在不管是简单请求还是非简单请求都可以跨域访问啦~

跨域时如何处理cookie

  • cookie:

    • 我们知道http时无状态的,所以在维持用户状态时,我们一般会使用cookie;
    • cookie每次同源请求都会携带;但是跨域时cookie是不会进行携带发送的;
  • 问题:

    • 由于cookie对于不同源是不能进行操作的;这就导致,服务器无法进行cookie设置,浏览器也没法携带给服务器(场景:用户登录进行登录操作后,发现响应中有set-cookie但是,浏览器cookie并没有相应的cookie)
  • 决解:

    1. 浏览器请求设置withCredentials为true即可让该跨域请求携带 Cookie;使用axios配置axios.defaults.withCredentials = true
    2. 服务器设置Access-Control-Allow-Credentials=true允许跨域请求携带 Cookie
“积跬步、行千里”—— 持续更新中~,喜欢的话留下个赞和关注哦!
查看原文

Vitamin 赞了文章 · 2019-03-13

你不知道的CORS跨域资源共享

了解下同源策略

  • 源(origin)*:就是协议、域名和端口号;
  • 同源: 就是源相同,即协议、域名和端口完全相同
  • 同源策略:同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源;
  • 同源策略的分类:

    1. DOM 同源策略:即针对于DOM,禁止对不同源页面的DOM进行操作;如不同域名的 iframe 是限制互相访问。
    2. XMLHttpRequest 同源策略:禁止使用 XHR 对象向不同源的服务器地址发起 HTTP 请求。
  • 不受同源策略限制:

    1. 页面中的链接,重定向以及表单提交(因为表单提交,数据提交到action域后,本身页面就和其没有关系了,不会管请求结果,后面操作都交给了action里面的域)是不会受到同源策略限制的。
    2. 资源的引入不受限制,但是js不能读写加载的内容:如嵌入到页面中的<script data-original="..."></script>,<img>,<link>,<iframe>

为什么要跨域限制

  • 如果没有 DOM 同源策略:那么就没有啥xss的研究了,因为你的网站将不是你的网站,而是大家的,谁都可以写个代码操作你的网站界面
  • 如果没有XMLHttpRequest 同源策略,那么就可以很轻易的进行CSRF(跨站请求伪造)攻击:

    1. 用户登录了自己的网站页面 a.com,cookie中添加了用户标识。
    2. 用户浏览了恶意页面 b.com,执行了页面中的恶意 AJAX 请求代码。
    3. b.coma.com发起 AJAX HTTP 请求,请求会默认把 a.com对应cookie也同时发送过去。
    4. a.com从发送的 cookie 中提取用户标识,验证用户无误,response 中返回请求数据;数据就泄露了。而且由于Ajax在后台执行,这一过程用户是无法感知的。
  • (附)有了XMLHttpRequest 同源策略就可以限制CSRF攻击?别忘了还有不受同源策略的:表单提交和资源引入,(安全问题下期在研究)

跨域决解方案

  1. JSONP 跨域:借鉴于 script 标签不受浏览器同源策略的影响,允许跨域引用资源;因此可以通过动态创建 script 标签,然后利用 src 属性进行跨域;

    • 缺点:
    1. 所有网站都可以拿到数据,存在安全性问题,需要网站双方商议基础token的身份验证。
    2. 只能是GET,不能POST。
    3. 可能被注入恶意代码,篡改页面内容,可以采用字符串过滤来规避此问题。
  2. 服务器代理:浏览器有跨域限制,但是服务器不存在跨域问题,所以可以由服务器请求所要域的资源再返回给客户端。
  3. document.domain、window.name 、location.hash:借助于iframe决解DOM同源策略
  4. postMessage:决解DOM同源策略,新方案
  5. CORS(跨域资源共享):这里讲的重点

CORS(跨域资源共享)

  • HTML5 提供的标准跨域解决方案,是一个由浏览器共同遵循的一套控制策略,通过HTTP的Header来进行交互;主要通过后端来设置CORS配置项。

CORS简单使用

  • 之前说得CORS跨域,嗯嗯,后端设置Access-Control-Allow-Origin:*|[或具体的域名]就好了;
  • 第一次尝试:
app.use(async(ctx,next) => {
    ctx.set({
        "Access-Control-Allow-Origin": "http://localhost:8088"
})
  • 发现有些请求可以成功,但是有些还是会报错:

clipboard.png

  • 请求被同源策略阻止,预请求的响应没有通过检查:http返回的不是ok?
  • 并且发现发送的是OPTIONS请求:

clipboard.png

  • 发现:CORS规范将请求分为两种类型,一种是简单请求,另外一种是带预检的非简单请求

简单请求和非简单请求

  • 浏览器发送跨域请求判断方式:

    • 浏览器在发送跨域请求的时候,会先判断下是简单请求还是非简单请求,如果是简单请求,就先执行服务端程序,然后浏览器才会判断是否跨域;
    • 而对于非简单请求,浏览器会在发送实际请求之前先发送一个OPTIONS的HTTP请求来判断服务器是否能接受该跨域请求;如果不能接受的话,浏览器会直接阻止接下来实际请求的发生。
  • 什么是简单请求
  1. 请求方法是如下之一:

    • GET
    • HEAD
    • POST
  2. 所有的Header都只包含如下列表中(没有自定义header):

    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma
  • 除此之外都是非简单请求

CORS非简单请求配置须知

  • 正如上图报错显示,对于非简单请求,浏览器会先发送options预检,预检通过后才会发送真是的请求;
  • 发送options预检请求将关于接下来的真实请求的信息给服务器:
Origin:请求的源域信息
Access-Control-Request-Method:接下来的请求类型,如POST、GET等
Access-Control-Request-Headers:接下来的请求中包含的用户显式设置的Header列表
  • 服务器端收到请求之后,会根据附带的信息来判断是否允许该跨域请求,通过Header返回信息:
Access-Control-Allow-Origin:允许跨域的Origin列表
Access-Control-Allow-Methods:允许跨域的方法列表
Access-Control-Allow-Headers:允许跨域的Header列表,防止遗漏Header,因此建议没有特殊需求的情况下设置为*
Access-Control-Expose-Headers:允许暴露给JavaScript代码的Header列表
Access-Control-Max-Age:最大的浏览器预检请求缓存时间,单位为s

CORS完整配置

  1. koa配置CORS跨域资源共享中间件:
const cors = (origin) => {
    return async (ctx, next) => {
        ctx.set({
            "Access-Control-Allow-Origin": origin, //允许的源
        })
        // 预检请求
        if (ctx.request.method == "OPTIONS") {
            ctx.set({
                'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST', //支持跨域的方法
                'Access-Control-Allow-Headers': '*', //允许的头
                'Access-Control-Max-Age':10000, // 预检请求缓存时间
                // 如果服务器设置Access-Control-Allow-Credentials为true,那么就不能再设置Access-Control-Allow-Origin为*,必须用具体的域名
                'Access-Control-Allow-Credentials':true // 跨域请求携带身份信息(Credential,例如Cookie或者HTTP认证信息)
            });
            ctx.send(null, '预检请求')
        } else {
            // 真实请求
            await next()
        }
    }
}

export default cors
  • 现在不管是简单请求还是非简单请求都可以跨域访问啦~

跨域时如何处理cookie

  • cookie:

    • 我们知道http时无状态的,所以在维持用户状态时,我们一般会使用cookie;
    • cookie每次同源请求都会携带;但是跨域时cookie是不会进行携带发送的;
  • 问题:

    • 由于cookie对于不同源是不能进行操作的;这就导致,服务器无法进行cookie设置,浏览器也没法携带给服务器(场景:用户登录进行登录操作后,发现响应中有set-cookie但是,浏览器cookie并没有相应的cookie)
  • 决解:

    1. 浏览器请求设置withCredentials为true即可让该跨域请求携带 Cookie;使用axios配置axios.defaults.withCredentials = true
    2. 服务器设置Access-Control-Allow-Credentials=true允许跨域请求携带 Cookie
“积跬步、行千里”—— 持续更新中~,喜欢的话留下个赞和关注哦!
查看原文

赞 127 收藏 102 评论 3

Vitamin 赞了文章 · 2018-12-27

在 Go 中恰到好处的内存对齐

image

原文地址:在 Go 中恰到好处的内存对齐

问题

type Part1 struct {
    a bool
    b int32
    c int8
    d int64
    e byte
}

在开始之前,希望你计算一下 Part1 共占用的大小是多少呢?

func main() {
    fmt.Printf("bool size: %d\n", unsafe.Sizeof(bool(true)))
    fmt.Printf("int32 size: %d\n", unsafe.Sizeof(int32(0)))
    fmt.Printf("int8 size: %d\n", unsafe.Sizeof(int8(0)))
    fmt.Printf("int64 size: %d\n", unsafe.Sizeof(int64(0)))
    fmt.Printf("byte size: %d\n", unsafe.Sizeof(byte(0)))
    fmt.Printf("string size: %d\n", unsafe.Sizeof("EDDYCJY"))
}

输出结果:

bool size: 1
int32 size: 4
int8 size: 1
int64 size: 8
byte size: 1
string size: 16

这么一算,Part1 这一个结构体的占用内存大小为 1+4+1+8+1 = 15 个字节。相信有的小伙伴是这么算的,看上去也没什么毛病

真实情况是怎么样的呢?我们实际调用看看,如下:

type Part1 struct {
    a bool
    b int32
    c int8
    d int64
    e byte
}

func main() {
    part1 := Part1{}
    
    fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
}

输出结果:

part1 size: 32, align: 8

最终输出为占用 32 个字节。这与前面所预期的结果完全不一样。这充分地说明了先前的计算方式是错误的。为什么呢?

在这里要提到 “内存对齐” 这一概念,才能够用正确的姿势去计算,接下来我们详细的讲讲它是什么

内存对齐

有的小伙伴可能会认为内存读取,就是一个简单的字节数组摆放

image

上图表示一个坑一个萝卜的内存读取方式。但实际上 CPU 并不会以一个一个字节去读取和写入内存。相反 CPU 读取内存是一块一块读取的,块的大小可以为 2、4、6、8、16 字节等大小。块大小我们称其为内存访问粒度。如下图:

image

在样例中,假设访问粒度为 4。 CPU 是以每 4 个字节大小的访问粒度去读取和写入内存的。这才是正确的姿势

为什么要关心对齐

  • 你正在编写的代码在性能(CPU、Memory)方面有一定的要求
  • 你正在处理向量方面的指令
  • 某些硬件平台(ARM)体系不支持未对齐的内存访问

另外作为一个工程师,你也很有必要学习这块知识点哦 :)

为什么要做对齐

  • 平台(移植性)原因:不是所有的硬件平台都能够访问任意地址上的任意数据。例如:特定的硬件平台只允许在特定地址获取特定类型的数据,否则会导致异常情况
  • 性能原因:若访问未对齐的内存,将会导致 CPU 进行两次内存访问,并且要花费额外的时钟周期来处理对齐及运算。而本身就对齐的内存仅需要一次访问就可以完成读取动作

image

在上图中,假设从 Index 1 开始读取,将会出现很崩溃的问题。因为它的内存访问边界是不对齐的。因此 CPU 会做一些额外的处理工作。如下:

  1. CPU 首次读取未对齐地址的第一个内存块,读取 0-3 字节。并移除不需要的字节 0
  2. CPU 再次读取未对齐地址的第二个内存块,读取 4-7 字节。并移除不需要的字节 5、6、7 字节
  3. 合并 1-4 字节的数据
  4. 合并后放入寄存器

从上述流程可得出,不做 “内存对齐” 是一件有点 "麻烦" 的事。因为它会增加许多耗费时间的动作

而假设做了内存对齐,从 Index 0 开始读取 4 个字节,只需要读取一次,也不需要额外的运算。这显然高效很多,是标准的空间换时间做法

默认系数

在不同平台上的编译器都有自己默认的 “对齐系数”,可通过预编译命令 #pragma pack(n) 进行变更,n 就是代指 “对齐系数”。一般来讲,我们常用的平台的系数如下:

  • 32 位:4
  • 64 位:8

另外要注意,不同硬件平台占用的大小和对齐值都可能是不一样的。因此本文的值不是唯一的,调试的时候需按本机的实际情况考虑

成员对齐

func main() {
    fmt.Printf("bool align: %d\n", unsafe.Alignof(bool(true)))
    fmt.Printf("int32 align: %d\n", unsafe.Alignof(int32(0)))
    fmt.Printf("int8 align: %d\n", unsafe.Alignof(int8(0)))
    fmt.Printf("int64 align: %d\n", unsafe.Alignof(int64(0)))
    fmt.Printf("byte align: %d\n", unsafe.Alignof(byte(0)))
    fmt.Printf("string align: %d\n", unsafe.Alignof("EDDYCJY"))
    fmt.Printf("map align: %d\n", unsafe.Alignof(map[string]string{}))
}

输出结果:

bool align: 1
int32 align: 4
int8 align: 1
int64 align: 8
byte align: 1
string align: 8
map align: 8

在 Go 中可以调用 unsafe.Alignof 来返回相应类型的对齐系数。通过观察输出结果,可得知基本都是 2^n,最大也不会超过 8。这是因为我手提(64 位)编译器默认对齐系数是 8,因此最大值不会超过这个数

整体对齐

在上小节中,提到了结构体中的成员变量要做字节对齐。那么想当然身为最终结果的结构体,也是需要做字节对齐的

对齐规则

  • 结构体的成员变量,第一个成员变量的偏移量为 0。往后的每个成员变量的对齐值必须为编译器默认对齐长度#pragma pack(n))或当前成员变量类型的长度unsafe.Sizeof),取最小值作为当前类型的对齐值。其偏移量必须为对齐值的整数倍
  • 结构体本身,对齐值必须为编译器默认对齐长度#pragma pack(n))或结构体的所有成员变量类型中的最大长度,取最大数的最小整数倍作为对齐值
  • 结合以上两点,可得知若编译器默认对齐长度#pragma pack(n))超过结构体内成员变量的类型最大长度时,默认对齐长度是没有任何意义的

分析流程

接下来我们一起分析一下,“它” 到底经历了些什么,影响了 “预期” 结果

成员变量类型偏移量自身占用
abool01
字节对齐13
bint3244
cint881
字节对齐97
dint64168
ebyte241
字节对齐257
总占用大小--32

成员对齐

  • 第一个成员 a

    • 类型为 bool
    • 大小/对齐值为 1 字节
    • 初始地址,偏移量为 0。占用了第 1 位
  • 第二个成员 b

    • 类型为 int32
    • 大小/对齐值为 4 字节
    • 根据规则 1,其偏移量必须为 4 的整数倍。确定偏移量为 4,因此 2-4 位为 Padding。而当前数值从第 5 位开始填充,到第 8 位。如下:axxx|bbbb
  • 第三个成员 c

    • 类型为 int8
    • 大小/对齐值为 1 字节
    • 根据规则1,其偏移量必须为 1 的整数倍。当前偏移量为 8。不需要额外对齐,填充 1 个字节到第 9 位。如下:axxx|bbbb|c...
  • 第四个成员 d

    • 类型为 int64
    • 大小/对齐值为 8 字节
    • 根据规则 1,其偏移量必须为 8 的整数倍。确定偏移量为 16,因此 9-16 位为 Padding。而当前数值从第 17 位开始写入,到第 24 位。如下:axxx|bbbb|cxxx|xxxx|dddd|dddd
  • 第五个成员 e

    • 类型为 byte
    • 大小/对齐值为 1 字节
    • 根据规则 1,其偏移量必须为 1 的整数倍。当前偏移量为 24。不需要额外对齐,填充 1 个字节到第 25 位。如下:axxx|bbbb|cxxx|xxxx|dddd|dddd|e...

整体对齐

在每个成员变量进行对齐后,根据规则 2,整个结构体本身也要进行字节对齐,因为可发现它可能并不是 2^n,不是偶数倍。显然不符合对齐的规则

根据规则 2,可得出对齐值为 8。现在的偏移量为 25,不是 8 的整倍数。因此确定偏移量为 32。对结构体进行对齐

结果

Part1 内存布局:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx

小结

通过本节的分析,可得知先前的 “推算” 为什么错误?

是因为实际内存管理并非 “一个萝卜一个坑” 的思想。而是一块一块。通过空间换时间(效率)的思想来完成这块读取、写入。另外也需要兼顾不同平台的内存操作情况

巧妙的结构体

在上一小节,可得知根据成员变量的类型不同,其结构体的内存会产生对齐等动作。那假设字段顺序不同,会不会有什么变化呢?我们一起来试试吧 :-)

type Part1 struct {
    a bool
    b int32
    c int8
    d int64
    e byte
}

type Part2 struct {
    e byte
    c int8
    a bool
    b int32
    d int64
}

func main() {
    part1 := Part1{}
    part2 := Part2{}

    fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
    fmt.Printf("part2 size: %d, align: %d\n", unsafe.Sizeof(part2), unsafe.Alignof(part2))
}

输出结果:

part1 size: 32, align: 8
part2 size: 16, align: 8

通过结果可以惊喜的发现,只是 “简单” 对成员变量的字段顺序进行改变,就改变了结构体占用大小

接下来我们一起剖析一下 Part2,看看它的内部到底和上一位之间有什么区别,才导致了这样的结果?

分析流程

成员变量类型偏移量自身占用
ebyte01
cint811
abool21
字节对齐31
bint3244
dint6488
总占用大小--16

成员对齐

  • 第一个成员 e

    • 类型为 byte
    • 大小/对齐值为 1 字节
    • 初始地址,偏移量为 0。占用了第 1 位
  • 第二个成员 c

    • 类型为 int8
    • 大小/对齐值为 1 字节
    • 根据规则1,其偏移量必须为 1 的整数倍。当前偏移量为 2。不需要额外对齐
  • 第三个成员 a

    • 类型为 bool
    • 大小/对齐值为 1 字节
    • 根据规则1,其偏移量必须为 1 的整数倍。当前偏移量为 3。不需要额外对齐
  • 第四个成员 b

    • 类型为 int32
    • 大小/对齐值为 4 字节
    • 根据规则1,其偏移量必须为 4 的整数倍。确定偏移量为 4,因此第 3 位为 Padding。而当前数值从第 4 位开始填充,到第 8 位。如下:ecax|bbbb
  • 第五个成员 d

    • 类型为 int64
    • 大小/对齐值为 8 字节
    • 根据规则1,其偏移量必须为 8 的整数倍。当前偏移量为 8。不需要额外对齐,从 9-16 位填充 8 个字节。如下:ecax|bbbb|dddd|dddd

整体对齐

符合规则 2,不需要额外对齐

结果

Part2 内存布局:ecax|bbbb|dddd|dddd

总结

通过对比 Part1Part2 的内存布局,你会发现两者有很大的不同。如下:

  • Part1:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx
  • Part2:ecax|bbbb|dddd|dddd

仔细一看,Part1 存在许多 Padding。显然它占据了不少空间,那么 Padding 是怎么出现的呢?

通过本文的介绍,可得知是由于不同类型导致需要进行字节对齐,以此保证内存的访问边界

那么也不难理解,为什么调整结构体内成员变量的字段顺序就能达到缩小结构体占用大小的疑问了,是因为巧妙地减少了 Padding 的存在。让它们更 “紧凑” 了。这一点对于加深 Go 的内存布局印象和大对象的优化非常有帮

当然了,没什么特殊问题,你可以不关注这一块。但你要知道这块知识点 😄

参考

查看原文

赞 77 收藏 39 评论 4

认证与成就

  • 获得 7 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-07-26
个人主页被 389 人浏览