gzip带来的工作中的小插曲

背景

今天,爬取一个https地址的时候,发现如下问题,从浏览器打开该地址,显示正常的json格式。但通过curl访问却返回类似乱码一样的内容,一开始以为是https协议安全机制导致的异常。进过细究后发觉不可能,后来查证是因为服务端对数据做了gzip压缩,因此客户端如果要获取到正确的数据,需要对数据做解压,在curl中,增加参数–compressed。如果是node的request模块,则在其配置相中设置gzip: true即可。

gzip

gzip 是服务端为了节省流量,对数据做压缩的一种方案。特别是在针对某些数据量特别大的接口,压缩可以大大减少网络带宽的消耗,但同时也会增加服务器CPU的压力,因此只有在接口数据量特别巨大,确实有必要的时候才用。服务端使用了gzip压缩,客户端获取到数据后就必须对数据做解压。浏览器会自动处理解压操作,而其他客户端一般需要某些设定或依赖其它的工具才能完成解压的工作。

https原理与运行流程

1.客户端的浏览器向服务器传送客户端 SSL 协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。

2.服务器向客户端传送 SSL 协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。

3.客户利用服务器传过来的信息 验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的 CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行第四步。

4.用户端随机产生一个用于后面通讯的“对称密码”,然后 用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的“预主密码”传给服务器。

5.如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器。

6.如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的CA 是否可靠,发行CA 的公钥能否正确解开客户证书的发行 CA 的数字签名,检查客户的证书是否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;

7.如果验证通过,服务器将用自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。

8.服务器和客户端用相同的主密码即“通话密码”,一个对称密钥用于 SSL 协议的安全数据通讯的加解密通讯。同时在 SSL 通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。

9.客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。

10.服务器向客户端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。

SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。

从上面的流程可以看出(加粗字体),SSL通信在握手阶段使用的是非对称加密,在数据的传输阶段使用的是对成加密。

参考资料

爬取facebook站点的正确姿势

背景

最近的爬虫工作做得有些无趣,还好遇上几个比较难缠的站点,积累点偏门的经验。平常我们爬普通站点,跟着页码一页一页慢慢翻就可以,当然如果你觉得太慢,大可直接构造分页,然后并发爬取,很快很暴力,但是被封的风险也很高,偶尔遇到些像facebook这样的站点,还是比较有趣的。

GraphQL

GraphQL是最近的一个技术热点,这个技术就是Facebook放出来的,他家的产品自然也是用的这个技术做查询。对于这个技术,我之前也就只是听说而已,没有太多了解,今天爬取这个站点的时候,算是打了个照面,知道了它的查询方法。

跟普通站点的分页不同,没有死板的页码,它通过前端构造一段查询的文本,通过post方法提交后,由后端解析,后端知道了要查询哪些数据,并如何整合数据,处理完之后,以特定的格式返回数据。GraphQL的查询方式有点像关系数据库的join,而且要什么数据,怎么整合数据都是前端在字符串中定义好的,后端只负责解析字符串并查询、整合和返回数据。以往我们都是在后端定义好数据的查询,前端顶多就是一个url和一堆参数,根本看不出数据存储格式以及她们之间的关联。但GraphQL改变了这一切,看一段GraphQL的查询语句:

1
{"q7":{"priority":0,"q":"Query PagePhotosTabAllPhotosView_react_PageRelayQL {node(1515755625400923) {@F3}} QueryFragment F0 : Feedback {does_viewer_like,id} QueryFragment F1 : Photo {id,album {id,name},feedback {id,can_viewer_comment,can_viewer_like,likers {count},comments {count},@F0}} QueryFragment F2 : Photo {image.sizing(cover-fill-cropped).height(201).width(201) as _image1LP0rd {uri},url,id,@F1} QueryFragment F3 : Page {id,posted_photos.after(1543357872640698).first(28) as _posted_photos1oJl4V {edges {node {id,@F2},cursor},page_info {has_next_page,has_previous_page}}}","query_params":{}}}
1
Query PagePhotosTabAllPhotosView_react_PageRelayQL {node(1515755625400923) {@F3}}

