以前一直没有好好的看过相关的只是,只是一知半解,之前在百度的时候知道 iframe 中的东西如果 domain 与 iframe 外的不一致,那么就不能用 js 去操作里面的对象。window.open 新开的东西也同样无法操作。但是具体的策略如何就说不清楚了,最近看看了这方面的东西,总结一下。
这部分的知识主要源自于 MDN W3 以及另外一篇不错的文章 Same Origin Policy Part 1: No Peeking。
首先介绍一下两个名词
然后从 SOP 说起,首先看看 MDN 上面的对于 同域 的定义
The same-origin policy restricts how a document or script loaded from one origin can interact with a resource from another origin.
Two pages have the same origin if the protocol, port (if one is specified), and host are the same for both pages.
很简单把,origin 和 domain 的意义是差不多的。在 chrome 的调试工具中可以输入
document.domain
获取。端口与协议的一致性也要考虑在内。
那么,在遇到跨域的问题时浏览器会怎么处理呢?通常会遵循如下的原则:
看到的那篇文章中把这些问题按照 linux 系统中的三个权限对应在 read write execute (rwx)。翻译过来就是
在进一步的阐明如下
任何企图获取异域资源内容的请求多会被禁止
这里所知的读行为其实是一个很宽泛的概念,指的是任何具备读操作潜力的行为。比如,我任何形式的 ajax 操作都可以获取返回的请求内容,获取了内容相当于我是读成功了。那么,由于 SOP 的限制,所有的 ajax 方式的跨域请求都会被禁止。相应的,iframe 以及 window.open 的页面内部的一切都是无法访问的。
写是可以接受的
links, redirects and form sumissions 这些都算是写,其实有些晕,尤其是 Links Redirects 凭什么算是写呢?不知道。我觉得这两个应该算是读的范畴,但是同样的,它们做了页面的跳转,同样是没有办法从链接打开的页面或者是重定向的页面中取回任何资源到原来的页面。唯一可以称作是写的就只有表单提交了,当你提交了表单之后同样是会做跳转,原网页依然会失去控制权。不过允许远程表单提交会带来 CSRF 的问题。
任何的执行都是允许的
在页面可执行的差不多就是两种文件 css 以及 js。从任何域拿到的 css 以及 js 都可以在我们自己的网站上正常的使用。
那么,当我们需要做跨域的请求的时候怎么办?
jsonp 是只把你请求的资源做成一个可执行的资源,把读转化为执行。比如我要请求一个 json
{"name": "aisensiy", "age": 24}
服务端把这个结果做成这样
callbackFunction({"name": "aisensiy", "age": 24});
那么,你只需要把返回的结果作为一个 js 加载到页面中,想要的结果就有了。
刚刚看到一个大神的文章,https://ruby-china.org/topics/7598
这里需要说明的一点是,设置document.domain这种跨子域通讯的方式,不是说子域与子域之间可以发送AJAX请求,而是利用子域与子域的iframe之间可以传送数据这一特性完成跨域通讯。
在 MDN 有关 SOP 的文章中说到可以通过修改 document.domain 的方式可以打通各个子域名。例如一个页面 A 的 domain 为 'a.company.com',通过
document.domain = 'company.com';
可以访问 company.com 域下的内容。
我刚才做了尝试,失败了。ajax 请求总的 Origin 并没有变化,所以依然报错。而且通过修改 ajax request header 中的 origin 也是不允许的。那么,我认为这种方式应该是行不通了,我不知道为神马文档没有更新,也可能是我尝试的方式有误?
其实大多数浏览器与服务器已经支持了一套允许跨域访问的标准,Cross-Origin Resource Sharing。服务器可以通过设置HTTP头,添加 Access-Control-Allow-Origin 字段来告诉浏览器什么样子的跨域请求是允许的。 比如
Access-Control-Allow-Origin:*
表示任何域的请求都是可以被接受的。而
Access-Control-Allow-Origin:https://example.com
表明只有从 example.com 的请求是被允许的。 我用 node 做了一个简单的测试。开一个 node server,一个在端口 8000。 端口 8000 的程序如下
var http = require('http');
http.createServer(function(req, res) {
res.writeHead(200, {
'ContentType': 'text/plain',
'Access-Control-Allow-Origin': 'https://localhost:3000'});
res.end('Hello node\n');
}).listen(8000);
console.log('Server running...');
在端口 4000 开一个 node server 向 localhost:8000 请求。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert(xhr.status);
}
}
};
xhr.open('GET', 'https://localhost:8000/');
结果如下
修改请求页面的端口到 3000,再次发送请求
请求被接受了。这种跨域的问题通常会在 做前后端分离的系统的时候用得到。比如 我所有的面向后端的请求全部集中在域 api.domain.com 而我的前端页面都在域 web.domain.com 下,那么在 server 端通过简单的添加允许的域就可以解决这个问题。
这里在补充一句,如果是需要允许多个域名的访问应该怎么做呢?我搜了下,发现一个 access-control-allow-origin-multiple-origin-domains 说明了这个问题:如果是多个域名的话,就需要server自己处理,如果发现请求在允许的列表中,就在 http 响应头中 Access-Control-Allow-Origin 字段设为这个域。
距离上一篇有关这本书的读书笔记已经有相当长的时间了(大约有两年)。两年过去了,我由一个宿舍换到了另外一个宿舍,还是在过着自己相对封闭的学习生活,虽然感觉不是最优状态,但是起码还不错吧。
这次写读书笔记的时候,其实整本书都已经看完了。这次是托豆瓣阅读的福,我可以在 touch 上面看完了中文版。可能是由于我一直对译本有歧视,虽然看的很快,但是不会视书中文字如真理,还会带着怀疑去翻阅原著,不过事实证明,这本书的翻译还是很不错的,看起来整体来说也比较的顺畅。
当项目由一个人变成两个人的时候,项目就会有所谓的沟通的成本了(这个说法应该是在人月神话里面知道的):首先,需要向新加入的人说清楚要做的东西是什么,为神马要做这个东西,以及怎么做的,现在处于什么状况了,然后你要做什么事情。随着人员的增加,这种说明以及规约的打成,方向的指定都便成了越发复杂的东西了。为了避免重复的工作或者是错误的理解,及时的不断的沟通是非常有必要的。书中讲解了一些策略,可以帮助团队更好的处理这些事情。
沟通是为了更好的工作,不能舍本逐末,让沟通花费了工作的时间。站着开会差不多就是为了让大家觉得不自在,赶紧开完走人,避免浪费时间。
To help keep the meeting focused, everyone should limit their contribution to answering these three questions:
• What did I achieve yesterday?
• What am I planning to do today? • What’s in my way?
Each participant is given only a short amount of time to talk (about two minutes). You may want to have a timer to help those of us who have the tendency to ramble. **If you want to discuss something in greater detail, then get together with the appropriate people after the stand-up meeting **(it’s OK to say, “I need to talk to Fred and Wilma about the database” during the meeting; just don’t start going into the details).
花些时间彼此了解进度一直遇到的难题,如果会上恰好有人在你越到的问题方面有研究那太好了,你可以在会后请教他可以快速的解决问题。注意,这里要强调把那些具体而细节的东西在会后单独搞定,拖累不相干认识开会是很多会议无聊的关键所在。
We mentioned Aristotle’s quote earlier: “It is the mark of an educated mind to be able to entertain a hought without accepting it.” You are entertaining thoughts and perspectives of others, and in the process you broaden yours.
很多 lead 都不懂这个,让下属变成按照他们意愿行事的工具,这样是可悲的。更可悲的是很多人就这么听之任之。每次这个世界陷入混乱都是因为太多没有个人主见的人对那些疯子听之任之。事实上,作为一个独立的思考体,每个人都有自己的意愿和偏好,虽然需要达到意见的一致,但是起码的看楼歪了赶紧叫停的能力是要有的吧。发挥团队的优势一方面是要鼓励各种看法,另一方面是队友们要多多提出自己的想法,不能因为一次的受挫就放弃,这也是自己提升在团队中的作用的一个好方法。
书中是有很多好的方法可以提升沟通的效果,但是需要一个比较根本的东西,就是对所做事情的认可。找到比较合适的人最关键。沟通不畅,项目受阻很多时候都是一些乌七八糟说不清楚的原因,彼此有隔阂神马的都不好摆到台面上。尤其是在中国,一个人一条龙,一群人一条虫,精明不用在正路上,人多嘴杂,人多事儿多,乌烟瘴气神马的就来了。
当忽悠一个人加入你的团队的时候,需要告诉他你做的东西是什么,以后什么前景,最终可以有什么回报等等等等。如果一切顺利的话,太好了,这很有可能是个好队友。但是如果他满怀质疑的听完之后,看似波澜不惊的背后有一万只羊驼在奔腾,那以后的分歧(或者说根源在分歧的问题)就会以各种形式接踵踏来。当然,由一个人变成两个人其实出现这种事情的概率比较小,因为他如果不喜欢你的东西,完全可以拒绝,处于最初阶段的东西大家都是因为喜欢做才在一起的,不喜欢完全可以不加入呀。但是如果你是一家公司,邀请一位新的同事加盟,那么他可能考虑种种因素最后决定即便是不喜欢你(们)做的东西,但是也希望在这里干一段时间。那么,坑爹的事情可能就开始了。干活的人不赞同操蛋的指挥,指挥的人容忍着操蛋的执行,进度变慢,环境变差,梦想破灭神马的都不好说。那种齐心协力的感觉是一种势,如果大势已去,那很有可能就无力回天了。
我一直都比较反对目前很多公司的那种面试形式:彼此素未平生,凭很短时间的了解,然后就决定他是否要来这家公司。这实在是太不靠谱了,而且有些面试的内容也确实无聊,须知路遥知马力日久见人心。虽然话是这么说的,但是人家确实不能说花个一年半载的去面试一个人。我比较喜欢的形式是先不要太高的要求可以让你去一家公司实习,然后根据他几个月来的表现来判定这个人的水平。(但是,悲剧的是一些公司把实习生变成了廉价劳动力,不论表现如何从来就没有正式的打算让人家转正的意愿。)我觉得好的团队未必是说要找非常厉害的人,应该是找志同道合的人。大家彼此说的来,项目的问题容易沟通,才是关键。对于技术的把握,并不是关键因素。
不过,我还有更极端的想法。目前的基础设施越发的发达,依靠很多人才能做一件事情的时代慢慢会过去,看看 kickstarter 上面的项目,很多独立的团队已经在做过去庞大的公司才能做的事情。依赖更清晰的分工以及社会化生产,设计芯片的人只需要做设计,而把生产交给别人。构建应用的人依赖 GAE 这样的平台较少设备维护以及基础设施维护的成本,更专注于自己的产品(PaaS正能量:6人团队,仅1人全职后端 支撑6000万用户,刚刚看到一篇应景的文章)。人越多,沟通成本越大,酱油出现的概率越多,项目的发展越发难以估计。控制团队的规模并让自己保持团队的成长,没准儿是未来的趋势。
得出的结论很简单:如果一个 200人的项目中,有 25 个最能干和最有开发经验的项目经理,那么开除剩下的 175 名程序员,让项目经理来编程开发。 --人月神话
要的就是这种效果。
一直实用 hash 这种结构,原理是知道的,具体的算法也了解,可是学完数据结构之后就差不多再也没有实现过这个数据结构了。借前一阵子接触 bloom filter 的机会在整理一下有关 hash 的内容。
hash 是将内容编码然后存储在 array 结构中,以便于在 O(1) 复杂度内获取的数据结构。其关键部分在于如何生成一个足够随机的编码以及如何解决冲突。解决冲突虽然有很多方法,但是我觉得链接法应该是最为广泛是实用的方法吧,概念简单,便于接受。至于如何生成 hash 的 哈希函数就是五花八门了。这个应该根据对于冲突的敏感程度以及算法的复杂度综合考虑比较好吧。不过 MD5 应该算是个很不错的算法了,而且是比较通用的。这里我介绍下一个字符串生成哈希的函数,EFLHash。
google 一下,会发现相关内容还挺多的,在很多的技术博客中都出现了。不过仔细一看会发现大家差不多都是抄来抄去的状态,厚道的会说是转的,不厚道的一字不提。code 直接粘贴估计自己都不知道贴的是什么,一些 code 有错误就都错的一样。这样的技术博客内容即便再多也意义不大,纯粹费电,博客的第一读者是自己,直接 copy 是自欺欺人。
看了一些哈希函数的写法,大概的意思差不多就是尽量把所有信息都考虑在内以尽量减小相似内容的冲突。比如 'abc' 与 'abd',如果仅仅考虑前两个字母而不考虑第三个,那么冲突的可能性会大很多。在算法导论中提到对于字符串这样的内容,可以把它看作是一个超大的数字,然后以对数字的方式根据 hash 桶的个数计算其所在的桶。那么 EFLHash 的思想也是类似的。在下面的代码中,我会给予注释表述其思想。
int ELFHash(char* str, int len) {
unsigned int hash = 0;
unsigned int x = 0;
int i = 0;
for (i = 0; i < len; i++) {
/* 这就相当于在构造一个 16 进制的大整数,每次加新的数字前进位 */
hash = (hash << 4) + str[i];
/* 但是一个整型最多就 32 位,如果再往左进位,高位就会丢失。那么,
就要考虑把高位的数字与低位的做异或运算保存下来 */
if ((x = hash & 0xf0000000L) != 0) {
hash ^= x >> 24;
/* 既然高位数字以及放在低位异或了,那么就可以把高位制空 */
hash &= ~x;
}
}
/* 最后为了返回给有符号整数,把最高位制零,如果你返回的是无符号数,
那就忽略这行 */
hash = hash & 0x7fffffffL;
return hash;
}
我觉得只要这种思想掌握了,那就不会觉得这种代码很诡异了,可以自己做一些修改,以适应各种场景。
然后,核心的算法有了,那么基于链接法解决冲突的 hash 数据结构就可以有了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define N 100
#define LEN 39
int ELFHash(char* str, int len) {
unsigned int hash = 0;
unsigned int x = 0;
int i = 0;
for (i = 0; i < len; i++) {
hash = (hash << 4) + str[i];
if ((x = hash & 0xf0000000L) != 0) {
hash ^= x >> 24;
hash &= ~x;
}
}
hash = hash & 0x7fffffffL;
return hash;
}
/* 这是我们的核心数据结构,其实就是链表的节点 */
typedef struct node {
char data[N];
struct node *next;
} node;
/* 初始化一个节点,设置字符串为空,next 指针指向 NULL */
void node_init(node *point) {
point->data[0] = '\0';
point->next = NULL;
}
/* 创建一个新的节点并初始化 */
node *node_create() {
node *point = (node*)malloc(sizeof(node));
node_init(point);
return point;
}
node* hash_find(char *str, node **root);
node** hash_create();
node* hash_add(char *str, node **root);
int hash_delete(char *str, node **root);
/* 创建一个新的 hash 数据结构,桶的个数为 LEN */
node** hash_create() {
int i;
node **root = (node**)malloc(LEN * sizeof(node*));
for (i = 0; i < LEN; i++) {
root[i] = NULL;
}
return root;
}
/* 添加一个新的元素 */
node* hash_add(char *str, node **root) {
int hash, index;
node *current, *tmp, *base, *prev;
/* 首先检查这个元素是否已经存在了,如果存在就不必再次添加 */
if (hash_find(str, root)) return;
hash = ELFHash(str, strlen(str));
index = hash % LEN;
prev = current = root[index];
/* 冲突总会有的,要把冲突的情况考虑在内 */
while (current) {
prev = current;
current = current->next;
}
tmp = node_create();
strcpy(tmp->data, str);
if (!prev) {
root[index] = tmp;
} else {
prev->next = tmp;
}
return tmp;
}
/* 在 hash 结构中找一个元素是否存在 */
node* hash_find(char *str, node **root) {
int hash, index;
node *current_node;
hash = ELFHash(str, strlen(str));
index = hash % LEN;
current_node = root[index];
while (current_node) {
if (strcmp(current_node->data, str) == 0) {
return current_node;
}
}
return NULL;
}
/* 删除已有的元素 */
int hash_delete(char *str, node **root) {
int hash, index;
node *current_node, *prev, *tmp;
hash = ELFHash(str, strlen(str));
index = hash % LEN;
current_node = prev = root[index];
while (current_node) {
if (strcmp(current_node->data, str) == 0) {
if (current_node == root[index]) {
free(current_node);
root[index] = NULL;
} else {
prev->next = current_node->next;
free(current_node);
}
return 1;
}
prev = current_node;
current_node = current_node->next;
}
return 0;
}
/* 一点点测试 */
int main(int argc, const char *argv[]) {
char *test1 = "test";
char *test2 = "test2";
node *result;
int r;
node **root = hash_create();
hash_add(test1, root);
result = hash_find(test2, root);
assert(!result);
result = hash_find(test1, root);
assert(result);
hash_add(test2, root);
result = hash_find(test2, root);
assert(result);
hash_add(test1, root);
r = hash_delete(test1, root);
assert(r == 1);
return 0;
}
hash 结构的操作就是那么结构,添加,查找,删除,如果考虑的再多一点,应该有一个销毁整个结构的函数,这里就不写了。
在你需要频繁查找某个元素是否在一个集合的时候,hash 是一个比较理想的结构。因为它的理想查找复杂度是 O(1)。但是它有一个问题,就是 hash 内部实际上还是存储了整个数据内容(比如上面的代码里面,依然把字符串保存了下来),那么这就会导致比较大的内存开销。想想一下,如果你的这个集合很大,比如有 1 百万,每个字符串占20个字节,那么你就需要 2O M 空间了,而且,其实 hash 为了避免冲突,不希望桶的个数太接近实际数据的个数。其空间可能会开到 30M 甚至 40M。其实这已经是很大的开销了(我不想举例子说有什么一个亿,来说明这个开销不能承受,我只是觉得应该实际一点,一百万我觉得还是可能有的,比如你做个爬取数据的程序,而你的VPS只有512M内存,你又有其它的程序在跑,你不想因为一个爬虫就把你VPS的内存都吃了)。
那么,这里 bloom filter 来了。这个数据结构的思想也非常的简单。它想用hash值来代表一个数据,然后只保存这个hash值。我通过hash值就可以判断这个元素到底是有还是没有,那不就省空间了么? 但是这里问题也很明显:一个hash值很容易出现冲突啊,如果冲突了,那我岂不是很容易误伤其他没有在这个集合的元素?那我可以弄多个哈希函数啊,我弄一个哈希函数冲突的概率比较大,那我弄八个呢?冲突的概率是不是小的几乎可以忽略了呢。这就是 bloom filter 的核心想法:用多个哈希结果代表这个元素存储下来。每次通过检查这些哈希函数的结果判断该元素是否属于这个集合。
下面大概的描述一下 bloom filter 的具体结构。bloom filter 是一个比较大的,连续的内存空间。假设我的集合有 1000000 个元素,我建立一个 1000000 字节(1M)的空间。把它作为一个一百万位的 0 1 bit 空间,并在初始化的时候全部制零。同时,我准备 8 个可以把元素映射到 0 - 999999 的哈希函数。每来一个新的元素,我用这 8 个哈希函数计算出 8 个哈希值,并把这 8 个哈希值对应的 bit 位制为 1。那么,当判断一个元素是否存在的时候,需要保证这 8 个比特位全部为 1。
如此一来,我们就不需要保存原始数据了,存储空间由 20M 变成了 1M,还是很划算的。
我看别人的技术博客也差不多是先看别人的代码,如果代码看懂了就不看大段大段的文字了,下面我依然把自己写的代码贴出来,希望可以避免看到的人再看我写的拙急的文字...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define CHAR_BIT 8
#define WORD_SIZE 32
#define TRUE 1
#define FALSE 0
#define SETBIT(a, n) (a[n/CHAR_BIT] |= (1<<(n%CHAR_BIT)))
#define GETBIT(a, n) (a[n/CHAR_BIT] & (1<<(n%CHAR_BIT)))
typedef unsigned int (*hashfunc)(const char *);
typedef struct bloom {
int char_size;
char *container;
int nearest_bit;
int func_size;
int *seeds; /* 注意,我这里给的不是一堆hash函数,而已hash函数里面的一个参数 */
} Bloom;
Bloom *bloom_create(int char_size, int func_size);
void *bloom_add(const char *str, Bloom* bloom);
int bloom_find(const char *str, Bloom* bloom);
/* 生成多个hash值的关键在于传递给这个函数的 k 值的区别。这个方法是算法导论里面的乘法
生成 hash 的部分提到的,去那里看比较靠谱,我说不清楚啊 o_o */
unsigned int multi_hash(int a, int k, int r, int bit_size) {
return ((unsigned int)(a * k) >> (WORD_SIZE - r)) % bit_size;
}
int nearest_bitsize(int size) {
int i = 1, n = 2;
while (size > n) {
i++;
n *= 2;
}
return i;
}
int ELFHash(const char* str) {
unsigned int hash = 0;
unsigned int x = 0;
int i = 0;
int len = strlen(str);
for (i = 0; i < len; i++) {
hash = (hash << 4) + str[i]; if ((x = hash & 0xf0000000L) != 0) {
hash ^= x >> 24;
hash &= ~x;
}
}
hash = hash & 0x7fffffffL;
return hash;
}
/* >min < max */
int randint_in_range(int min, int max) {
return rand() % (max - min + 1) + min + 1;
}
int *generate_hash_seed(int size, int min, int max) {
int i, seed = time(NULL);
int *seeds = (int*)malloc(size * sizeof(int));
for (i = 0; i <size; i++) {
srand(seed);
seeds[i] = randint_in_range(min, max);
seed = seeds[i];
}
return seeds;
}
int main(int argc, const char *argv[]) {
Bloom *bloom = bloom_create(400, 8);
bloom_add("aisensiy", bloom);
printf("%d\n", bloom_find("aisensiy", bloom));
printf("%d\n", bloom_find("aisensiy1", bloom));
return 0;
}
Bloom *bloom_create(int char_size, int func_size) {
int i, min, max, bit_size;
Bloom *bloom = (Bloom*)malloc(sizeof(Bloom));
bloom->char_size = char_size;
bloom->container = (char*)malloc(sizeof(char) * char_size);
/* set all char to 0 */
for (i = 0; i < char_size; i++) {
bloom->container[i] = 0;
}
bit_size = char_size * CHAR_BIT;
bloom->nearest_bit = nearest_bitsize(bit_size);
/* 这里的最大值与最小值也是与生成 hash 函数有关,建议去看算法导论 */
max = 1<<(WORD_SIZE - 1);
min = 1<<(WORD_SIZE - bloom->nearest_bit);
bloom->func_size = func_size;
bloom->seeds = generate_hash_seed(func_size, min, max);
return bloom;
}
void *bloom_add(const char *str, Bloom* bloom) {
int hash, i, position, bit_size;
hash = ELFHash(str);
bit_size = bloom->char_size * CHAR_BIT;
/* 用这 N 个哈希函数生成哈希值 */
for (i = 0; i < bloom->func_size; i++) {
position = multi_hash(bloom->seeds[i], hash, bloom->nearest_bit, bit_size);
printf("position: %d\n", position);
SETBIT(bloom->container, position);
}
printf("position end\n");
}
int bloom_find(const char *str, Bloom* bloom) {
int hash, i, position, bit_size;
hash = ELFHash(str);
bit_size = bloom->char_size * CHAR_BIT;
/* 通过检查这 N 个哈希值来判断元素是否存在 */
for (i = 0; i < bloom->func_size; i++) {
position = multi_hash(bloom->seeds[i], hash, bloom->nearest_bit, bit_size);
printf("position: %d\n", position);
if (GETBIT(bloom->container, position) == FALSE) {
return FALSE;
}
}
printf("position end\n");
return TRUE;
}
这种算法类的东西,我描述起来很是拙急啊。感觉说的不够明白 o_o。这里给点参考吧。