ajax跨域的一些理解和解决方案

1 什么是ajax跨域

ajax 的出现使得网页可以通过在后台与服务器进行少量数据交换,实现网页的局部刷新。但是出于安全的考虑,ajax不允许跨域通信。如果尝试从不同的域请求数据,就会出现错误。如果能控制数据驻留的远程服务器并且每个请求都前往同一域,就可以避免这些安全错误。

2 为什么ajax不允许跨域通信

上文说ajax不允许跨域通信是因为安全的问题。举个例子,你在浏览器中访问银行页面,www.bank.com,这时bank.com的cookie中会保存你的个人信息。然后你又访问一个恶意的网站,这个网站中有一段js代码会去请求www.bank.com。如果允许跨域,代码执行时,会带着bank.com域的cookie去访问www.bank.com,然后www.bank.com通过cookie验证通过,那么你的银行账号就危险了。因此ajax不允许跨域通信

3 一些解决方案

  • 服务端设置Access-Control-Allow-Origin
    这种方式只要服务端把response的header头中设置Access-Control-Allow-Origin为制定可请求当前域名下数据的域名即可。
  • 服务器中转
    让 Web 页面向它源自的 Web 服务器请求数据,并且让 Web 服务器像代理一样将请求转发给真正的第三方服务器。
  • 采用jsonp
    在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但img、iframe、script等标签是个例外,这些标签可以通过src属性请求到其他服务器上的数据。利用script标签的开放策略,我们可以实现跨域请求数据,当然,也需要服务端的配合。当我们正常地请求一个JSON数据的时候,服务端返回的是一串JSON类型的数据,而我们使用JSONP模式来请求数据的时候,服务端返回的是一段可执行的JavaScript代码。
    例如,请求http://www.a.com/user?id=123,获取数据:{“id”: 123, “name” : “张三”, “age”: 17}。
    则前端代码:

    1
    <script type="text/javascript" src="http://www.a.com/user?id=123?callback=foo"></script>

    服务端代码:

    1
    2
    3
    4
    5
    $json = json_encode(array("id" => 123, "name" => "张三", "age" => 17));
    if(isset($_GET['callback'])){
    $json = 'try{' . $_GET['callback'] . '(' . $json . ')}catch(e){}';
    }
    echo $json;

    那么就可以使用foo函数处理返回的数据了。
    使用ajax方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //方法1
    $.ajax({
    dataType: 'jsonp',
    url: 'http://www.a.com/user?id=123',
    success: function(data){
    //处理data数据
    }
    });

    //方法2
    $.getJSON('http://www.a.com/user?id=123&callback=?', function(data){
    //处理data数据
    });

    //方法3
    function foo(data){
    //处理data数据
    }
    $.getScript('http://www.a.com/user?id=123&callback=foo');

事件反应堆

最近在网上看到一个简易事件反应堆的实现:
https://github.com/song0071000/Reactor_Implemention 学习了下。

  • 反应堆的作用
    在编写网络程序时,需要处理网络的读写事件、定时器事件等。网络事件的收集通常采用select、epoll等IO多路复用技术,定时器通常采用小顶堆实现。这些通常都是不变的,变化的是事件发生后的逻辑处理。反应堆的作用就是把事件的收集和事件发生后的处理分开,使得开发人员只需关注后者。
    下图是一个反应堆的设计,图中的左半部分是反应堆,右半部分是事务处理。这个反应堆可以处理网络和定时器两类事件。
    其中,EventHandler类是事件处理的基类,它有读、写、处理错误等函数。我们只需继承这个类,然后完成读写逻辑,然后将其注册到反应堆中即可。
  • 反应堆的实现
    其中,Reactor类是反应堆的基类,它有注册事件、删除事件、处理事件等功能。在它依赖的类中,有个map<handle_t, EventHandler *> m_handlers成员变量。EventHandler事件注册时,就放在这个变量中,事件删除时,就从这个变量中移除。
    比如,我们要监听一个socket,继承EventHandler,然后将socket的handle和事件类注册到反应堆中。
    注册是通过Reactor类型的全局变量g_reactor调用RegisterHandler(EventHandler * handler, event_t evt)函数。由于底层是采用select或epoll,注册就是采用select或epoll监听这些socket。当有事件发生时,调用EventHandler的事件处理函数处理读写。
  • 定时器
    定时器的实现是通过一个小顶堆time_heap,然后在反应堆中有个成员变量time_heap* m_eventtimer。这个类有添加定时器、移除定时器、处理定时器等功能。添加定时器就是在小顶堆中添加一个heap_timer事件,这个事件中有过期时间和回调函数。
    事件反应堆

反应堆处理事件的过程的示意代码

1
2
3
4
5
6
7
8
9
10
11
while(1)
{
// 获取定时器最小过期时间
timeout = get_min_expire_from(time_heap);
// 从监听的sockets中获取活跃的socket
ready_sockets = select_from(m_handlers, timeout);
// 处理socket的网络事件
HandleEvents(ready_sockers);
// 处理过期的定时事件
HandleTimes(time_heap);
}