定义一个查询,返回一个对象,将node后面的ID作为F3的查询ID查询病返回结果,查询表达式为{@F3}

1
QueryFragment F3 : Page {id,posted_photos.after(1543357872640698).first(28) as _posted_photos1oJl4V {edges {node {id,@F2},cursor},page_info {has_next_page,has_previous_page}}}

定义查询碎片,从Page集合中查询ID为1515755625400923的相关字段,返回内容包括id和_posted_photos1oJl4V

1
posted_photos.after(1543357872640698).first(28) as _posted_photos1oJl4V {edges {node {id,@F2},cursor},page_info {has_next_page,has_previous_page}}

从posted_photos这个字段中查询出ID为1543357872640698之后的28条数据,并以_posted_photos1oJl4V为名返回,对象中包括edages和page_info两个属性,edges包括node和curser两个属性,page_info包括has_next_page和has_previous_page两个属性。node包含id,以及该id对应下F2的返回数据。

1
QueryFragment F2 : Photo {image.sizing(cover-fill-cropped).height(201).width(201) as _image1LP0rd {uri},url,id,@F1}

从Photo集合中查出与当前id相对应的图片,并将图片缩放,以_image1LPord.uri返回,同时返回url和id以及F1返回值。

另外两段我就不解释了,大致明白了这段代码的含义。我做的爬虫其实只对它的图片感兴趣,因此我是想要他的原图的,怎么搞更方便呢??我尝试着去删掉.height(201).width(201)这段代码,虽然它返回了一张更大的图,但并非原图,很可惜,只能通过其他途径来获取了。例如url字段。

我们爬取的要求很经常是首次爬取全部,之后每天爬取前几页。怎么爬取全部呢?F3里面的posted_photos.after(1543357872640698).first(28)你知道怎么改了嘛,把.after(1543357872640698).first(28)也去掉就可以了,这个测试是通过的。通过调整得到了下面的查询字符串:

1
{"q7":{"priority":0,"q":"Query PagePhotosTabAllPhotosView {node(1515755625400923) {@F3}} QueryFragment F2 : Photo {url} QueryFragment F3 : Page {posted_photos as data {edges {node {id,@F2}}}}","query_params":{}}}

通过encodeURIComponent转码并套上curl的代码

1
curl -X POST --data "__user=0&__a=1&__dyn=7xeUmwkHgfpEbEK4osrWo5O12wAxu13wqovzEcUO2qaxe4E4u14wXx6exm3q260EEqx24o&__req=5&__be=-1&__pc=PHASED:DEFAULT&lsd=AVopxP6J&__rev=2408878&batch_name=PagePhotosTabAllPhotosView_react_PageRelayQL&method=GET&queries=%7B%22q7%22%3A%7B%22priority%22%3A0%2C%22q%22%3A%22Query%20PagePhotosTabAllPhotosView%20%7Bnode(1515755625400923)%20%7B%40F3%7D%7D%20QueryFragment%20F2%20%3A%20Photo%20%7Burl%7D%20QueryFragment%20F3%20%3A%20Page%20%7Bposted_photos%20as%20data%20%7Bedges%20%7Bnode%20%7Bid%2C%40F2%7D%7D%7D%7D%22%2C%22query_params%22%3A%7B%7D%7D%7D&response_format=json&scheduler=phased" https://www.facebook.com/api/graphqlbatch/

请求后就可以活动该用户所有图片页面了。

浅谈html中charset的作用

概要

我们编辑一份代码的时候,会用一个文件来存储这份代码。用来存储的这个文件,有多种编码方式,常见的有utf-8gb2312编码方式。当你在文件中写入某些特殊字符,例如中文的时候,计算机需要使用当前文件的编码方式来存储这些字符。因此,存储这些信息的格式,是以这种编码方式进行转译后的代码格式,而并非该字符本身。这种编码计算机可读,而人类不可读。

如果你刚接触计算机,可能会好奇,“明明我打开文件看到的就是我能理解的文本,并没有所谓我读不懂的东西。“是的,当你打开文本文件的时候,计算机肯定要给你你能看懂的文本,如果他给的是一堆看都看不懂的代码,你肯定要退货。所以,你所看到的文本,其实是文本文件这个程序通过你设定的那个编码格式,将代码文件转换成你能读懂的文本后结果。

