存档

‘programming’ 分类的存档

python Route 简单使用笔记

2014年10月15日 没有评论

Routes is a Python re-implementation of the Rails routes system for mapping URLs to application actions, and conversely to generate URLs. Routes makes it easy to create pretty and concise URLs that are RESTful with little effort.

Routes 其实就是 Python 版本的 Rails 的 routes. 它用来将用户的不同 URLs 自动匹配 到不同的应用上, 对于开发 RESTful 的 API 和其他 web 的应用非常方便.

例如下面根据官网上的例子修改的, 简单明了:

>>> from routes import Mapper
>>> map = Mapper()
>>> map.connect(None, "/error/{action}/{id}", controller="error")
>>> map.connect("home", "/", controller="main", action="index")
>>> print map
Route name Methods Path                
                   /error/{action}/{id}
home               /                   
>>> print map.match('/error/myapp/4')
{'action': u'myapp', 'controller': u'error', 'id': u'4'}
>>> print map.match('/')
{'action': u'index', 'controller': u'main'}

简单来讲, routes 的使用有以下简单的几步:

  1. 创建一个用来响应用户请求的控制器 C
  2. 创建一个 Mapper m
  3. 用 m.connect(或者 resource) 连接需要解析的 URLs 到 Controller 上
  4. 使用 routes.middleware.RoutesMiddleware 自动化 URLs 到 Controller 的处理

下面是一个例子

import webob.dec
import eventlet
from eventlet import wsgi, listen
from routes import Mapper, middleware
 
 
# 控制器
# 只有两个动作: index 和 add
class controller(object):
    def index(self):
        return "do index()"
 
    def add(self):
        return "do show()"
 
 
# WSGI App, 详情请参考我前面的文章 python paste.deploy 探索
class App(object):
    def __init__(self):
        # 上面提到的 1, 2, 3, 4
        self.controller = controller()
        m = Mapper()
        m.connect('blog', '/blog/{action}/{id}', controller=controller,
                  conditions={'method': ['GET']})
        self.router = middleware.RoutesMiddleware(self.dispatch, m)
 
    @webob.dec.wsgify
    def dispatch(self, req):
        # RoutesMiddleware 会根据接收到的 url, 自动调用 map.match(), 做路由匹配,
        # 然后调用第一个参数, 这里即是 self.dispatch()
        match = req.environ['wsgiorg.routing_args'][1]
        if not match:
            return 'error url: %s' % req.environ['PATH_INFO']
 
        # 根据用户的请求动作, 调用相应的 action 处理函数(index/add)
        action = match['action']
        if hasattr(self.controller, action):
            func = getattr(self.controller, action)
            ret = func()
            return ret
        else:
            return "has no action:%s" % action
 
    @webob.dec.wsgify
    def __call__(self, req):
        return self.router
 
if __name__ == '__main__':
    socket = listen(('0.0.0.0', 8000))
 
    server = eventlet.spawn(wsgi.server, socket, App())
    server.wait()

Table of Contents

其它

关于使用 Mapper.resource() 来批量创建 RESTful API, 这里不介绍了, 详情请到 官网 查看

分类: programming, python 标签: ,

python paste.deploy 探索

2014年10月15日 没有评论

简介

Paste Deployment 是一个用来查找和配置 WSGI 应用的系统. 只需要利用它提供的一个 简单的入口 loadapp, 就可以从配置文件或者 python EGG 里加载 WSGI 应用. 用户 所要做的, 仅仅是调用 loadapp 接口, 不需要暴露程序内部的实现细节.

配置文件说明

配置文件是 INI 格式 的, 被分为不同的段, 每个段的段名由 [类型:名字] 组成, 类型包括以下几种:

app

app 接受的参数(environ, start_response), app 需要完成的任务是响应 envrion 中的请求, 准备好响应头和消息体, 然后交给 start_response 处理, 并返回响应消息体. 这个比较好理解, 就是 WSGI 直接调用 app 指定的对象.

filter

