Long polling
这两天在公司的项目里用了 Long polling,了解了它的实现原理,其实不像它的名字那么玄乎,只是 Ajax 和 HTTP 的类似小妙招的办法。
先解释一下 Long polling 是什么:
首先得说到传统的 Polling,Polling 是 Ajax 隔一段时间去抓取服务器上的数据,检查数据是否更新,但这样有很大问题,首先是每次请求会实用一个 HTTP request,对应的服务器就得建一个新的线程来处理这个 HTTP request,消耗网络流量、服务器资源不说,绝大多数情况下,数据短时间内是不会更新的,也就是说绝大多数的 Ajax 请求都只能无功而返。
而 Long polling 就是用来解决这个问题的。
它的核心是:
1. 做一个超时非常长的 Ajax 请求,并且在错误捕获代码里不断执行自己。2. CGI 部分接收到请求后在限定的时间内(Ajax 超时时间内)每隔一段时间(例如一秒)对数据库进行查询,可以使用 sleep 类似的方法。
3. 如果有新的数据则返回,如果即将到 Ajax 超时的时间则返回一个错误值,比如 404,这样那个非常长的 Ajax 请求会再发一个过来,继续查询。
这个办法的最大价值是有效减少了 HTTP 请求数,对服务器而言就不用开启新的线程去处理它,旧的线程如果不是因为超时,则只会在数据已经更新的情况下返回数据。可以节约大量资源,而且实时性更高。
目前,人人网的消息提示、Web 版阿里旺旺、新浪微博的新微博提示应该都是使用这种方法做的。
但这个办法对开发有
所以最好做好两套配置,一套用于产品环境的多线程环境,
我依然对那位在现有架构下想出这种办法的人表示钦佩。
这儿有范例代码,简单又实用:http://stackoverflow.com/questions/333664/simple-long-polling-example-code
春运又到了,献上更新版抓黄牛脚本。
好不容易搞定了火车票(当然不是通过酷讯或者黄牛),把去年写过的抓黄牛脚本重写了一下,提供给各位还在等待购买火车票的 Programmer 使用。说是抓黄牛,自然还包括普通转票者。原理还是通过轮询酷讯网站上的内容,但是增加了几个新特性:
- 用 re 提供的正则表达式替换掉了 SGMLParser 提高效率
- 可以轮询多个地址了,比如我到吉安和井冈山都可以,所以我要遍历两个地址
- 可以将转向链接直接打印在屏幕上了
- 提供了 Python 3 的 Package 级支持,但是因为 re 模块变更,正则表达式在 Python 3 里无法运行,暂时没心思更新了。
尽管酷讯推出了秒杀器,不过还是觉得不妥,一是没任何输出,谁知道它是否真的能秒到,二是不跨平台,在 Mac 和 Linux 上暂时无法使用。
Patches are welcome. :-)
#!/usr/bin/python # encoding: utf-8 # # Catch the yellow cattles script # # Author: Xuqing Kuang <xuqingkuang@gmail.com> # New features: # * Use regexp to instead of SGMLParser for performance # * Polling multiple URL at one time. # * Print out the redirect URL. # * Basic packages compatible with Python 3 # TODO: # * Use one regexp to split the href and text of link # * Update re package usage to compatible with Python 3 import time import os import re try: import urllib2 as urllib except ImportError: # Python 3 compatible import urllib.request, urllib.error urls = ( "http://piao.kuxun.cn/beijing-jinggangshan/", "http://piao.kuxun.cn/beijing-jian/", ) keyword = '3张' sequence = 60 class TrainTicket(object): """ Catch the yellow cattle """ def __init__(self, urls, keyword, sequence = 60): self.urls = urls self.keyword = keyword self.sequence = sequence self.cache=[] self.html = '' self.links = [] if hasattr(urllib, 'build_opener'): self.opener = urllib.build_opener() else: # Python 3 compatible self.opener = urllib.request.build_opener() self.result = [] self.re_links = re.compile('<a.*?href=.*?<\/a>', re.I) # self.re_element = re.compile('', re.I) # Hardcode at following self.requests = [] for url in urls: if hasattr(urllib, 'Request'): request = urllib.Request(url) else: # Python 3 compatible request = urllib.request.Request(url) request.add_header('User-Agent', 'Mozilla/5.0') self.requests.append(request) def get_page(self, request): """ Open the page. """ try: self.html = self.opener.open(request).read() except urllib.HTTPError: return False return self.html def get_links(self, html = ''): """ Process the page, get all of links """ if not html: html = self.html self.links = self.re_links.findall(html) return self.links def get_element(self, link = ''): """ Process the link generated by self.get_links(). Return list of the href and text """ # FIXME: have no idea how to split the href and text with one regex # So use two regex for temporary solution href = re.findall('(?<=href=").*?(?=")', link) # Get the href attribute if not href: # Process the no href attr href = [''] text = re.split('(<.*?>)', link)[2] # Get the text of link a. href.append(text) # Append to the list. return href def get_ticket(self, request = None): """ Generate the data structure of tickets for each URL. """ if not request: request = self.requests[0] self.get_page(request) self.get_links() i = 0 while i < len(self.links): link = self.get_element(self.links[i]) if not link: continue url = link[0] name = link[1] if name and name.find(keyword) >= 0 and url not in self.cache: self.result.append(( i, name, url, )) self.cache.append(url) i += 1 return self.result def print_tickets(self): """ Process all of URLS and print out the tickets information. """ while 1: self.result = [] try: print('Begin retrive') for request in self.requests: print('Begin scan %s' % request.get_full_url()) self.get_ticket(request) print('Found %s urls.' % len(self.links)) for r in self.result: print('Index: %s\nName: %s\nURL: %s\n' % ( r[0], r[1], r[2] )) print('Scan finished, begin sleep %s seconds' % self.sequence) time.sleep(self.sequence) except KeyboardInterrupt: exit() except: raise if __name__ == '__main__': tt = TrainTicket(urls, keyword, sequence) tt.print_tickets()
字符串反转和判断素数的 Python 语言算法范例
第一个是反转字符串,前两种是我出来丢人的,请直接看第三种。
方法1:
取出最大长度并 -1,以此为索引,依次递减,并将结果加入数组。
def reverse(string): if not string: return string str_list = [] i = len(string) - 1 while(i >= 0): str_list.append(string[i]) i -= 1 return ''.join(str_list)
方法2:
类似 C 中的位移操作,使用一个中间变量 t 记录临时值,然后将前后为置换,时间主要花在将字符串转换成数组上,实际遍历只需要上面方法的一半,但比上面方法要多消耗一个临时变量 t 的内存。当然,如果用 C 就可以直接指向内存,这样最节省内存,但是 Python 可能没更好办法。
但因为对 str 操作会造成 TypeError,所以还需要组成一个临时数组并在最后合并。
def reverse2(string): if not string: return string idx = 0 length = len(string) - 1 h_length = length / 2 # Half of length # 'str' object does not support item assignment for Python # Use C to solve it will be better. # So we convert it to be a list from the string. str_list = [x for x in string] while(idx <= h_length): t_idx = length - idx # Use varible t to store the temporary string t = str_list[t_idx] # Start to move the bit str_list[t_idx] = str_list[idx] str_list[idx] = t idx += 1 return ''.join(str_list)
方法3:
由 @Cotton 提供,直接用数组的方式,通过本用来做间隔数的段落进行反向遍历,我只能赞叹一句,Python 太强大了!
def reverse3(string): return string[::-1]
另一个是求素数,没想出什么比较好的算法,只能递归,但是因为除法(求余)非常缓慢,所以我对此算法非常不满。
方法1:
这里使用一个 range 直接生成一个数组,并用 for 递归,因为我觉得一次生成数组的速度应该比多次改写一个变量速度要快,但是自然,消耗内存稍大,如果对内存有要求,也可用 while 加递减代替。
def is_prime1(num): # Initial to presume it's a prime rt = True # It seems every number is possible to be the one, so it have to make a range. for i in range(2, num): if num % i == 0: rt = False break return rt
方法2:
由 jyf1987 提供,双数的判断一次解决(但是 3、5 的倍数如何能更快地排除呢?),剩下的是单数的递归,同时引入平方根(很好的想法,因为大于平方根后的数的计算已经没有意义,因为另一个数必然也是之前计算过的,因为 N = a*b,所以 N=M^2),降低运算量,引入 xrange 降低内存使用量,因为是原生实现,我相信应该不会比直接用 range 生成数组更慢。
from math import sqrt def is_prime2(num): # Checking the Type/value if type(num) != int: raise TypeError if num < 2: raise ValueError('The number must be great than 1') # Inital to presume it's a prime rt = True sq_num = int(sqrt(num)) # First we detect is it prime for 2 if num == 2: return rt if num % 2 == 0: rt = False return rt # Now, start to detect the odd number # Because all of even number could division by 2, then how about 3? -_-# for i in xrange(3, sq_num, 2): if num % i == 0: rt = False break return rt
冰天雪地三十六度翻转跪求更好算法~嘿嘿。。。
QPID 消息队列初步
QPID 是一种高性能 Message Bus,目前因为牵扯到工具的 SOA 架构,我的项目中将会整合它,以将自身对数据库的修改提交到 Message Bus 中, 供其它程序监听调用。
目前主流的 Message Bus 主要有以下几种:
而之所以选择 QPID 是因为它有以下几个优点(引用源):
- Supports transactions
- Persistence using a pluggable layer — I believe the default is Apache Derby
- This like the other Java based product is HIGHLY configurable
- Management using JMX and an Eclipse Management Console application - http://www.lahiru.org/2008/08/what-qpid-management-console-can-do.html
- The management console is very feature rich
- Supports message Priorities
-
Automatic client failover using configurable connection properties -
- http://qpid.apache.org/cluster-design-note.html
- http://qpid.apache.org/starting-a-cluster.html
- http://qpid.apache.org/cluster-failover-modes.html
- Cluster is nothing but a set of machines have all the queues replicated
- All queue data and metadata is replicated across all nodes that make up a cluster
- All clients need to know in advance which nodes make up the cluster
- Retry logic lies in the client code
- Durable Queues/Subscriptions
- Has bindings in many languages
- For the curious: http://qpid.apache.org/current-architecture.html
而对我而言,QPID 比较有优势的地方是,一有 Python 的 bindding(Perl 的兄弟对不起了),二是源代码比较充足。
为此我写了两个基类,简单地调用了 QPID Python 中的 Receiver 和 Sender,相对于 message_transfer 方法,这种方法可以传递 Dictionary 对象,一共三个文件,其实也可以合在一起使用。
#!/usr/bin/python import qpid import qpid.messaging import logging logging.basicConfig() class QPIDBase(object): def __init__(self, host='10.66.93.193', port='5672', queue_name='tmp.testing', username='guest', password='guest'): """ Arguments: host port queue_name username password """ self.host = host self.port = port self.queue_name = queue_name self.username = username self.password = password self.connection = None self.session = None def init_connect(self, mechanism='PLAIN'): """Initial the connection""" url = 'amqp://guest/guest@%s:%s' %(self.host, self.port) self.connection = qpid.messaging.Connection( url = url, sasl_mechanisms=mechanism, reconnect=True, reconnect_interval=60, reconnect_limit=60, username=self.username, password=self.password ) self.connection.open() def init_session(self): """Initial the session""" if not self.connection: self.init_connect() self.session = self.connection.session() def close(self): """Close the connection and session""" self.session.close() self.connection.close()
#!/usr/bin/python import qpid.messaging from datetime import datetime from base import QPIDBase class QPIDSender(QPIDBase): def __init__(self, **kwargs): super(QPIDSender, self).__init__(**kwargs) self.sender = None def init_sender(self): """Initial the sender""" if not self.session: self.init_session() self.sender = self.session.sender(self.queue_name) def send(self, content, t = 'test'): """Sending the content""" if not self.sender: self.init_sender() props = {'type': t} message = qpid.messaging.Message(properties=props, content = content) self.sender.send(message) def typing(self): """Sending the contents real time with typing""" content = '' while content != 'EOF': content = raw_input('Start typing:') self.send(content) if __name__ == '__main__': s = QPIDSender() s.send('Testing at %s' % datetime.now()) s.close()
#!/usr/bin/python from pprint import pprint from base import QPIDBase class QPIDReceiver(QPIDBase): def __init__(self, **kwargs): super(QPIDReceiver, self).__init__(**kwargs) self.receiver = None def init_receiver(self): """Initial the receiver""" if not self.session: self.init_session() self.receiver = self.session.receiver(self.queue_name) def receive(self): """Listing the messages from server""" if not self.receiver: self.init_receiver() try: while True: message = self.receiver.fetch() content = message.content pprint({'props': message.properties}) pprint(content) self.session.acknowledge(message) except KeyboardInterrupt: pass self.close() # Test code if __name__ == '__main__': r = QPIDReceiver() r.receive()
代码非常简单,容易读懂,使用方法是在一台 Linux Server 上安装好 qpid-cpp-server, 并且启动后,在 Client 上安装 python-qpid,然后修改一下 base.py __init__ 方法的 host 字段,或者在代码中自行指定好服务器地址,即可直接执行测试。
需要说明的是 QPID 返回的数据结构,包含可以为 Dictionary 对象的 properties 和只能为纯文本的 content 两个属性,也就是说可以将数据结构保存到 properties,而消息名称保存成 content 中,即:
try: while True: message = self.receiver.fetch() content = message.content pprint({'props': message.properties}) pprint(content) self.session.acknowledge(message) except KeyboardInterrupt: pass
一个终端执行 receiver.py 监听消息,再开一个终端执行 sender.py,将会如以下输出:
$ python ./receiver.py {'props': {u'type': u'test', 'x-amqp-0-10.routing-key': u'tmp.testing'}} 'Testing at 2010-12-06 14:54:59.536093'
如果有兴趣试下 QPIDSender.typing() 方法,再把 Kerberos 的用户名读出来,就可以做一个 IM 啦~
问题:现在似乎 Sender 发出的消息一次只能有一个 Receiver 接收,也就是现有代码不能用于 SOA,而这理论上应该是不应该的,依然在探索。
(可以尝试打开两个 receiver.py 测试)
Django 应用程序调试
这里要介绍的是,全面的 Django app 调试,从最简单的打印变量,到使用 Django 自带的 Debug Middleware 调试 SQL,最后到全面的 Django debug toolbar 的使用。
2. Django debug context processor[1](中级)
3. Django debug toolbar[2](更简单而强大。。。 -_-#)
这是最简单的办法,在启动了 django-admin runserver 后,可将变量打印到终端上,适用于临时性的排错,当然还有其它办法,只是我觉得这种办法最简单。
下面是简单范例。
from pprint import pprint from django.http import HttpResponse from myapp.core.models import Case def index(request, template = 'index.html'): c = Case.objects.select_related('author').get(pk = 100) pprint(str(c.query)) # 打印 C.objects.get(pk = 100) 调用的 SQL pprint(dict(c)) # 打印 C.objects.get(pk = 100) 的世纪内容。 return HttpResponse(c.__dict__)
二、Django debug context processor[1]
该 Middleware 主要用于调试 SQL 执行情况,能够将所有的数据库查询 SQL 及花费时间打印出来,但是它要求代码使用 RequestContext,普通的 Context 和 render_to_response() 便无法直接使用了,如果之前代码使用 Context 构建,可能需要重写这部分代码。
其实我推荐在开始编写代码的时候,就使用 django.views.generic.simple.direct_to_template 来渲染页面,像如下:
from django.views.generic.simple import direct_to_template def index(request, template = 'index.html'): ... return direct_to_template(request, template, { 'parameters': parameters, 'case': c, })
下面说安装和使用方法:
在 settings.py 的 'TEMPLATE_CONTEXT_PROCESSORS' 段中加入 'django.core.context_processors.debug',如下:
# RequestContext settings
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.request',
'django.core.context_processors.media',
'django.core.context_processors.debug',
'myapp.core.context_processors.processor',
...
)
在 settings.py 中加入 'INTERNAL_IPS',用于识别开发机地址,内容写入本机 IP 地址即可:
# Needed by django.core.context_processors.debug: # See http://docs.djangoproject.com/en/dev/ref/templates/api/#django-core-context-processors-debug INTERNAL_IPS = ('127.0.0.1', )
然后,在共享模板的开头(别告诉我你一个页面一个模板文件。。。),加入生成 SQL 列表的代码:
<body id="body"> {% if debug %} <div id="debug"> <p> {{ sql_queries|length }} Quer{{ sql_queries|pluralize:"y,ies" }} {% ifnotequal sql_queries|length 0 %} (<span style="cursor: pointer;" onclick="var s=document.getElementById('debugQueryTable').style;s.display=s.display=='none'?'':'none';this.innerHTML=this.innerHTML=='Show'?'Hide':'Show';">Show</span>) {% endifnotequal %} </p> <table id="debugQueryTable" style="display: none;"> <tr class="odd"> <td>#</td> <td>SQL</td> <td>Time</td> </tr> {% for query in sql_queries %} <tr class="{% cycle odd,even %}"> <td>{{ forloop.counter }}</td> <td>{{ query.sql|escape }}</td> <td>{{ query.time }}</td> </tr>{% endfor %} </table> </div> {% endif %} ... </body>
最终生成的效果是在页面顶部,增加了一个 XX Quueries 项,点击 (Show) 后如下:
Django debug toolbar 是我到目前为止见过的安装最简单,功能最强大的调试工具,它的主要特性有:
* 更加完善的 SQL 调试(比 Django debug processor 更加精准,Django debug contect processor 无法处理关系查询(Select related))
* 记录 CPU 使用时间(可惜没有针对代码级的 profile,希望未来的版本能增加这个功能)
* 完整记录 HTTP Headers 和 Request 请求
* 完整记录模板 Context 内容,包括 RequestContext 和直接传入的变量
* 记录 Signals
* python logging 模块的日志支持
安装也比较简单,可以使用 yum 直接安装,也从上面的地址下载后,直接使用 setuptools 通用的安装方法安装即可。
$ tar zxvf robhudson-django-debug-toolbar-7ba80e0.tar.gz $ cd robhudson-django-debug-toolbar-7ba80e0 $ python ./setup.py build $ sudo python ./setup.py install
如需确保安装正常,从 Python shell 里看看能否 import 即可,不出错,即安装正常:
$ python Python 2.6.1 (r261:67515, Feb 11 2010, 00:51:29) [GCC 4.2.1 (Apple Inc. build 5646)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import debug_toolbar >>>
然后是配置你的 settings.py。
我的调试 settings.py 和在产品服务器上运行的是不一样的,我也建议最好将二者分开,因为 Django app 开启 Debug 后对性能损耗非常严重。
将下面红字加入你自己的 settings.py 文件:
MIDDLEWARE_CLASSES = ( ... 'debug_toolbar.middleware.DebugToolbarMiddleware', ) INSTALLED_APPS = ( ... 'debug_toolbar' ) TEMPLATE_DIRS = ( ... '/Library/Python/2.6/site-packages/django_debug_toolbar-0.8.3-py2.6.egg/debug_toolbar/templates/', #按需修改!指向 debug_toolbar 的模板目录。 ) DEBUG_TOOLBAR_PANELS = (
'debug_toolbar.panels.version.VersionDebugPanel',
'debug_toolbar.panels.timer.TimerDebugPanel',
'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
'debug_toolbar.panels.headers.HeaderDebugPanel',
'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
'debug_toolbar.panels.template.TemplateDebugPanel',
'debug_toolbar.panels.sql.SQLDebugPanel',
'debug_toolbar.panels.signals.SignalDebugPanel',
'debug_toolbar.panels.logger.LoggingPanel'
)
然后使用 django-admin.py runserver 启动测试服务器,下图是 Django debug toolbar 的 SQL 查询页面。
相关链接:
[1] http://www.djangosnippets.org/snippets/93/
[2] http://github.com/robhudson/django-debug-toolbar/downloads
Django 下的 Kerberos 登录
Kerberos 这种统一用户名和密码进行登录的方式在各大公司(尤其外企)内部应该都得到广泛应用,它以其安全、高效和易于管理等特性得到了很多系统管理员的喜爱。
目前网上对于 Kerberos 登录原理的描述都过于复杂,其实它的实现非常简单,当你向一个部署了 Kerberos 的应用服务器发起登录请求的时候,服务器会去 /etc/krb5.conf 里描述的 KDC 服务器用 Kerberos 协议发起一个登录请求,如果用户名密码验证通过,将会向服务器发一个票(Ticket),否则将会引出一个错误。然后服务器可以将票发给客户端,以后客户端就可以用这张票进行其它操作。与火车票和电影票一样,Kerberos 的票,也是有使用时间限制的,如果不经过特殊设置,这张票的超时时间大约是 6 个小时。
而在 Django 里使用 Kerberos 登录,有两种办法,一种是由 Django 直接向 KDC 验证密码,另一种是在 Apache 上使用 mod_auth_kerb 模块,由浏览器来处理登录请求。
这两种办法其实都是对 Django Auth Backend 的重载,所有的 Auth Backend 都位于 django/contrib/auth/backends.py 里,这里[2]也有一个使用 Email 来进行验证的范例,我受此启发,写了这两个例子,希望也能抛砖引玉,能给你们更多启发。
第一种 - 由 Django 直接向 KDC 验证密码:
这种办法比较简单,需要在 web server 上装好 python-kerberos 包,并且配置好 /etc/krb5.conf,详细的配置方法,最好咨询 IT 部门,配置成功后在服务器上用 Kerberos 上有的普通用户运行 kinit,如果能够密码验证通过就行。
并且在 Django 的 settings.py 里写入类似下面这行,定义 Kerberos 的 Realm:
# Kerberos settings KRB5_REALM = 'EXAMPLE.COM'
与上面的 Email 验证例子类似的是,我们需要对 authenticate 方法进行重载,加入 kerberos 认证代码,python-kerberos 已经提供了 checkPassword 方法。
try: auth = kerberos.checkPassword( username, password, '', settings.KRB5_REALM ) except kerberos.BasicAuthError, e: return None
完整代码如下:
import kerberos from django.conf import settings from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import User class KerberosBackend(ModelBackend): """ Kerberos authorization backend for TCMS. Required python-kerberos backend, correct /etc/krb5.conf file, And correct KRB5_REALM settings in settings.py. Example in settings.py: # Kerberos settings KRB5_REALM = 'EXAMPLE.COM' """ def authenticate(self, username=None, password=None): try: auth = kerberos.checkPassword( username, password, '', settings.KRB5_REALM ) except kerberos.BasicAuthError, e: return None try: user = User.objects.get(username=username) except User.DoesNotExist: user = User.objects.create_user( username = username, email = '%s@%s' % (username, settings.KRB5_REALM.lower()) ) user.set_unusable_password() user.save() return user
第二种:Apache 上使用 mod_auth_kerb:
这种方法略有复杂,部署它需要向 KDC 申请一个 keytab 文件,以授权该 Web Server 向 KDC 发起请求,并且需要安装和配置 mod_auth_kerb(很简单,后面有),并且 /etc/krb5.conf 一点也不能少。
但是好处也是非常明显的,上面那种依然是使用 Django Auth Contrib 的 Session Manager 来负责登录信息的维持,但是这种方法将能够完全使用 Kerberos 自身提供的 Features,包括登录维持,和 kinit 的支持,也就是说,只要在本机上使用 kinit 成功登录过一次,用 Firefox (目前似乎在 Linux 上只支持该浏览器)访问部署了 mod_auth_kerb 的网站,将都不再需要登录。
它的原理包括两种条件,一种是没有在本机执行 kinit 的,使用 Firefox 直接访问服务器,服务器将会返回一个 401 Authorization Required 错误,这时 Firefox 会弹出对话框询问你的 Kerberos 用户名和密码,并提交你的密码。另一种在本机已经执行过 kinit 的,Firefox 会去读取你客户端的 Kerberos ticket 缓存,如果没有过期的话,就会使用它。无论哪种办法,Firefox 都将在 HTTP Header 里添加一个 'Authorization' 段,并且加入 Basic Authorization 验证方式,例如我本机上的:
Authorization Basic eGt1YW5nOkxvdmVvZnJvYWQuMTIz
使用这种部署方式,在 Django 1.1 版本以下,还没有比较好的解决办法,但好在 Django 1.1 提供了 RemoteUserBackend 后端,依然在 django/contrib/auth/backends.py 路径里,通过阅读它的代码,我们可以看到它其实依然是个 ModelBackend 的继承,而 Django 的 Request Handler 已经默认将 HTTP Meta 里的 REMOTE_USER 段给加入处理范围之内了,因此 RemoteUserBackend 的 ’authenticate‘ 与 ModelBackend 不太一样。 :-)
其实代码都已经写好,我们只需要处理一下拿到用户后的处理办法('configure_user' 方法)和处理用户名的方法('clean_username' 方法)就可以了。
我这里在拿到用户后,出于保护密码的原则,为该用户设置了一个无效密码('user.set_unusable_password()' 方法),并且设置了该用户的 Email。 同时,因为 RemoteUserBackend 默认返回的用户名是 ‘[username]@[KRB5_REALM]',所以我也把后面的 REALM 给去掉,直接贴代码:
from django.conf import settings from django.contrib.auth.backends import RemoteUserBackend class ModAuthKerbBackend(RemoteUserBackend): """ mod_auth_kerb modules authorization backend for TCMS. Based on DjangoRemoteUser backend. Required correct /etc/krb5.conf, /etc/krb5.keytab and Correct mod_auth_krb5 module settings for apache. Example apache settings: # Set a httpd config to protect krb5login page with kerberos. # You need to have mod_auth_kerb installed to use kerberos auth. # Httpd config /etc/httpd/conf.d/<project>.conf should look like this: <Location "/"> SetHandler python-program PythonHandler django.core.handlers.modpython SetEnv DJANGO_SETTINGS_MODULE <project>.settings PythonDebug On </Location> <Location "/auth/krb5login"> AuthType Kerberos AuthName "<project> Kerberos Authentication" KrbMethodNegotiate on KrbMethodK5Passwd off KrbServiceName HTTP KrbAuthRealms EXAMPLE.COM Krb5Keytab /etc/httpd/conf/http.<hostname>.keytab KrbSaveCredentials off Require valid-user </Location> """ def configure_user(self, user): """ Configures a user after creation and returns the updated user. By default, returns the user unmodified. Here, the user will changed to a unusable password and set the email. """ user.email = user.username + '@' + settings.KRB5_REALM.lower() user.set_unusable_password() user.save() return user def clean_username(self, username): """ Performs any cleaning on the "username" prior to using it to get or create the user object. Returns the cleaned username. For more info, reference clean_username function in django/auth/backends.py """ return username.replace('@' + settings.KRB5_REALM, '')
Django 是一个很强大的框架,虽然缺点和优点都同样的明显,有些甚至是由于 Python 语言或者类库造成的问题,但是因为其使用的便利性,高效的开发,而且其开发小组也非常活跃,使其特性的添加非常频繁,而且网上也有大量资源,例如 Django Snippets 网站,因此依然有着非常巨大优势。而通过阅读它的代码,往往都能获得更多启发。
链接:
写了个监视酷讯火车票的 Python 程序
受不了了,买火车票买不到,只好盯上黄牛票了,可是没法不停地刷页面啊,刚刚就错过了一个发布了 20 分钟的黄牛票,打电话回去时已经打不通了。。。-_-#
就写了个程序来解决这个问题,粘了一堆代码(参考太多,头一次写这种东西,原作者勿怪),总算成了,可能有 bug,欢迎提交 patch 或者更好的解决办法。
可以通过修改下面的参数来修改程序执行:
url = "http://piao.kuxun.cn/beijing-jinggangshan/" # 把火车票的搜索地址粘在这里,这里假设是北京到井冈山的 key = "2张" # 搜索关键字,我得俩人啊。。。 sequence = 60#60 * 5 # 搜索间隔,给服务器压力别太大,每分钟一次就行了。
#!/usr/bin/python # encoding: utf-8 import urllib2 import mailbox import time import os import re from sgmllib import SGMLParser class URLListName(SGMLParser): is_a="" name=[] def start_a(self, attrs): self.is_a=1 def end_a(self): self.is_a="" def handle_data(self, text): if self.is_a: self.name.append(text) url = "http://piao.kuxun.cn/beijing-jinggangshan/" key = "2张" sequence = 60#60 * 5 request = urllib2.Request(url) request.add_header('User-Agent', 'Mozilla/5.0') opener = urllib2.build_opener() data = opener.open(request).read() ticket_name = URLListName() ticket_name.feed(data) cache=[] while 1: try: print "beign retrive" data = opener.open(request).read() ticket_name.feed(data) print "beign scan" for result in ticket_name.name: if result and result.find(key) >= 0: if result in cache: pass else: print "found:" + result cache.append(result) print "scan finished, begin sleep " + str(sequence) + " seconds." time.sleep(sequence) except: raise
Python 技巧:@classmethod 修饰符
通常情况下,如果我们要使用一个类的方法,那我们只能将一个类实体化成一个对象,进而调用对象使用方法。
比如:
def __init__:
...
def print_hello(self):
print "Hello"
要用 print_hello() 就得:
hlo.print_hello()
Hello
如果用了 @classmethod 就简单了。
[转帖] Python 中 switch 的解决办法
其他语言中,switch语句大概是这样的
{
case value1: do_some_stuff1();
case value2: do_some_stuff2();
…
case valueN: do_some_stuffN();
default: do_default_stuff();
}
而python本身没有switch语句,解决方法有以下3种:
一个 Django 的超简单的数据库应用
昨天 Launch & Learn 的时候,做了个简单的 Django 的数据库应用,以配合 Li Li Zhang 的 ORM Course。
其实用 Django 做数据库应用真的很简单,不过比较适合用于重头设计的 Database Schema,然后用 syncdb 来同步数据库。
目前 Django 对 ForeignKey 和 One to One, One to Many, Many to Many 支持得都不错,最大的缺陷还是缺乏 Join 的支持导致多表联查的性能不好,这也是我在做开发的过程中碰到的最大问题。
其实用 Django 重头做一个数据库开发非常简单,用下面几步就可以了。
FIRST: Create new project and a new app with django-admin.py $ django-admin.py startproject [project_name] $ cd [project_name] $ django-admin.py startapp [app_name] SECOND: Edit the settings.py file to adjust db settings and installed apps. THIRD: Edit the [app_name]/models.py to modeling the database schema FOURTH: Edit the [prject_name]/urls.py to adjust the path of URL. FIFTH: Edit the [app_name]/views.py to realize your function. SIXTH: To run the manage.py to start the test server in the project. $ ./manage.py runserver THE END: Use your browser to navigate to your http://localhost:8000 and enjoy in it. :-)
这里提供了一个范例,也是昨天在 Launch and Learn 上演示过的:www.box.net/shared/6036abumgv 中的 lnl.tar.bz2 文件,里面提供的 Steps 其实就是上面那段,只是代码中也提供了注释以方便阅读。
在 Mac OS X 上为 Django 安装 MySQL-python 1.2.2
先去 Sun 网站上下载最新版本的 MySQL, 再去 djangoproject.org 上下载最新版本的 Django 1.0 release, 并且正常安装.
然后用 easyinstall mysql-python, 发现无法正常安装.
查看 easyinstall 的下载路径, 用下面的命令下载并且解压缩
$ cd /tmp $ curl -o MySQL-python-1.2.2.tar.gz http://internap.dl.sourceforge.net/sourceforge/mysql-python/MySQL-python-1.2.2.tar.gz $ tar xvf MySQL-python-1.2.2.tar.gz $ cd MySQL-python-1.2.2
然后修改 site.cfg, 修改下面内容:
由
#mysql_config = /usr/local/bin/mysql_config
改成
mysql_config = /usr/local/mysql/bin/mysql_config
否则会出现找不到 MySQL config 的问题:
... File "/tmp/easy_install-nHSsgl/MySQL-python-1.2.2/setup_posix.py", line 24, in mysql_config EnvironmentError: mysql_config not found
然后修改 _mysql.c, 把第 37 到 39 行注释掉, 如下:
//#ifndef uint //#define uint unsigned int //#endif
否则会出现:
In file included from /usr/local/mysql/include/mysql.h:47, from _mysql.c:40: /usr/include/sys/types.h:92: error: duplicate 'unsigned' /usr/include/sys/types.h:92: error: two or more data types in declaration specifiers error: command 'gcc' failed with exit status 1
然后再用 python ./setup.py build 编译
$ python ./setup.py build running build running build_py copying MySQLdb/release.py -> build/lib.macosx-10.5-i386-2.5/MySQLdb running build_ext building '_mysql' extension gcc -fno-strict-aliasing -Wno-long-double -no-cpp-precomp -mno-fused-madd -fno-common -dynamic -DNDEBUG -g -Os -Wall -Wstrict-prototypes -DMACOSX -I/usr/include/ffi -DENABLE_DTRACE -pipe -Dversion_info=(1,2,2,'final',0) -D__version__=1.2.2 -I/usr/local/mysql/include -I/System/Library/Frameworks/Python.framework/Versions/2.5/include/python2.5 -c _mysql.c -o build/temp.macosx-10.5-i386-2.5/_mysql.o -g -Os -arch i386 -fno-common -D_P1003_1B_VISIBLE -DSIGNAL_WITH_VIO_CLOSE -DSIGNALS_DONT_BREAK_READ -DIGNORE_SIGHUP_SIGQUIT gcc -Wl,-F. -bundle -undefined dynamic_lookup -arch i386 -arch ppc build/temp.macosx-10.5-i386-2.5/_mysql.o -L/usr/local/mysql/lib -lmysqlclient_r -lz -lm -lmygcc -o build/lib.macosx-10.5-i386-2.5/_mysql.so ld: warning in build/temp.macosx-10.5-i386-2.5/_mysql.o, file is not of required architecture ld: warning in /usr/local/mysql/lib/libmysqlclient_r.dylib, file is not of required architecture ld: warning in /usr/local/mysql/lib/libmygcc.a, file is not of required architecture
然后再用 python ./setup.py install 安装
$ sudo python ./setup.py install Password: running install running bdist_egg running egg_info writing MySQL_python.egg-info/PKG-INFO writing top-level names to MySQL_python.egg-info/top_level.txt writing dependency_links to MySQL_python.egg-info/dependency_links.txt reading manifest file 'MySQL_python.egg-info/SOURCES.txt' reading manifest template 'MANIFEST.in' writing manifest file 'MySQL_python.egg-info/SOURCES.txt' installing library code to build/bdist.macosx-10.5-i386/egg running install_lib running build_py copying MySQLdb/release.py -> build/lib.macosx-10.5-i386-2.5/MySQLdb running build_ext creating build/bdist.macosx-10.5-i386 creating build/bdist.macosx-10.5-i386/egg copying build/lib.macosx-10.5-i386-2.5/_mysql.so -> build/bdist.macosx-10.5-i386/egg copying build/lib.macosx-10.5-i386-2.5/_mysql_exceptions.py -> build/bdist.macosx-10.5-i386/egg creating build/bdist.macosx-10.5-i386/egg/MySQLdb ...
然后用下面的命令进行测试:
$ cd ~ $ python Python 2.5.1 (r251:54863, Apr 15 2008, 22:57:26) [GCC 4.0.1 (Apple Inc. build 5465)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import MySQLdb >>> MySQLdb.apilevel '2.0' >>> import django >>> print django.VERSION (1, 0, 'final')
如果能正常输出就没有问题了 :-)