编码方式相当于是一把钥匙,只有给对钥匙,计算机才能将代码正确解析成人类能看懂的文本。当我们需要将这种由人类不可读的代码重新转换为人类可读的文本时,我们需要告诉计算机,这种代码使用哪种类型的编码方式做的转换,计算机才能正确地将代码解析成正确的可读文本,否则,计算机会将代码做错误的解析,最终输出的文本我们称为乱码

html

当我们要在浏览器中展现各种文字的时候,我们需要将一份代码文件交给浏览器去呈现。我们举个比较简单的例子,像我的博客,我的前端页面目前搭建的是一个静态站点,那么我要交给浏览器的这份代码文件必然是直接从某个文本中读取出来的。这个文件就是前文中所说的那个代码文件,它在创建或保存的时候就被指定了编码格式,因此,他所保存的内容就是以该编码格式进行编码后的代码结果。当我们需要把这些代码在浏览器中呈现的时候,我们同样需要告诉浏览器,这些文件是以什么编码格式做的编码。

这个时候,我们可能会有另外一个疑惑,我们保存代码的时候不是已经在文件中指定了该文件的编码格式吗,那我们将文本推送给浏览器的时候,不就清楚我们用的是什么格式的编码了吗?为什么还要再告诉浏览器我们用的是什么编码?也许只有我曾经犯过这个低级的错误!

其实我们推给浏览器的是这个文本中的内容,而文本文件的编码格式是以属性的方式存储在该文件相关的位置,并不存储在文本文件中。因此,当我们将这个文本文件的内容推给浏览器的时候,浏览器并不知道它是以什么样的编码格式存在的,为了保证浏览器能够解析文本内容,我们需要告诉浏览器这个文本文件的存储格式,告诉的方式就是<meta charset="???">

做一个测试,我们可以随便建立一个文本文件,以html为后缀,写入一段中文,双击打开后发现是一些乱码。是的,因为现代浏览器默认的编码格式是ISO-8859-1,而我们一般创建的文本文件是UTF-8编码(也有可能是gb2312等其它编码)。因为没有指定正确的编码格式,因此,网页以ISO-8859-1的解码方式去解了UTF-8编码后的代码文件,用错了钥匙,于是出现了乱码。这时候我们只要在指定<meta charset="UTF-8">即可。

爬虫

为什么会写这篇文章呢?因为最近在做爬虫。爬到一个国外的站点,这个站点设置了charset="ibm866"。根据我们爬虫的框架,在抓取到页面数据后,会用页面指定的charset去反编码页面。之前的站点都正常,出了这个站点。一执行反编码操作,页面内容全部变成乱码。很奇怪,于是我用curl尝试请求该页面,发现返回回来的数据在bash里面可以正常显示。内容与网页所见一致,由于bash本身是utf-8编码,于是我推断,该站点正确的编码格式应该是utf-8而不是ibm866,否则我的bash不可能正常解析。

疑问

于是,我产生了疑问,如果说文档的编码格式是UTF-8,而页面设置的编码格式是ibm866,那根据常理来说应该会解析错误,导致页面出现乱码,但那个站点却显示正常。

为了搞清楚这个问题,我创建了一个UTF-8格式的html文件,写上一段中文,双击打开,乱码,预料之中。然后我把这个文件放到web服务器中去,通过http访问这个文件,这个文件正常显示中文了,我比较惊讶,浏览器默认编码格式并非UTF-8。接着,我试着将charset定义为各种错误的编码方法,结果都是正常显示中文字符。这个有点超过我的知识范围了,我在segmentfault提交了一个问题,如果你明白这个原理,请不吝赐教,谢谢!!

sublime中配置使用eslint

之前一直使用jshint,只从用上了asyncwait这些语法糖之后,发现jshint无法识别了,于是准备换eslint

安装

1
npm install eslint -g
npm install babel-eslint -g

sublime插件安装

  • 安装 SublimeLinter
  • 安装 SublimeLinter-contrib-eslint

配置