filter 是过滤器, 和 python 中的装饰器是一个路子, 它接受一个 app 对象作为参数, 返回一个封装后的 app. 在一般的应用中, 可能在运行到最后一个 app 时候, 前面需要先 处理其他 filter, 如果在处理某一个 filter 的时候, 某些判断条件未通过, filter 有权直接返回, 不交由下面的模块继续处理, 比如认证未通过等.

filter-app

也是一个过滤器, 在某个应用只需要一个过滤器的时候, 一般用该类型, 它需要一个 next 字段指定这个 filter 应用到哪个应用上.

pipeline

如果需要使用多个 filter 过滤一个应用, 需要使用 pipeline 的方式, 他的配置就是一个 名为 pipeline 的 key, value 是以多个 filter和最后的一个应用结尾的列表: 如

[pipeline:main]
pipeline = filter1 filter2 filter3 app

composite

用来完成将将一个请求调度定向(dispatched)到多个(多种)应用上. 比如应用有 v1, v2 的版本, 就可以用 composite 来做调度. composite 其实像是 app, 但是实际上是由 多个应用组成. 比如下面是 openstack glance 的使用

[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app

使用示例

app

下面的例子展示了一个只有名为 blog 的app, 配置文件指定了 app_factory 为 Blog.factory, 这个工厂函数会创建一个 Blog 的实例, 当然你需要实现该类的 __call__ 方法, 这样 wsgi 就会在适当的使用调用你完成你想要做的.

# file: app.ini
[app:blog]
paste.app_factory = app:Blog.factory
# use = call:app:blog_app
#!/usr/bin/python
#encoding: utf-8
 
# file: app.py
 
import os
import eventlet
from eventlet import wsgi, listen
from paste import deploy
 
cfg_file = 'app.ini'
 
 
class Blog(object):
    def __init__(self):
        pass
 
    # 工厂函数, ini 配置文件中指定的值会找到这个函数来创建该 app 的实例
    @classmethod
    def factory(cls, global_conf, **local_conf):
        return cls()
 
    # python 的对象调用机制, 简单的来讲就是 python 中对象分为可调用的和不可调用的
    # 有 __call__ 方法的可以调用, 这样 ini 中指定的配置项的值调用的时候就直接调用到
    # 这个函数了
    def __call__(self, environ, start_response):
        # start_response 把用户传递的 HTTP status 和 headers 记录然后返回
        start_response('200 OK', {("Content-type", "text/plain")})
        return 'welcome to my blog\n'
blog_app = Blog.factory
 
if __name__ == '__main__':
    socket = listen(('0.0.0.0', 8000))
    # paste 提供的入口函数
    app = deploy.loadapp('config:%s' % os.path.abspath(cfg_file), 'blog')
    server = eventlet.spawn(wsgi.server, socket, app)
    server.wait()

一个复杂的例子

下面是一个复杂的例子, 先来看他的 ini 文件:

[composite:wiki]
use = egg:Paste#urlmap
/: home
/v1: wikiv1
 
[filter-app:home]
paste.filter_factory = app:WikiFilter.factory
next = homeapp
 
[app:homeapp]
paste.app_factory = app:Wiki.factory
 
 
[pipeline:wikiv1]
pipeline = logip logmethod v1
 
[filter:logip]
paste.filter_factory = app:LogIPFilter.factory
 
[filter:logmethod]
paste.filter_factory = app:LogMethodFilter.factory
 
[app:v1]
paste.app_factory = app:V1.factory

app 名为 wiki, 首先它是一个 composite 的类型, 根据前面说的(egg.Paste 这个是一个 app), 它会根据 url 对应不同的应用, 比如如果是 http://localhost/ 它就到 home, http://localhost/v1, 就到 wikiv1

home 是只有一个 filter 过滤的 app, filter 对应的工厂函数为 Wiki.factory, 它过滤后 在调用 homeapp, 这个 homeapp 就和前面的 blog一样只是一个简单的 app

wikiv1 是一个 pipeline, 它经过 logip(记录对方 IP), logmethod(记录 HTTP medhod) 这两个 filter, 然后到 v1 的 app

相关代码如下:

class Wiki(object):
    def __init__(self):
        pass
 
    @classmethod
    def factory(cls, global_conf, **local_conf):
        return cls()
 
    def __call__(self, environ, start_response):
        start_response('200 OK', {("Content-type", "text/plain")})
        return 'welcome to my wiki\n'
 
 
class Middleware(object):
    def __init__(self, app):
        self.app = app
 
    @classmethod
    def factory(cls, global_conf, **kwargs):
        def filter(app):
            return cls(app)
        return filter
 
 
class WikiFilter(Middleware):
    def __init__(self, app):
        super(WikiFilter, self).__init__(app)
 
    def __call__(self, environ, start_response):
        req = Request(environ)
        if req.method == 'PUT':
            start_response('200 OK', {("Content-type", "text/plain")})
            return 'Bad request\n'
        else:
            return self.app(environ, start_response)
 
 
class LogIPFilter(Middleware):
    def __init__(self, app):
        super(LogIPFilter, self).__init__(app)
 
    def __call__(self, environ, start_response):
        print 'request IP is: %s' % environ['REMOTE_ADDR']
        return self.app(environ, start_response)
 
 
class LogMethodFilter(Middleware):
    def __init__(self, app):
        super(LogMethodFilter, self).__init__(app)
 
    def __call__(self, environ, start_response):
        print 'request method is: %s' % environ['REQUEST_METHOD']
        return self.app(environ, start_response)
 
 
class V1(object):
    @classmethod
    def factory(cls, global_conf, **local_conf):
        return cls()
 
    def __call__(self, environ, start_response):
        start_response('200 OK', {("Content-type", "text/plain")})
        return 'welcome to my V1 wiki\n'

Resources

完整的代码点击 这里, 配置文件请点击 这里

分类: programming, python 标签: ,

在 Django 中处理 HTTP Transfer-Encoding

2014年7月29日 没有评论

什么是 Transfer-Encoding

在使用 HTTP 协议 传送数据时, 有时不能事先确定 body 的长度(比如边压缩边传输的 gzip 报文), 因此无法得到 Content-Length 的值, 所以发送方不能在 header 中指定 Content-Length. 接收方也无法通过 Content-Length 得到报文体的长度. 因此 HTTP/1.1 协议在 header 中引入了 Transfer-Encoding(分块传输编码), 当 Transfer-Encoding 值为 chunked 时, 表明采用 chunked 编码方式来进行报文体的传输. chunked 编码的基本方法是将大块数据分解成多块小数据. 每块都可以自指定长度.

Django 中的问题

目前, django 只支持正常的带 content-length 字段的 HTTP 请求, 如果 HTTP 请求中 有字段, Transfer-Encoding 且值为 chunked, 那么 django 的 view 得到的请求的 HTTP content 为空.

这其实不是 django 的问题, 这是由于 WSGI 的限制.

解决方法

但是如果使用 Apache 的 mod_wsgi 来部署 django 程序. 就可以在程序中的 WSGI 封装中把原始的数据读出来, 然后得到其长度, 并把该长度作为字段 Content-Length 的值传递给 django 程序要使用这种方式, 首先得在 Apache 的配置文件中打开 WSGI 的 Chunked 支持.

WSGIChunkedRequest On

然后在 WSGI 的封装中调用 wsgi.input.read() 读出整个数据, 把该数据的长度赋值给 CONTENT_LENGTH.

#+BEGIN_SRC python
import StringIO
 
django_application = get_wsgi_application()
 
 
def application(environ, start_response):
    if environ.get("mod_wsgi.input_chunked") == "1":
        stream = environ["wsgi.input"]
        data = stream.read()
        environ["CONTENT_LENGTH"] = str(len(data))
        # wsgi.input 已经被消耗了, 重新赋值
        environ["wsgi.input"] = StringIO.StringIO(data)
 
    return django_application(environ, start_response)

遗留问题

  • gzip 的请求需要重新计算 Content-Length
  • 该方法只适用于 embedded 模式, 对 daemon 模式会有问题.

分类: programming 标签: ,

在 python 中动态导入模块

2013年12月23日 没有评论

在模块化编程的时候, 有时候模块的名字和细节, 主模块是不清楚的, 为了实现动态载入 模块(运行时载入模块), 不同的编程语言有不同的处理方式, 如在 C/CPP 中的 dlopen 等.

在 python 中, 也有相应的处理机制, 这些机制可以避免我们在模块化编程的时候, 频繁的修改上层代码, 比如不断的 import 等.

以一个小程序为例, 该程序支持 add, remove 命令:

# foo add some
# foo remove some

为了支持更多的命令, 比较优雅的方式是把每个命令模块化, 在合适的时机导入/注册 这些模块, 而不是在上层接口不断的 import. 我们可以通过 setuptools 的 entry_points 来达到这个目的.

1 首先, 在 setup 中注册插件名称和加载函数, 在 entry_points 中设置插件的组, 和需要注册的插件(名称和load函数)

# @file setup.py
from setuptools import setup
 
setup(
    # Other keywords
    entry_points={
        'foo': [
            'add = add:make',
            'remove = remove:make',
            'update = update:make',
        ],
    }
)

2 分别编写 add.py, remove.py, update.py 这些模块, 在每个模块里面, 分别编写各 自的 load 方法, 方法名要和上面设置的(这里为 make)一致, 例如:

# file add.py
import os
 
 
def make():
    print "register %s" % os.path.splitext(os.path.basename(__file__))[0]

3 通过 pkg_resources, 获取相应的模块名和模块方法

# file foo.py
import pkg_resources
entry_points = [
    (ep.name, ep.load()) for ep in pkg_resources.iter_entry_points('foo')
    ]
for (name, fn) in entry_points:
    fn()

以后如果增加新的命令, 只需要修改 setup.py, 把模块名字和方法注册进去就可以了.

测试一下:

$ python setup.py sdist
$ python foo.py 
register add
register update
register remove

分类: programming 标签: ,

利用 API 获取 CPU 和内存信息

2013年9月1日 没有评论

今天在研究 CPU 的热插拔时,看到了获取系统 CPU 和内存信息的 API, 才发现以前在实现 这些功能的时候去 /proc 获取数据是多么的 ugly.

这些 API 主要是利用了 sysconf 这个 POSIX 的接口, 这个接口可以获取系统运行 时信息, 包括 CPU 信息, 内存信息, 进程可以打开的最大文件句柄数等. 它的声明如下:

long sysconf(int name);

  • _SC_NPROCESSORS_CONF: 获取系统中总的 CPU 数量, 注意这里获取的是所有的 CPU 线程的数量
  • _SC_NPROCESSORS_ONLN: 获取系统中可用的 CPU 数量, 没有被激活的 CPU 则不统计 在内, 例如热添加后还没有激活的.
  • _SC_PHYS_PAGES: 总的物理内存页大小.
  • _SC_AVPHYS_PAGES: 可用的物理内存页大小.

下面是我写的一些 demo 代码(在下面的代码里把 windows 获取这新信息的方法也写出来做参考):

#include<stdio.h>
 
#if defined(_WIN32)
#define _WIN32_WINNT 0x0500
#include <windows.h>
void sysinfo_print()
{
    int cpu_num;
    SYSTEM_INFO si;
    MEMORYSTATUSEX memory;
 
    // 大部分 Windows 系统不支持热添加功能, 所以 online number 没有什么意义.
    GetSystemInfo(&si);
    cpu_num = si.dwNumberOfProcessors;
    printf("The number of processors: %d\n", cpu_num);
 
    memory.dwLength = sizeof(memory);
    GlobalMemoryStatusEx(&memory);
    printf("The memory size: %I64uK\n", memory.ullTotalPhys/1024);
    printf("The free memory size: %I64uK\n", memory.ullAvailPhys/1024);
}
#else
#include<unistd.h>  
#include<errno.h>
#include <string.h>
void sysinfo_print()
{
    int cpu_num, cpu_online_num;
    int mem_size, mem_free_size;
 
    cpu_num = sysconf(_SC_NPROCESSORS_CONF);
    if (cpu_num != -1) {
        printf("The number of processors: %d\n", cpu_num);
    } else {
        printf("Failed to get number of processors: %s\n", strerror(errno));
    }
 
    cpu_online_num = sysconf(_SC_NPROCESSORS_ONLN);
    if (cpu_online_num) {
        printf("The number of online processors: %d\n", cpu_num);
    } else {
        printf("Failed to get number of online processors: %s\n",
               strerror(errno));
    }
 
    // 注意: OSX 不支持下面两个宏.
    mem_size = sysconf(_SC_PHYS_PAGES);
    if (mem_size) {
        printf("The memory size: %dK\n", mem_size * 4);
    } else {
        printf("Failed to get memory size: %s\n", strerror(errno));
    }
 
    mem_free_size = sysconf(_SC_AVPHYS_PAGES);
    if (mem_free_size) {
        printf("The free memory size: %dK\n", mem_free_size * 4);
    } else {
        printf("Failed to get free memory size: %s\n", strerror(errno));
    }
 
}
#endif
 
int main(int argc, char *argv[])
{
    sysinfo_print();
 
    return 0;
}

分类: linux, programming 标签: ,

调试 C 程序的奇淫技巧

2013年1月5日 没有评论

调试时不要开启任何优化选项

如果在编译的时候开启了一些优化选项, 比如 -O2, -O3 什么的, 有的变量/函数会被 gcc 自动优化掉, 比如以下的代码片段:

    for (i = 0; i < msg->num_of_channels; i++) {
        channel_new_t *c;
 
        c = g_new(channel_new_t, 1);
        c->session = g_object_ref(session);
        c->type = msg->channels[i].type;
        c->id = msg->channels[i].id;
        /* no need to explicitely switch to main context, since
           synchronous call is not needed. */
        /* no need to track idle, session is refed */
        g_idle_add((GSourceFunc)_channel_new, c);
    }

当我 break 到这行, 想查看 ichannels[i] 的值时, 开启优化选项和 未开启的区别如下:

// CFLAGS="-g"
1430            c->type = msg->channels[i].type;
(gdb) p i
$9 = <optimized out>
 
// CFLAGS="-g -O0"
1430            c->type = msg->channels[i].type;
(gdb) p i
$1 = 0
(gdb) p msg->channels[0]
$2 = {type = 5 '\005', id = 0 '\000'}

要关闭 gcc 的优化, 通常可以通过以下途径达到:

  • 如果程序的 configure 脚本提供了 “–enable-debug”, 打开它
  • 在正式编译代码的的时候, 定制 CFLAGS 参数, 去掉优化选项(使用 -O0), e.g. make CFLAGS=”-O0 -g”

core dump

默认情况下, 当程序发生 Segmentation fault 时, 内核只是简单的把该进程结束, 不做任何事情, 如果想要保留出错时候的上下文, 可以使用 ulimit -c unlimited 设置 dump core 文件 的大小, 使其不为0(或者用 setrlimit()设置).

这样, 程序 Segmentation fault 的时候, 会在当前目录下(或者 /var/***) 下 生成一个 名为 core(或者 core.*) 的文件, 用 gdb 工具就可以看到程序到底为什么 Segmentation fault 了.

例如下面的程序, 明显的有一个非法内存访问:

#include <stdio.h>
 
int main(int argc, char *argv[])
{
    char *s = NULL;
    sprintf(s, "foo");
    return 0;
}

编译运行后, 会得到一个 名为 core 的文件, 用 gdb 打开, 然后 gdb 会停在程序 出错的地方, 查看 s 的值, 可以看到 s 的值为 0x0, 即传递了一个非法的内存引用给 sprintf() 函数引发了异常.

$ ulimit -c unlimited
$ gcc -o test test.c -g
$ ./test
Segmentation fault (core dumped)
 
$ gdb test core
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0  0x0000000000400512 in main (argc=1, argv=0x7fff7eed3048) at test.c:6
6       sprintf(s, "foo");
(gdb) bt
#0  0x0000000000400512 in main (argc=1, argv=0x7fff7eed3048) at test.c:6
(gdb) p s
$1 = 0x0

分类: programming 标签: , ,

Coroutine(协程) 介绍

2012年11月26日 1 条评论

概念

coroutine 和函数一样, 区别在于 coroutine 有多个入口点, 而一般的函数 函数只能有一个入口点. 一般的函数只能从开始的地方执行, 一旦退出, 就只能从 唯一的入口点再开始了. 但是 coroutine 不同, 当它觉得没有任务需要处理时, 它可以把 CPU 让给其他函数, 然后它在这个让出的点等待, 直到其它函数再把 CPU 给它.

考虑以下的例子(producer-consumer 的模型):

producer 创建 buf, consumer 处理 buf. 这是很常见的编程模型, 在 C/S 或者其他编程框架中很常见.

一般我们用多线程的话, 大概会这样写:

q = new queue
 
producer_thread():
    loop:
        create some new items
        lock queue // 操作 queue 之前需要加锁保护数据
        add the items to q
        unlock queue
 
consumer_thread():
    loop:
        lock queue // 同样需要加锁
        remove some items from q
        unlock queue
        consumer items
 
main():
    create_producer_thread()
    create_consumer_thread()

而如果我们使用用 coroutine 的话, 情况就会好很多:

q = new queue
 
producer():
    loop:
        create some new items
        add the items to q
        yield to consumer
 
consumer():
    loop:
        remove some items from q
        consumer items
        yield to producer
 
main():
    initialize coroutine

coroutine 的优点

通过上面简单的示例(真实情况可能更复杂一些), 我们已经可以看到使用 coroutine 的优点了:

  • produce 和 consume 都是在同一个线程里执行的, 不会有 race condition 的问题 发生.
  • coroutine 是在用户空间实现的, coroutine 的堆栈是由用户自己维护的, 在切换 的时候, 用户交换 caller 和 coroutine 的堆栈, 这比在内核空间开执行的 thread 的切换节省了大量的开销(想象一下, 切换 thread 的时候, 需要做很多上下文的 切换, 各种寄存器 ……).
  • coroutine 的成本很低, 可以产生大量的 coroutine, 不像线程, 每个线程有自己的 线程堆栈, ID, 上下文等.
  • 可以由用户来控制 coroutine 的执行, 虽然 thread 也可以进行某种程序的控制, 但是 基本上 thread 的调度都是由 OS 来完成的.

用法实例

c

下面是在 QEMU 中使用的 coroutine.

/* file: test.c
 * coroutine_ucontext.c continuation.c could be fetched from spice or QEMU
 * source, use following command to compile:
 * gcc -o test test.c coroutine_ucontext.c continuation.c -DWITH_UCONTEXT
 */
 
#include <stdio.h>
#include "coroutine.h"
 
struct coroutine *co;
 
static void *coroutine_fun(void *data)
{
    while (1) {
        sleep(1);
        printf ("Yield to caller context\n");
        coroutine_yield(co);
        printf("\nI am in coroutine context\n");
    }
}
 
void coroutine_enter(void *data)
{
    while (1) {
        printf("Yield to coroutine context\n");
        coroutine_yieldto(co, data);
        printf("\nI am in caller context\n");
    }
}
 
static int coroutine_setup()
{
    co = malloc(sizeof(*co));
 
    co->stack_size = 16 << 20; /* 16Mb */
    co->entry = coroutine_fun;
    co->release = NULL;
    coroutine_init(co);
 
    return 0;
}
 
int main(int argc, char *argv[])
{
    coroutine_setup();
    coroutine_enter("coroutine");
    return 0;
}

python

python 中用来生成迭代器的就是一个 coroutine

#!/usr/bin/python
 
def coroutine():
    count = 0;
    while True:
        yield count
        count += 1;
 
 
c = coroutine()
for i in range(10):
    print "count:%d" % c.next()

分类: programming 标签: