到底web服务器的极限是多少呢?100 r/s? 500 r/s?还是1000 r/s?

StaticGenerator上看到,原来1000 r/s都不算什么.

真的是这样吗?如果是真的那就太恐怖了。

目前的web基本上都是动态的,为了提高性能,我们常常会使用各种各样的手段优化,例如减少IO,合理的字符串使用和操作,memcached等等。但是尽管全部优化都做足了,全动态的web点击率以是在1000 - 2000 r/s(4核4G标准的服务器配置)之间。

还能进一步优化吗?能,这就需要利用前面提到的,“静态文件”。我们知道,web服务器处理静态文件的请求是非常高效的,特别是nginx,它宣称“支持高达 50,000 个并发连接数的响应”。(呵呵,感觉有点像在为nginx卖广告。)

将动态内容静态化输出,有几个问题是必须解决的:

  1. url怎么对应到静态文件路径?
  2. 静态文件什么时候失效?即防止脏数据的存在。
  3. 什么时候生成静态文件?

 

URL转换成静态文件路径

如果web应用的url是友好的,是非常容易对应到静态文件的。例如:

http://hostname/post/some-post-detailname/

这样我们很容易想象到对应的静态文件路径是/post/some-post-detailname/index.html或者/post/some-post-detailname.html等等.

一个通用的方案:对url算md5后来确定静态文件的路径.

还是使用上面的url,它的path是/post/some-post-detailname/,算md5结果为:81982658fe1d78f51d228950babd1457,则可以取路径为/8/1/982658fe1d78f51d228950babd1457

在nginx中如果生成这样的路径呢?答案是使用内嵌perl,以下是我的一个例子:

 

perl_set  $path_md5  '
    use Digest::MD5 qw(md5_hex);
    use File::stat;
    
    sub {
        my $r = shift;
        my $s = md5_hex($r->uri);
        my $path_md5 = join "", join("/", substr($s, 0, 1), substr($s, 1, 1), substr($s, 2)), ".html";
        my $filepath = "/data/www/".$path_md5;
        if(-f $filepath) {
            my $mtime = stat($filepath)->mtime;
            if(time() - $mtime > 1800) {
                return $path_md5.".new";
            }
        }
        return $path_md5;
    }
';

 

生成静态文件

生成静态文件的时机,这个取决你的应用,像blog,新闻等内容为主的应用,可以在第一次请求的时候生成;又如一些web api类型的,可以在同一请求达到指定的次数时,才生产静态文件。

至于怎样生成静态文件?最简单的方式是将response的内容直接写到文件中,如果你是基于django的话,StaticGenerator可以帮你大忙了。

静态文件的失效时间,像上面生成静态文件路径的例子中,我设定的失效时间的30分钟。

 

完整的配置文件例子如下: 点击nginx.conf下载

 

user www www;

pid 
/home/test/nginx.pid;
worker_processes  
8;
error_log 
/data/nginx/logs/error.log;

events {
  worker_connections  
2048;
  
use epoll;
}
 