在项目目录下新建.eslintrc文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"env": {
"browser": true,
"node": true,
"es6": true
}
,

"parser": "babel-eslint",
"ecmaFeatures": {
"jsx": true
}
,

"rules": {
"semi": [2, "always"],
"quotes": [2, "single"]
}

}

你也可以执行eslint --init自行配置。

执行

代码执行的时候,因为这些语法糖目前都还未被支持,所以需要使用babel进行降级。

新建文件.babelrc写入以下内容

1
2
3
4
{
"presets": ["es2015"],
"plugins": ["syntax-async-functions","transform-regenerator", "transform-runtime"]
}

新建文件package.json写入以下内容

1
2
3
4
5
6
7
8
{
"babel-plugin-syntax-async-functions": "^6.1.4",
"babel-plugin-transform-regenerator": "^6.1.4",
"babel-plugin-transform-runtime": "^6.1.4",
"babel-polyfill": "^6.1.4",
"babel-preset-es2015": "^6.1.4",
"request": "^2.65.0"
}

执行npm i

测试

将下面代码存于项目中的一个文本文件中,暂且命名为test.es7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use strict';

const async = function () {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('end'), 2000);
});
};

const test = async function() {
let ret = await async();
console.log(ret);
};

test();

执行babel-node test.es7。会在2秒后看到end的输出。正常的项目开发中,我们还需要借助gulp等工具作为开发和发布工具。这里只做eslint示例就不再介绍。

async与generator的区别在于,generator没有自动执行的功能,需要依赖co库才能执行,async有自动执行的功能,因此可以像普通函数一样执行。

mongodb replica set配置与mongoose连接实例

mongodb的replica set是一个强大的功能,可以实现读写分离功能,有heartbeat功能,可以实现自动故障转移,比master-slave更强大。

配置

主机1

保存在/etc/mongodb_1.conf

1
systemLog:
   path: "/var/log/mongodb/mongodb_1.log"
   logAppend: true
storage:
   journal:
      enabled: true
   dbPath: "/var/lib/mongodb_1"
processManagement:
   fork: true
   pidFilePath: "/var/run/mongodb/mongodb_1.pid"
net:
   bindIp: 127.0.0.1
   port: 27017
setParameter:
   enableLocalhostAuthBypass: false
replication:
   oplogSizeMB: 1000
   replSetName: 'local-test'

主机2

保存在/etc/mongodb_2.conf

1
systemLog:
   path: "/var/log/mongodb/mongodb_2.log"
   logAppend: true
storage:
   journal:
      enabled: true
   dbPath: "/var/lib/mongodb_2"
processManagement:
   fork: true
   pidFilePath: "/var/run/mongodb/mongodb_2.pid"
net:
   bindIp: 127.0.0.1
   port: 27017
setParameter:
   enableLocalhostAuthBypass: false
replication:
   oplogSizeMB: 1000
   replSetName: 'local-test'

主机3

保存在/etc/mongodb_3.conf

1
systemLog:
   path: "/var/log/mongodb/mongodb_3.log"
   logAppend: true
storage:
   journal:
      enabled: true
   dbPath: "/var/lib/mongodb_3"
processManagement:
   fork: true
   pidFilePath: "/var/run/mongodb/mongodb_3.pid"
net:
   bindIp: 127.0.0.1
   port: 27017
setParameter:
   enableLocalhostAuthBypass: false
replication:
   oplogSizeMB: 1000
   replSetName: 'local-test'

启动

1
mongod -f /etc/mongodb.conf  
mongod -f /etc/mongodb_2.conf  
mongod -f /etc/mongodb_3.conf

设置

通过mongo命令,连接某一台机器,输入下面的配置命令,priority用来标示机器的优先权,优先权高的机器为primary机器,其他机器为secondary,当primary机器故障时,replica set 自动选择剩下机器中priority最高的作为primary机器。

1
// 配置信息
var config = {_id:"repmore",members:[{_id:0,host:'127.0.0.1:27017',priority :2},{_id:1,host:'127.0.0.1:27018',priority:1},{_id:2,host:'127.0.0.1:27019',priority:1}]};
// replica set 初始化
rs.initiate(config);

测试

目前,通过mongo链接三台机器,会发现27017位primiary,其他为secondary。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
local-test:SECONDARY> rs.slaveOk()
local-test:SECONDARY> rs.status()
{
"set" : "local-test",
"date" : ISODate("2016-05-15T23:56:34.146Z"),
"myState" : 2,
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 43848,
"optime" : Timestamp(1463323447, 1),
"optimeDate" : ISODate("2016-05-15T14:44:07Z"),
"lastHeartbeat" : ISODate("2016-05-15T23:56:32.643Z"),
"lastHeartbeatRecv" : ISODate("2016-05-15T23:56:32.641Z"),
"pingMs" : 0,
"electionTime" : Timestamp(1463312747, 1),
"electionDate" : ISODate("2016-05-15T11:45:47Z"),
"configVersion" : 1
},
{
"_id" : 1,
"name" : "127.0.0.1:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 48134,
"optime" : Timestamp(1463323447, 1),
"optimeDate" : ISODate("2016-05-15T14:44:07Z"),
"configVersion" : 1,
"self" : true
},
{
"_id" : 2,
"name" : "127.0.0.1:27019",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 45603,
"optime" : Timestamp(1463323447, 1),
"optimeDate" : ISODate("2016-05-15T14:44:07Z"),
"lastHeartbeat" : ISODate("2016-05-15T23:56:32.279Z"),
"lastHeartbeatRecv" : ISODate("2016-05-15T23:56:32.279Z"),
"pingMs" : 0,
"configVersion" : 1
}
],
"ok" : 1
}

我们kill掉primary机器后,27018、27019会随机选择一台变为primary,因为他们两者的priority是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
local-test:SECONDARY> rs.status()
{
"set" : "local-test",
"date" : ISODate("2016-05-15T23:58:06.082Z"),
"myState" : 2,
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27017",
"health" : 0,
"state" : 8,
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : Timestamp(0, 0),
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2016-05-15T23:58:04.925Z"),
"lastHeartbeatRecv" : ISODate("2016-05-15T23:57:56.777Z"),
"pingMs" : 0,
"lastHeartbeatMessage" : "Failed attempt to connect to 127.0.0.1:27017; couldn't connect to server 127.0.0.1:27017 (127.0.0.1), connection attempt failed",
"configVersion" : -1
},
{
"_id" : 1,
"name" : "127.0.0.1:27018",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 48226,
"optime" : Timestamp(1463323447, 1),
"optimeDate" : ISODate("2016-05-15T14:44:07Z"),
"configVersion" : 1,
"self" : true
},
{
"_id" : 2,
"name" : "127.0.0.1:27019",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 45695,
"optime" : Timestamp(1463323447, 1),
"optimeDate" : ISODate("2016-05-15T14:44:07Z"),
"lastHeartbeat" : ISODate("2016-05-15T23:58:04.452Z"),
"lastHeartbeatRecv" : ISODate("2016-05-15T23:58:04.451Z"),
"pingMs" : 0,
"electionTime" : Timestamp(1463356678, 1),
"electionDate" : ISODate("2016-05-15T23:57:58Z"),
"configVersion" : 1
}
],
"ok" : 1
}

mongoose链接实例

1
'use strict';

var mongoose = require('mongoose');

mongoose.connect('mongodb://127.0.0.1:27017/test', {
	db: { native_parser: true },
  	server: { poolSize: 5 },
  	replset: { rs_name: 'local-test' }
}, function(err) {
	if (err) throw err;
});

var Cat = mongoose.model('Cat', { name: String });

var kitty = new Cat({ name: 'Zildjian' });
kitty.save(function (err) {
  if (err) {
    console.log(err);
  } else {
    console.log('meow');
  }
});

Cat.findOne({}, function(err, data) {
	if (err) throw err;
	console.log(data);
});

Mongodb中一些不常用的命令备忘

查找的出和正则表达式不匹配的文档

1
db.favour.findOne({'floor_id': {$not: /^[0-9A-Za-z]{24,24}$/}});

查找出文档中某个数组元素超过5的文档