由此可见,事件反应堆将事件的处理从一个主动过程变成一个被动过程:我们只需将想要处理的事件注册到反应堆上,事件发生时由反应堆去处理,而不是我们主动处理。

Linux下,把前台程序放到后台运行

平常在Linux的命令行中,运行一个服务或一个长时间处理的脚本。下班的时候,需要关机,但我们又不想让程序停止,我们可以通过下面命令把程序放到后台运行:

1
2
$ ctrl+z
$ bg

其中ctrl+z是将一个前台正在执行的命令放到后台,并处于暂停状态,不可执行。bg是将一个后台暂停的命令,在后台继续执行。程序的运行状态如下:

1
2
3
4
5
6
7
8
9
10
11
wangzhilong@in17-164^:~/tmp$ python test_flask.py
* Running on http://0.0.0.0:8786/ (Press CTRL+C to quit)
^Z
[1]+ Stopped python test_flask.py

wangzhilong@in17-164^:~/tmp$ jobs
[1]+ Stopped python test_flask.py
wangzhilong@in17-164^:~/tmp$ bg
[1]+ python test_flask.py &
wangzhilong@in17-164^:~/tmp$ jobs
[1]+ Running python test_flask.py &


一些相关命令的说明

  • jobs
    查看当前在后台运行的命令。jobs -l可以列出命令的PID,jobs的状态可以是running, stopped, Terminated。
  • bg
    将一个后台暂停的命令,变成后台执行。如果后台有多个暂停的命令,通过bg %num选择需要执行的暂停命令,num是暂停命令的序号。如果不指定序号,则执行最后一个暂停的命令,也就是num最大的。
  • fg
    将后台中的命令,调到前台执行,如果后台有多个命令,通过bg %num选择需要调到前台的命令。
  • &
    加在一个命令的最后,可以把这个命令放到后台执行。
  • nohup
    如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令。该命令可以在你退出帐户/关闭终端之后继续运行相应的进程。

我们平时在个人账户下运行程序时,使用命令nohup cmd &。可以将程序放在后台运行,而且自己的账户退出时,程序还会继续执行。

dup/dup2函数用法

系统调用dup和dup2能够复制文件描述符。dup返回新的文件描述符(没有用的文件描述符最小的编号)。dup2可以让用户指定返回的文件描述符的值,它通常用来重新打开或者重定向一个文件描述符。函数原型如下:

1
2
3
#include <unsitd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);

dup和dup2都是返回新的文件描述符,如果出错,返回-1.在dup2中,如果newfd文件已经打开,关闭打开的文件,重定向oldfd所指的文件。新老描述符共享文件的偏移量(位置)、标志和锁,但是不共享close-on-exec标志。


每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将视为一个矢量,每个描述符占用一项。与每个文件描述符项关联的是:

  • 文件描述符标志
  • 指向一个文件表项的指针

内核为所有打开文件维持一张文件表。每个文件表包含:

  • 文件状态标志(读、写、增写、同步、非阻塞等)
  • 当前文件位移量
  • 指向该文件v节点表项的指针

下面是一段比较简单的程序:创建文件,并将标准输出重定向到文件。

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
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{

int fd;
//(1)打开文件
fd = open("my.file", O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if (fd == -1)
{
perror("\nERROR:fail to create my.file.");
return -1;
}
//(2)文件重定向
if (dup2(fd, STDOUT_FILENO) == -1)
{
perror("\nERROR: fail to redirect std output.");
return -1;
}
//(3)删除oldfd
if (close(fd) == -1)
{
perror("\nERROR: fail to close my.file.");
return -1;
}
//(4)标准输出
if (write(STDOUT_FILENO, "HELLO", 5) == -1)
{
perror("\nERROR: fail to write to file.");
return -1;
}
return 0;
}

  1. open函数打开文件,文件不存在时,根据给定的参数进行文件创建,创建以后再打开。并在系统文件表内创建一个条目。
    这里写图片描述
  2. dup2函数将标准输出定向到刚打开的fd中。
    这里写图片描述
  3. close()后,删除掉fd。
    这里写图片描述
    经过以上三步后,进行标准输出时,会被输出到myfile文件中。

python单例装饰器

单例是设计结构中一种常见的模式。在工程中,确保一个实例、确保日志被初始化一次等,就可以使用单例。下面是使用python的装饰器实现的一个单例模式。可以装饰类和函数。

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
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
singleton decorator

usage:

decorete function: this function just be executed once.
@singlenton
def fun():
# do something
return

decorate class: this class just have one instance.
@singleton
class MyClass(object):
def __init__():
pass
"""

def singleton(cls):
instances = {}
def _singleton(*args, **kw):
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return _singleton