http {
  
# default nginx location
  include        /home/test/mime.types;
  default_type    text
/html;
  log_format main
      
'$remote_addr $host $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent" $process $request_time $sent_http_x_type';
  
  client_header_timeout  10s; 
# If after this time the client send nothing, nginx returns error "Request time out" (408). 
  client_body_timeout    10s;
  send_timeout           10s; 
# if after this time client will take nothing, then nginx is shutting down the connection. 
  connection_pool_size        256;
  client_header_buffer_size    1k;
  large_client_header_buffers    
4 2k;
  request_pool_size        4k;
  output_buffers   
4 32k;
  postpone_output  
1460;
  sendfile        on;
  tcp_nopush             on;
  keepalive_timeout      
20 10;
  tcp_nodelay            on;
  
  fastcgi_connect_timeout 
300;
  fastcgi_send_timeout 
300;
  fastcgi_read_timeout 
300;
  fastcgi_buffer_size 64k;
  fastcgi_buffers 
4 64k;
  fastcgi_busy_buffers_size 128k;
  fastcgi_temp_file_write_size 128k;
 
  client_max_body_size       10m;
  client_body_buffer_size    256k;
  proxy_connect_timeout      
90;
  proxy_send_timeout         
90;
  proxy_read_timeout         
90;
  client_body_temp_path      
/data/nginx/logs/client_body_temp;
  proxy_temp_path            
/data/nginx/logs/proxy_temp;
  fastcgi_temp_path          
/data/nginx/logs/fastcgi_temp;
 
  gzip off;
  gzip_min_length  
1100;
  gzip_buffers     
4 32k;
  gzip_types       text
/plain text/html application/x-javascript text/xml text/css;
 
  ignore_invalid_headers    on;
  
  perl_set  
$path_md5  '
    use Digest::MD5 qw(md5_hex);
    use File::stat;
    
    sub {
        my $r = shift;
        my $s = md5_hex($r->uri);
        my $path_md5 = join "", join("/", substr($s, 0, 1), substr($s, 1, 1), substr($s, 2)), ".html";
        my $filepath = "/data/www/".$path_md5;
        if(-f $filepath) {
            my $mtime = stat($filepath)->mtime;
            if(time() - $mtime > 1800) {
                return $path_md5.".new";
            }
        }
        return $path_md5;
    }
';
  
  server {
    
listen 80;
    server_name 
127.0.0.1;
    
index index.html;
    root   
/data/www;
    set 
$process "nginx";
    
    
# static resources
    #location ~* ^.+\.(html|jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js)$
    #{
    #  expires 30d;
    #  break;
    #}

    
    location 
/site_media {
        root   
/home/test/web;
        access_log   off;
        
#expires 30d;
        rewrite ^/site_media/(.*/media/$1 break;
    }
    
    location 
/nginx_status {
      
# copied from http://blog.kovyrin.net/2006/04/29/monitoring-nginx-with-rrdtool/
      stub_status on;
      access_log   off;
      allow 
127.0.0.1;
      allow 
192.168.0.0/16;
      allow 
219.131.196.66;
      deny all;
      break;
    }
    
    location 
/request_status {
      access_log   off;
      allow 
127.0.0.1;
      allow 
192.168.0.0/16;
      deny all;
      rewrite 
^/request_status/(.*/rrd/$1 break;
      autoindex  on;
    }
    
    location 
~* ^/(webmd5|weburl|urlsafe|website|urlnotfound|reporturl|suggesturl|receive|leak|virus|site|admin|index)/ {
      
#rewrite (.*) /$path_md5 redirect;
      try_files /$path_md5 @fastcgi;
    }
    
    location 
@fastcgi {
       set 
$process "fcgi";
       fastcgi_pass unix
:/home/test/gateway.sock;
       fastcgi_param PATH_INFO 
$fastcgi_script_name;
       fastcgi_param REQUEST_METHOD 
$request_method;
       fastcgi_param QUERY_STRING 
$query_string;
       fastcgi_param CONTENT_TYPE 
$content_type;
       fastcgi_param CONTENT_LENGTH 
$content_length;
       fastcgi_pass_header Authorization;
       fastcgi_param REMOTE_ADDR 
$remote_addr;
       fastcgi_param SERVER_PROTOCOL 
$server_protocol;
       fastcgi_param SERVER_PORT 
$server_port;
       fastcgi_param SERVER_NAME 
$server_name;
       fastcgi_param REQUEST_FILENAME 
$path_md5;
       
#fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
       fastcgi_intercept_errors off;
       break;
    }
    
    location 
/403.html {
      root   
/usr/local/nginx;
      access_log   off;
    }
 
    location 
/401.html {
      root   
/usr/local/nginx;
      access_log   off;
    }
 
    location 
/404.html {
      root   
/usr/local/nginx;
      access_log   off;
    }
 
    location 
= /_.gif {
      empty_gif;
      access_log   off;
    }
 
    access_log    
/data/nginx/logs/access.log main;
  }
}

 

希望本文对你有用. ^_^

 

 

 

posted @ 2009-04-25 16:48 MK2 阅读(168) | 评论 (3)编辑

假如你的服务器很慢,而且经常无法连接,很可能你遇上了我最近的烦恼.

试着执行:

$ netstat -nta|awk '/^tcp/{++S[$NF]} END {for(a in S) print a,S[a]}'

如果结果类似:

LAST_ACK 14
SYN_RECV 348
ESTABLISHED 70
FIN_WAIT1 229
FIN_WAIT2 30
CLOSING 33
TIME_WAIT 18122

 

对了,就是TIME_WAIT状态太多!!!

无论你怎么配置你的服务器,怎么修改你的程序,怎么优化你的代码,TIME_WAIT同样是那么多。

 

搜索到很多文章说修改系统配置:

net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_recycle=1

虽然TIME_WAIT看起来是少了,是的,是看起来。实际这样 设置后,反而将原来的问题隐藏了,TIME_WAIT少了...

 

没办法了吧,TIME_WAIT少了,服务器依然很慢,依然无法连接.

 

我的经验教训,根本不是服务器端的问题,是客户端,客户端没有关闭连接,又或者没像你期待那样被关闭了。

重新看看客户端关闭连接的那块代码吧,做完整测试,确认真的关闭后再回来看看TIME_WAIT,很难找到了吧?

 

别在原地死钻空子,或许这里根本没有宝藏。

 

希望本文对你有用. ^_^

posted @ 2009-03-26 15:14 MK2 阅读(173) | 评论 (0)编辑

Tenjin, "Tenjin is a very fast and full-featured template engine available in several script languages."

官方的测试结果: 也快得太夸张了

MacOS X 10.4 Tiger, Intel CoreDuo 1.83GHz, Memory 2GB
Language Template Engine Test#1(sec) Test#2(sec)
Python(2.5.1) pyTenjin (0.6.1) 6.96 5.61
Cheetah (2.0) 20.36 19.82
Django (0.9.5) 71.33 59.80
Myghty (1.1) 107.88 19.30
Kid (0.9.6) 380.24 378.96
Genshi (0.4.4) 560.30 271.69
Mako (0.1.9) 17.78 13.49
Templetor (web.py 0.22) 428.19 61.53
Ruby(1.8.6) rbTenjin (0.6.0) 7.34 4.52
eruby (1.0.5) 12.29 11.53
ERB(def_method) (Ruby1.8.6) 36.73 5.85
PHP(5.2.0) phpTenjin (0.0.1) 5.39 3.64
Smarty (2.6.18) 10.84 10.21
Perl(5.8.8) plTenjin (0.0.1) 10.42 5.72
Template-Toolkit(XS) (2.18) 103.58 26.30
HTML::Template (2.9) 46.70 30.21
JS(spidermonkey) jsTenjin (0.0.1) 19.00 12.98
JS(Rhino, JDK5) jsTenjin (0.0.1) 24.29 19.15
Java(JDK5) Velocity (1.4) 22.80 11.41
Velocity (1.5) 20.01 8.42

 

这样一来,就可以做到在web端和浏览器端使用相同的模板语法了.

python的例子:

<html>
  
<body>
    
<h1>${title}</h1>
    
<table>
<?py i = 0 ?>
<?py for item in items: ?>
<?py     i += 1 ?>
<?py     color = i % 2 == 0 and '#FFCCCC' or '#CCCCFF' ?>
      
<tr bgcolor="#{color}">
        
<td>#{i}</td>
        
<td>${item}</td>
      
</tr>
<?py #endfor ?>
    
</table>
  
</body>
</html>

javascript的例子:

<html>
  
<body>
    
<h1>${title}</h1>
    
<table>
<?js for (var i = 0, n = items.length; i < n; i++) { ?>
<?js    var color = i % 2 == 1 ? '#FFCCCC' : '#CCCCFF'; ?>
      
<tr bgcolor="#{color}">
        
<td>#{i+1}</td>
        
<td>${items[i]}</td>
      
</tr>
<?js } ?>
    
</table>
  
</body>
</html>

希望本文介绍的内容对你有用! ^_^

 

posted @ 2009-03-22 12:52 MK2 阅读(248) | 评论 (1)编辑

在看PEP-0138时无意发现,就萌生起自己实现一个类似的装饰器的念头,毕竟这个装饰器确实很好用。

其中accepts和returns是在原有的基础上进行改造的,详细可以查看代码

 

#!/usr/bin/python
#
 -*- coding: utf-8 -*-
"""All are decorator!!!
"""

class ArgumentLengthError(Exception):
    
""""""
    
class ArgumentTypeError(Exception):
    
"""Accepts argument type error."""

class ReturnValueError(Exception):
    
"""Return value error."""

def accepts(*types):
    
"""参数类型校验装饰器
    
    若需要装饰的方法是类方法,则必须符合python的规范,类方法第一个参数的名称必须为self
    
"""
    
def check_accepts(f):
        is_class_method 
= False
        want_len, now_len 
= len(types), f.func_code.co_argcount
        
if now_len - want_len == 1 and f.func_code.co_varnames[0] == 'self'# fixed class method
            now_len -= 1
            is_class_method 
= True
        
if want_len != now_len:
            
raise ArgumentLengthError('want %d args, but now %d' % \
                                      (want_len, now_len))
        
def new_f(*args, **kwds):
            
if is_class_method:
                check_args 
= args[1:]
            
else:
                check_args 
= args
            
for (a, t) in zip(check_args, types):
                
if not isinstance(a, t):
                    
raise ArgumentTypeError("arg %r does not match %s" % \
                                            (a, t))
            
return f(*args, **kwds)
        new_f.func_name 
= f.func_name
        new_f.
__doc__ = f.__doc__
        
return new_f
    
return check_accepts

def returns(rtype):
    
"""返回值类型校验装饰器"""
    
def check_returns(f):
        
def new_f(*args, **kwds):
            result 
= f(*args, **kwds)
            
if not isinstance(result, rtype):
                
raise ReturnValueError(
                    
"return value %r does not match %s" % (result,rtype))
            
return result
        new_f.func_name 
= f.func_name
        new_f.
__doc__ = f.__doc__
        
return new_f
    
return check_returns

def synchronized(lock):
    
"""锁同步装饰方法
    
    lock必须实现了acquire和release方法
    
"""
    
def sync_with_lock(f):
        
def new_f(*args, **kwargs):
            lock.acquire()
            
try:
                
return f(*args, **kwargs)
            
finally:
                lock.release()
        new_f.func_name 
= f.func_name
        new_f.
__doc__ = f.__doc__
        
return new_f
    
return sync_with_lock

 

 

使用举例:若accepts与其他任何装饰器同时使用的话,必须将accepts放到最内层,要不然,会导致参数校验异常.

 

    @synchronized(__locker)
    @returns(tuple)
    @accepts(dict)
    
def update(self, data):
        
"""更新计划任务"""
        tasks 
= self.get_tasks()
        delete_task 
= None
        
for task in tasks:
            
if task[PLANTASK.ID] == data[PLANTASK.ID]:
                tasks.insert(tasks.index(task), data)
                tasks.remove(task)
                delete_task 
= task
        r, msg 
= self._refresh(tasks, delete_task)
        
return r, msg, data[PLANTASK.ID]

 

 

PS: synchronized 装饰方法在python2.5 + 可以使用with语法来代替了,

def func3():
        with LockTester._lock:
            LockTester._count += 1
            time.sleep(0.1)
            return LockTester._count

 

希望本文对你有用! ^_^

posted @ 2009-02-08 12:27 MK2 阅读(160) | 评论 (0)编辑

今天在做一个web页面控制memcached重启的功能,本以为非常简单,不就获取pid,然后kill,在重新启动memcached就这么简单。

没想到使用subprocess.Popen() 来调用命令时竟然发现response确实是返回到客户端了,但是服务器端和客户端的http连接竟然还连接着,一直不断。

查看了一下python的文档,发现:http://docs.python.org/library/subprocess.html

popen2 closes all file descriptors by default, but you have to specify close_fds=True with Popen

 

 

我的代码如下:

 

def _restart(port, start_cmd):
    cmd 
= 'ps aux | grep "memcached .* %s"' % port
    p 
= subprocess.Popen(cmd, shell=True, close_fds=True, # 必须加上close_fds=True,否则子进程会一直存在
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdoutdata, stderrdata 
= p.communicate()
    
if p.returncode != 0:
        
return False, error_response(cmd, stderrdata)
    
for r in stdoutdata.split('\n'):
        
if cmd in r:
            
continue
        
break
    
if r:
        pid 
= r.split()[1]
        
        cmd 
= 'kill %s' % pid
        p 
= subprocess.Popen(cmd, shell=True, close_fds=True,
                             stdout
=subprocess.PIPE, stderr=subprocess.PIPE)
        p.communicate()
    p 
= subprocess.Popen(start_cmd, shell=True, close_fds=True,
                         stdout
=subprocess.PIPE, stderr=subprocess.PIPE)
    stdoutdata, stderrdata 
= p.communicate()
    
if p.returncode != 0:
        
return False, error_response(cmd, stderrdata)
    
return True, None

 

 

希望对你有用 ^_^

posted @ 2009-02-06 15:15 MK2 阅读(338) | 评论 (2)编辑

最近在做一些很简单的web请求优化,web端的逻辑非常简单,就是接收用户POST上来的数据,并保存到数据库,和处理用户的GET查询请求。

web服务器:Ubuntu + Lighttpd + fastcgi + django,数据库使用mysql 5.0

为了达到海量用户并发访问的要求,几乎所有代码都尽可能简单地写,参数校验都几乎没有做。连接池、精简版的memcached,精简版的django等等,python的好处就是你可以随便修改任何源代码。

一切似乎都准备就绪,但是最终压力测试的结果令我惊讶,POST的点击率竟然比GET的还要快(1200:800),不太可能吧。尽管我如何调整lighttpd的参数,检测memcached是否生效,都没法找到原因,查询怎么会比插入数据慢呢?

 

看了代码一遍又一遍,不断反复地测试,结果依然是一样。

 

无意中看了一下django的模板语言部分,难道是django的template特别慢?

我们的响应结果有两种格式:json和xml,json是直接使用simplejson生成的,而xml是使用django的模板生成的。

于是我对比了json和xml的测试结果,原来这里就是问题所在,xml的点击率比json的满了近400.

 

终于找到了突破口,于是抛弃了django的模板,我直接用最原始的方式,组装字符串

哇啦啦。。。。用ab测试后,测试结果终于正常了,点击率达到1500。

 

后来冷静想想,django的模板肯定会慢,因为每次xml格式的reponse都会进行一次io操作读取本地模板文件,然后又要执行复杂的模板语言解析,必然速度会慢许多。

 

不要小看一些我们平时看起来不耗时的操作或逻辑代码,通常都是这些不起眼的操作,导致我们的应用的整体性能大大降低。

 

web优化依然还是那么几项:尽量避免复杂逻辑,连接池,cache,还有io操作,就连字符串拼接都要仔细考虑。

 

希望本文对你有用。 ^_^

posted @ 2009-02-03 15:14 MK2 阅读(160) | 评论 (0)编辑

最近工作有点多,趁周末有空,继续分享我在学习和使用python过程中的一些小tips。

有没有遇到过这样的事情:对数据库执行插入或更新操作,因为数据量大或其他原因,导致此次操作非常耗时,有时甚至等上好几个小时,也无法完成。很郁闷,怎么操作不超时啊?因为数据库配置时超时时间很长,并且有些操作又是需要很长时间的,所以不能修改默认的超时时间。

因为客观条件不允许,我们不能靠数据库超时来终止此次操作,所以必须要在自己的方法逻辑模块里实现超时检测的功能。

在python里有没有可以不用修改原来的方法内部逻辑,就能实现超时检测呢?肯定有啦,就是利用装饰器。装饰器是什么?在博客园找到了一篇介绍文章:函数和方法装饰漫谈(Function decorator)

废话听完,我现在介绍主角出场:超时装饰器,timeout decorator。

超时检测逻辑:启动新子线程执行指定的方法,主线程等待子线程的运行结果,若在指定时间内子线程还未执行完毕,则判断为超时,抛出超时异常,并杀掉子线程;否则未超时,返回子线程所执行的方法的返回值。

在实现过程中,发现python默认模块里是没有方法可以杀掉线程的,怎么办呢?当然先问问google或百度,果然,keill thread这个关键词很热门,很快就搜索到我想要的东西了:"Kill a thread in Python",就是以下这个KThread类,它继承了threading.Thread,并添加了kill方法,让我们能杀掉它:

 

import sys


class KThread(threading.Thread):
    
"""A subclass of threading.Thread, with a kill()
    method.
    
    Come from:
    Kill a thread in Python: 
    http://mail.python.org/pipermail/python-list/2004-May/260937.html
    
"""
    
def __init__(self, *args, **kwargs):
        threading.Thread.
__init__(self, *args, **kwargs)
        self.killed 
= False

    
def start(self):
        
"""Start the thread."""
        self.
__run_backup = self.run
        self.run 
= self.__run      # Force the Thread to install our trace.
        threading.Thread.start(self)

    
def __run(self):
        
"""Hacked run function, which installs the
        trace.
"""
        sys.settrace(self.globaltrace)
        self.
__run_backup()
        self.run 
= self.__run_backup

    
def globaltrace(self, frame, why, arg):
        
if why == 'call':
          
return self.localtrace
        
else:
          
return None

    
def localtrace(self, frame, why, arg):
        
if self.killed:
          
if why == 'line':
            
raise SystemExit()
        
return self.localtrace

    
def kill(self):
        self.killed 
= True

 

好了,万事戒备,让我们来完成剩下的代码吧,也就是timeout decorator:

class Timeout(Exception):
    
"""function run timeout"""
    
def timeout(seconds):
    
"""超时装饰器,指定超时时间
    若被装饰的方法在指定的时间内未返回,则抛出Timeout异常
"""
    
def timeout_decorator(func):
        
"""真正的装饰器"""
        
        
def _new_func(oldfunc, result, oldfunc_args, oldfunc_kwargs):
            result.append(oldfunc(
*oldfunc_args, **oldfunc_kwargs))
        
        
def _(*args, **kwargs):
            result 
= []
            new_kwargs 
= { # create new args for _new_func, because we want to get the func return val to result list
                'oldfunc': func,
                
'result': result,
                
'oldfunc_args': args,
                
'oldfunc_kwargs': kwargs
            }
            thd 
= KThread(target=_new_func, args=(), kwargs=new_kwargs)
            thd.start()
            thd.join(seconds)
            alive 
= thd.isAlive()
            thd.kill() 
# kill the child thread
            if alive:
                
raise Timeout(u'function run too long, timeout %d seconds.' % seconds)
            
else:
                
return result[0]
        _.
__name__ = func.__name__
        _.
__doc__ = func.__doc__
        
return _
    
return timeout_decorator

 

真的能运行吗?写个测试程序运行运行:

@timeout(5)
def method_timeout(seconds, text):
    
print 'start', seconds, text
    time.sleep(seconds)
    
print 'finish', seconds, text
    
return seconds

if __name__ == '__main__':
    
for sec in range(110):
        
try:
            
print '*' * 20
            
print method_timeout(sec, 'test waiting %d seconds' % sec)
        
except Timeout, e:
            
print e

 

看,真的行:

 

 

原始代码: threadutil.py.zip

 

本次的tips可能有点复杂,运用到多线程,装饰器等,希望它对你有所帮助。

PS:山顶风光果然一流,独缺美女相伴!!!

posted @ 2008-08-30 22:29 MK2 阅读(1224) | 评论 (6)编辑
对于我这个从.NET过来的人,对python的str和unicode会感到非常不适应。经常在一些常用的地方遇到编码异常问题。如保存字符串到文本中,是要先编码还是直接保存呢?字符串是str还是unicode呢?保存字符串到数据库是直接保存str又或是先将unicode编码得到的str呢?
好多个问号,这都是我个python初学者碰到的问题。在尝试多次痛苦后,总算有了一些思路。原来unicode早已在python实现的很好,只是我使用不当罢了。

一个很关键的并且要常记住的,就是代码中所有字符串都统一使用unicode,而不是str。这样,自己就能很清楚要处理的字符串类型了。请记住,是所有,任何地方。
例如:
>>> s1 = u'%s欢迎您!' % u'北京'
>>> s1
u'\u5317\u4eac\u6b22\u8fce\u60a8\uff01'
>>> print s1
北京欢迎你!

若像这样,就会抛异常:
>>> s2 = '%s欢迎您!' % u'北京'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 2: ordinal not in range(128)
由UnicodeDecodeError可猜想得到,解析器尝试使用ascii对'%s欢迎您!'进行解码,由于'%s欢迎您!'实际是使用utf-8编码的(这是我系统终端默认的),所以使用ascii解码肯定会错,只要如下,就可以重现这个异常了:
>>> s2 = '%s欢迎您!'.decode('ascii')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 2: ordinal not in range(128)

分清encode和decode。str --> decode(c) --> unicode, unicode --> encode(c) --> str,其中编码类型c必须相同。

将unicode字符串写入文件前先使用特定编码对其进行编码(如unicodestr.encode('utf-8'))得到str,保证写入文件的是str;从文件读取到str,然后对其进行解码(如encodestr.decode('utf-8'))得到unicode。
这是互逆的两个操作,编码类型一定要一致,否则会出现异常。

自己支持了unicode,但是你团队的其他人是否都使用unicode呢?你使用的其他模块是否也使用unicode呢?这个一定要清楚的,不然同样会出现许多因为编码问题的异常。

好了,晚了,随便写了点,大家晚安,2008.8.1,本世纪的第一次日全蚀,你要看吗?

Technorati 标签: , ,
posted @ 2008-08-01 01:15 MK2 阅读(319) | 评论 (0)编辑
A呵呵,一直都是用WLW写blog的,很久之前就装了Scribefire,一直都没用,今天来个测试,呵呵,如果满意,以后就用它了。



直接帖截图测试:



edit





上传图片测试:OK





测试其他吧哈哈乱来。

©®°±¶º¿¿¾


呵呵,看来真的可以取代WLW



Technorati Tags: , ,
posted @ 2008-06-10 19:23 MK2 阅读(286) | 评论 (0)编辑

因经常在多台机器上使用Firefox,难免会想装一些已经习惯了的插件(晕,都依赖他们),没安装上总是用起来不顺手。于是就有了这篇文章。

1. 第一个要备忘的肯定是它:Google Browser Sync

image

可以同步些什么?看看这个截图吧:

image

很Cool吧,仿佛在那台机打开FF,都感觉是一样的,完全获取回上次的所有状态,并且支持加密传输,这样个人私隐就不会泄露了。

2. GMarks

虽然说Google Browser Sync可以同步Bookmarks,但是我还是喜欢将Bookmarks保存到互联网上,GMarks就是一个Google Bookmarks的插件,使用也非常简单,Bookmarks太多了吧,不記得放在那个位置了吧,呵呵,不怕,在自己的Bookmarks上搜索一下不就行了?

image

3. All-in-One Gestures Extension

有使用鼠标手势控制的习惯吧?怕使用了Firefox而无法拥有这个功能吗?呵呵,哪All-in-One Gestures Extension肯定是你的最爱,完全支持你想要的手势。

image

image

4. DictCN (呵呵,忘记推荐沪江小D,绝对比这个精彩多了!!!)

呵呵,像我这些英语很差但是又要经常看英文的人,这个插件肯定比钱包更加重要,放在你的FF上吧,肯定会用上的,特别是它还支持发音功能。

image image

5. AutoHide

觉得FF上方的工具栏太占位置了吗?导致看到的页面太小了吧?呵呵,这个插件绝对帮你将工具栏完全隐藏了,按Shift+F11让FF全屏吧。

image

6. Firebug 不顶不行

呵呵,这个不用我介绍了吧,太出名了!!!

image

估计每个做Web开发的人的FF上都会有它。o(∩_∩)o...

7. Showcase

经常打开几十个tab而又不想关掉它们吗?是不是要找一个tab花了很多时间?使用Showcase可以让你马上找到你想找的tab,不信?看看截图吧:

image

想用了吧?

8. IE Tab

不用IE不行啊,有时候真的没有了IE,很多事情就没法做了,于是有打开IE。很麻烦吧,如果你的系统是Windows,那么IE Tab可以很方便地设置当前页面使用IE内核访问,还可以设置更多的规则,唉,我们的世界没了IE真不容易:

image

9. Fireshot

一个页面太长了吧,是否有想将整个页面截图处理的需求呢?Fireshot可以实现,并且还有上传,直接将截图的链接发给你的朋友吧,呵呵,这样介绍网站,也是一个不错的选择。

image

image

10. Tab Scope

想预览一下tab里面是什么内容吗?看看它是不是已经加载完了?这个插件可以提高你的兴趣。

image

11. QuickDrag

可以直接拖拉页面上的链接,并在新tab中打开,不用点鼠标中键了,Cool啊!!

12. ColorZilla

可以获取页面上任意部位的颜色。

image

??. FEBE

好了,装了这么多插件,有备份这些插件的插件吗?呵呵,你都想备份了吧?用FEBE吧,你想怎样备份都可以。

image

image

这里可以查看更多截图:http://customsoftwareconsult.com/extensions/febe/febe50/FEBE5.0.html

 

呵呵,今天到此为止吧,以后用到更多好的插件我会继续补充的,:-)

 

希望对你有用!!

 

Technorati 标签: ,,,
posted @ 2008-06-09 10:49 MK2 阅读(426) | 评论 (2)编辑

在没有使用AjaxForm前,我做的一个小小的评论提交的Web form,评论内容使用了TinyMCE做文本编辑。为了增加一点点的用户体验,就顺手拿AjaxForm来实现Ajax提交。可是发现出现了一个意外的事情。就是每次提交,第一次提交时,AjaxForm会无法获得当前编辑的评论内容,即TextArea里面的内容,要再点击一次提交,才能将TextArea的内容提交上去。

关键是TinyMCE上的内容没有在提交前更新到TextArea中。于是想看看AjaxForm是否有在提交前的事件绑定,发现在beforeSubmit事件中,formData的内容已经被填充,虽然可以在此处自行将当前的TinyMCE的内容填充上去(详细可查看这里),可是总觉得是不太漂亮的解决方案。

为了找是否有其它途径解决此问题,我查看了一下AjaxForm的源代码,发现原来AjaxForm作者已经为这问题提出了统一的解决方案,具体源代码如下:

    // hook for manipulating the form data before it is extracted;
    // convenient for use with rich editors like tinyMCE or FCKEditor
    var veto = {};
    this.trigger('form-pre-serialize', [this, options, veto]);
    if (veto.veto) {
        log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
        return this;
   }

果然,真的有对应的事件让我们可以在formData被序列号前将TinyMCE的数据更新到TextArea中。只要我们绑定form的form-pre-serialize事件即可。

于是一个我的代码就这样出来了:(对应FCKEditor类似)

    // bind form using 'ajaxForm' 
    $('#commentForm').ajaxForm(options);
    // 绑定form-pre-serialize事件,在触发form-serilaize事件前保存tinyMCE的数据到textarea中
    $('#commentForm').bind('form-pre-serialize', function(event, form, options, veto) {
	tinyMCE.triggerSave();
    });
 
^_^ 希望对你有用...

Technorati: , , ,

 
posted @ 2008-06-08 20:38 MK2 阅读(574) | 评论 (4)编辑

最近在收集PE的FileInfo信息,发现不是每个PE都会有,而且有也不一定全部都有,总结了样本,基本上包含一下信息:

LegalCopyright :版权信息

InternalName: 内部名称

FileVersion:文件版本

CompanyName:公司名称

LegalTrademarks:注册商标

Comments:注释

ProductName:产品名称

ProductVersion:产品版本

FileDescription:文件描述

OriginalFilename:原始文件名

PrivateBuild:私有编译 (见样本3)

SpecialBuild:特殊编译

 

几个特殊的信息:

BuildDate:  编译日期 (见样本1)

BuildNumber: 编译号(见样本1)

FileType: 文件类型 (本应该出现在VS_FIXEDFILEINFO中的,却又出现在FileInfo中,造成冲突,以VS_FIXEDFILEINFO中的为准)(见样本1)

OLESelfRegister:  (见样本2)

文件的分析信息列表:

样本1:http://www.cnblogs.com/Files/fengmk2/35d417aa12d58edfc4ed0156e8ee855f.Nap.txt

样本2:http://www.cnblogs.com/Files/fengmk2/OLESelfRegister_QQ.exe.txt

样本3:http://www.cnblogs.com/Files/fengmk2/PrivateBuild_8c26cc7d912ab9568b44a62291f7ac51.dll.txt

Technorati: ,

posted @ 2008-06-06 10:40 MK2 阅读(94) | 评论 (0)编辑

在threading module中,有一个非常特别的类local。一旦在主线程实例化了一个local,它会一直活在主线程中,并且又主线程启动的子线程调用这个local实例时,它的值将会保存在相应的子线程的字典中。

我们先看看测试代码:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Description: test the threading.local class
# Create: 2008-6-4
# Author: MK2[fengmk2@gmail.com]
from threading import local, enumerate, Thread, currentThread

local_data = local()
local_data.name = 'local_data'

class TestThread(Thread):
        def run(self):
                print currentThread()
                print local_data.__dict__
                local_data.name = self.getName()
                local_data.add_by_sub_thread = self.getName()
                print local_data.__dict__

if __name__ == '__main__':
        print currentThread()
        print local_data.__dict__
        
        t1 = TestThread()
        t1.start()
        t1.join()
        
        t2 = TestThread()
        t2.start()
        t2.join()
        
        print currentThread()
        print local_data.__dict__
运行结果:

<_MainThread(MainThread, started)>
{'name': 'local_data'}
<TestThread(Thread-1, started)>
{}
{'add_by_sub_thread': 'Thread-1', 'name': 'Thread-1'}
<TestThread(Thread-2, started)>
{}
{'add_by_sub_thread': 'Thread-2', 'name': 'Thread-2'}
<_MainThread(MainThread, started)>
{'name': 'local_data'}

 

主线程中的local_data并没有被改变,而子线程中的local_data各自都不相同。

怎么这么神奇?local_data具有全局访问权,主线程,子线程都能访问它,但是它的值却是各当前线程有关,究竟什么奥秘在这里呢?

查看了一下local的源代码,发现就神奇在_path()方法中:

def _patch(self):
    key = object.__getattribute__(self, '_local__key')
    d = currentThread().__dict__.get(key)
    if d is None:
        d = {}
        currentThread().__dict__[key] = d
        object.__setattr__(self, '__dict__', d)

        # we have a new instance dict, so call out __init__ if we have
        # one
        cls = type(self)
        if cls.__init__ is not object.__init__:
            args, kw = object.__getattribute__(self, '_local__args')
            cls.__init__(self, *args, **kw)
    else:
        object.__setattr__(self, '__dict__', d)
 

每次调用local实例的属性前,local都会调用这个方法,找到它保存值的地方.

d = currentThread().__dict__.get(key)  就是这个地方,确定了local_data值的保存位置。所以子线程访问local_data时,并不是获取主线程的local_data的值,在子线程第一次访问它是,它是一个空白的字典对象,所以local_data.__dict__为 {},就像我们的输出结果一样。

如果想在当前线程保存一个全局值,并且各自线程互不干扰,使用local类吧。

 

Technorati: , , ,

posted @ 2008-06-04 22:46 MK2 阅读(382) | 评论 (0)编辑

先来看看我们之前是怎样获取到当前登录user的:在view中,我们常常就会通过request对象来获取当前用户user的引用:

def comment_add(request):
    # do something...
    user = request.user
    # to do .....

 

这样,确实很方便就能获取多用户的信息。可是,如果要做别的地方获取user呢?例如要在model中获取user呢?

class Post(models.Model):
    title = models.CharField('Post标题', maxlength=100, db_index=True)
    create_date = models.DateTimeField('发布时间', default=datetime.now(), editable=False)
    modified_date = models.DateTimeField('最后修改时间', default=datetime.now(), editable=False)
    author = models.ForeignKey(User, verbose_name="作者", editable=False)
    content = models.TextField('内容')
    last_visitor_IP = models.IPAddressField('最后的访问者IP', null=True, blank=True, editable=False)
    tags = models.ManyToManyField(Tag, verbose_name="the list of tags", null=True, blank=True)
    slug = models.SlugField('自定义url', maxlength=100, null=True, blank=True)
    hits = models.IntegerField('点击数', editable=False, default=0)


可以看到,author是editable=False的,即我们想Post被保存是自动设置author为当前登录用户user。而这里没有request,怎样获取到这个user呢?找了一下django的文章,提到了使用middleware来解决这个需求。

好吧,先在项目中创建一个"middleware"模块(即带有一个空__init__.py文件的目录),接着创建"threadlocals.py"到此目录,代码如下:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Description: thread locals middleware
# Create: 2008-6-4
try:
    from threading import local
except ImportError:
    from django.utils._threading_local import local

_thread_locals = local()
def get_current_user():
    return getattr(_thread_locals, 'user', None)

class ThreadLocals(object):
    """Middleware that gets various objects from the
    request object and saves them in thread local storage."""
    def process_request(self, request):
        _thread_locals.user = getattr(request, 'user', None)

接着将此middleware添加到项目中,打开setting.py,修改代码如下:

MIDDLEWARE_CLASSES = (
    "django.middleware.common.CommonMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "yourproject.middleware.threadlocals.ThreadLocals", 
)
写到这里,发现这不类似与asp.net中的HttpModule吗?

然后为了自动设置author为当前用户,我们需要重载post的save方法:

先导入middleware

from net4.middleware import threadlocals
def save(self):
    if self.id is None:
        self.author = threadlocals.get_current_user()
    super(Post, self).save()

编码工作完成了,还不快点进入admin看看是否可以正常工作了呢?

 

其实大部分都是算直接在参考文章里哪过来的,呵呵,反正是花了点时间才找到,呵呵,就拿来主义,让大家一齐分享咯。

 

希望对你有用! ^_^


本文参考:
Making User info available outside requests

posted @ 2008-06-04 16:52 MK2 阅读(316) | 评论 (2)编辑

昨天在保存一些中文字符到文本文档时,发现一个很奇怪的现象。先看看代码:

#coding=utf-8
import os

def write_use_open(filepath):
    try:
        file = open(filepath, 'wb')
        try:
            content = '中华人民共和国abcd \r\nee ?!>??@@@!!!!!???¥@#%@%#xx学校ada\r\n'
            print file.encoding
            print file.newlines
            print file.mode
            print file.closed
            print content
            file.write(content)
        finally:
            file.close()
            print file.closed
    except IOError, e:
        print e
    

if __name__ == '__main__':
    filepath = os.path.join(os.getcwd(), 'file.txt')
    write_use_open(filepath)
 
开始我是IDLE编写的,并直接按F5运行,没发现问题,文件也被正确地保存,文件的编码类型也是utf-8.
image 
image
可是我用命令行运行,却发现显示出现乱码了,然后在打开文件发现文件被正确保存了,编码还是utf-8:
image 
我想问题是命令行不能自动识别字符编码吧,因为IDLE显示是正确的,它支持utf-8。
 
于是我修改了代码,在字符串前加了'u',表明content是unicode:

content = u'中华人民共和国abcd \r\nee ?!>??@@@!!!!!???¥@#%@%#xx学校ada\r\n'

可是运行发现,命令行是正确显示了,但是却出现异常:

image

很明显,content里包含了非ASCII码字符,肯定不能使用ASCII来进行编码的,write方法是默认使用ascii来编码保存的。

很容易就可以想到,在保存之前,先对unicode字符进行编码,我选择utf-8

#coding=utf-8
import os

def write_use_open(filepath):
    try:
        file = open(filepath, 'wb')
        try:
            content = u'中华人民共和国abcd \r\nee ?!>??@@@!!!!!???¥@#%@%#xx学校ada\r\n'
            print file.encoding
            print file.newlines
            print file.mode
            print file.closed
            print content
            print unicode.encode(content, 'utf-8')
            file.write(unicode.encode(content, 'utf-8'))
        finally:
            file.close()
            print file.closed
    except IOError, e:
        print e
    
if __name__ == '__main__':
    filepath = os.path.join(os.getcwd(), 'file.txt')
    write_use_open(filepath)
 
看看运行结果:
image 
OK了打开文档也是正确的。
读取文件又怎样?同样道理,只是这次不是编码了,而解码:
def read_use_open(filepath):
    try:
        file = open(filepath, 'rb')
        try:
            content = file.read()
            content_decode = unicode(content, 'utf-8')
            print 'original text'
            print content
            print 'decode using utf-8'
            print content_decode
        finally:
            file.close()
    except IOError, e:
        print e
    
if __name__ == '__main__':
    filepath = os.path.join(os.getcwd(), 'file.txt')
    write_use_open(filepath)
    print 'read file ---------------------------'
    read_use_open(filepath)

image

为什么不直接在open的时候就解码呢?呵呵,可以啊,可以使用codecs的open方法

import codecs
def read_use_codecs_open(filepath):
    try:
        file = codecs.open(filepath, 'rb', 'utf-8')
        try:
            print 'using codecs.open'
            content = file.read()
            print content
        finally:
            file.close()
    except IOError, e:
        print e
image 
 
好了,希望对你有用。
 
本文参考:Unicode HOWTO
Technorati 标签: ,,,,,
posted @ 2008-05-31 13:36 MK2 阅读(430) | 评论 (0)编辑