1
2
3
4
5
6
// 查找出个数等于5的
db.floor.find({item_list: {$size: 5}})
// 查找出个数不等于5的
db.floor.find({item_list: {$not: {$size: 5}}})
// 查找出个数大于5的
db.floor.find({$where: 'this.item_list && this.item_list.length > 5'})

Nginx配置支持跨域

公司使用swagger作为接口文档容器,文档和接口的域名不同,需要跨域。遇到的最郁闷的坑是,没弄清楚各个属性的含义,导致Access-Control-Allow-Headers漏了两个key没加,一直请求失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if ($request_method = 'OPTIONS') {  
add_header 'Access-Control-Allow-Origin' 'https://docs.domain.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,token';
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' 'https://docs.domain.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,token';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' 'https://docs.domain.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,token';
}

跨域资源共享头信息

顺便学习下跨域共享的头信息

Access-Control-Allow-Origin

origin参数指定一个允许向该服务器提交请求的URI.对于一个不带有credentials的请求,可以指定为’*’,表示允许来自所有域的请求.

1
Access-Control-Allow-Origin: <origin> | *

Access-Control-Expose-Headers

设置浏览器允许访问的服务器的头信息的白名单

1
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

Access-Control-Max-Age

这个头告诉我们这次预请求的结果的有效期是多久

1
Access-Control-Max-Age: <delta-seconds>

Access-Control-Allow-Credentials

告知客户端,当请求的credientials属性是true的时候,响应是否可以被得到.当它作为预请求的响应的一部分时,它用来告知实际的请求是否使用了credentials.注意,简单的GET请求不会预检,所以如果一个请求是为了得到一个带有credentials的资源,而响应里又没有Access-Control-Allow-Credentials头信息,那么说明这个响应被忽略了.

1
Access-Control-Allow-Credentials: true | false

Access-Control-Allow-Methods

指明资源可以被请求的方式有哪些(一个或者多个). 这个响应头信息在客户端发出预检请求的时候会被返回. 上面有相关的例子.

1
Access-Control-Allow-Methods: <method>[, <method>]*

Access-Control-Allow-Headers

也是在响应预检请求的时候使用.用来指明在实际的请求中,可以使用哪些自定义HTTP请求头.

1
Access-Control-Allow-Headers: X-PINGOTHER

就像我遇到的坑,必须把request header中的key都写到这里,否则是无法成功的。

预请求头

Origin

表明发送请求或者预请求的域

1
Origin: <origin>

Access-Control-Request-Method

在发出预检请求时带有这个头信息,告诉服务器在实际请求时会使用的请求方式

1
Access-Control-Request-Method: <method>

Access-Control-Request-Headers

在发出预检请求时带有这个头信息,告诉服务器在实际请求时会携带的自定义头信息.如有多个,可以用逗号分开.

1
Access-Control-Request-Headers: <field-name>[, <field-name>]*

排序算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// 插入排序:假设数组有n个元素,i = 1:n,若i < i-1,则交换两者,直到使得1到i的数组有序递增。时间复杂度O(N^2)
int *InsertSort(int ary[], int n)
{


int i, j, z = 0;

for (i = 1; i < n; i++) {
for (j = i; j > 0; j--) {
if (ary[j] < ary[j - 1]) {
swap(&ary[j], &ary[j - 1]);
} else {// 当i>=i-1时,接下去的比较无意义
break;
}
}
}

return ary;
}

// 选择排序:假设数组长度为n,i=1:n,从i+1到n中选择最小的一个和i交换。时间复杂度O(N^2)
int *SelectionSort(int ary[], int n)
{


int i, j, k, z = 0;

for (i = 0; i < n; i++) {
for (k = i, j = i + 1; j < n; j++) {
if (ary[k] > ary[j]) {
k = j;
}
}
swap(&ary[i], &ary[k]);
}

return ary;
}

// 冒泡排序:假设数组长度为n,i=1:n-1,若i>i+1,则交换两者,直到使得1到n的数组有序递增。时间复杂度O(N^2)
int *BubbleSort(int ary[], int n)
{


int i, j, z = 0;

int swapped = 0;

for (i = 0; i < n; i++) {
for (j = n - 1; j > i; j--) {
if (ary[j] < ary[j - 1]) {
swap(&ary[j], &ary[j - 1]);
swapped = 1;
}
}
if (!swapped) break;
}

return ary;
}

// 堆排序:假设数组长度为n,i=1:n/2-1,若i<2i,则交换两者,若i<2i+1,则交换两者,直到i=0结束,交换0和n-1的元素。n=n-1,重复操作,直到n=2。时间复杂度O(nlgn)
int *HeapSort(int ary[], int n, int total)
{


int i;

for (i = n / 2 - 1; i >= 0; i--) {
if (ary[i] < ary[2 * i]) swap(&ary[i], &ary[2 * i]);
if (ary[i] < ary[2 * i + 1]) swap(&ary[i], &ary[2 * i + 1]);
}

swap(&ary[0], &ary[n - 1]);

if (n > 1) HeapSort(ary, n - 1, total);

return ary;
}

// 希尔排序:假设数组长度为n,步长g循环n>>1,i=g:n,为所有的j = i-g loop, j > 0做排序。
int *ShellSort(int ary[], int n)
{


int gap, i, j, temp;

for (gap = n >> 1; gap > 0; gap >>= 1) {
for (i = gap; i < n; i++) {
temp = ary[i];
for (j = i - gap; j >= 0 && ary[j] > temp; j -= gap) {
ary[j + gap] = ary[j];
}
ary[j + gap] = temp;
}
}

return ary;
}

// 快速排序
void _QuickSort(int ary[], int start, int end)
{

if (start >= end) return;

int l = start, r = end - 1;

while (l < r) {
while (l < r && ary[l] < ary[end]) ++l;
while (l < r && ary[r] >= ary[end]) --r;
if (l != r) swap(&ary[l], &ary[r]);
printf("111本次排序结果:\n");
printf("l: %d, r: %d, s: %d, e: %d\n", l, r, start, end);
printArray(ary, 10);
printf("\n");
}

if (ary[l] > ary[end]) swap(&ary[end], &ary[l]);
else l++; // l++是为了保护已经在有序位上的元素,例如为8,7,9排序,l指向7的索引1,但明显9最大,9后面没有更多的数据需要排序,仅需要对8和7做排序,因此l++,使得l的值为2,仅对8,7做排序

printf("222本次排序结果:\n");
printf("l: %d, r: %d, s: %d, e: %d\n", l, r, start, end);
printArray(ary, 10);
printf("\n");
_QuickSort(ary, start, l - 1);
_QuickSort(ary, l + 1, end);

return;
}

int *QuickSort(int ary[], int n)
{


_QuickSort(ary, 0, n - 1);

return ary;
}

// 未完,待续。。。

交换排序

冒泡排序

算法描述

假设数组ary长度为N,i=0:N-1,j=N-1:i+1,如果ary[j-1] > ary[j],则swap(ary[j - 1], ary[j]);

算法

算法实现

鸡尾酒排序

鸡尾酒排序是冒泡排序的一个变种,冒泡排序是一个方向从头排到尾,鸡尾酒排序是双向同时排序。

算法描述

假设数组ary长度为N,无限循环做以下处理,i=0:N-2,如果ary[i]大于ary[i + 1],则swap(ary[i], ary[i + 1]),设置swapped=true,退出i的循环;j=N-2:0,如果ary[j]大于ary[j + 1],则swap(ary[j], ary[j + 1]),设置swapped=true,退出j的循环。如果swapped!=true,说明已经有序,则退出无限循环。

奇偶排序

奇偶排序是一个不断比较左右两个值大小的算法,当左值大于右值时,做交换操作,直到交换不再发生,则排序结束。

算法描述

假设数组ary长度为N,无限循环做以下处理,i=0:1,j=i:N-1;step=2,若ary[j]大于ary[j + 1]则交换两者,swapped赋值为true,当swapped不为true时,退出无限循环。

梳排序

侏儒排序

桶排序

假设数组有N个元素,初始化M(M根据情况决定)个桶,将N个元素根据规则放入M个桶内,并排好序,最后将所有桶合并并排序。

,