6.30.2009

Давно ожидаемые релизы

1. PHP 5.3.0 (namespaces, mysqlnd, late static binding, оператор ?:, замыкания и лямбда-функции, удалена поддержка режима совместимости zend.ze1, добавлена константа __DIR__)
2. FireFox 3.5 (приватный серфинг, новый javascript движок, новые свойства CSS 2.1 и 3, нативный JSON, фоновое выполнения javascript, querySelectors и querySelectorsAll, cross-site AJAX)

С нетерпением жду появления новой версии лисы в репозиториях ubuntu.

Что до php, то не порадовало использование разделителя '\" для пространств имен (всё-таки надеялся что будет нечто более привычное "::" или ".") к остальным нововведениям буду тоже присматриваться, все-таки надеюсь в скором времени пополнить ряды ZCE...

6.05.2009

Google Page Speed

Ответ от гугля на яховский yslow в виде расширения для FireFox интегрирующегося в FireBug: http://code.google.com/speed/page-speed/

Создалось впечатление что информации выдает несколько больше чем yslow.

5.26.2009

nginx 0.7 stable

Буквально вчера ветка 0.7.х http-сервера nginx была обьявлена как stable, что не может не радовать =). Впринципе она довольно давно используется на одном посещаемом сайте и проблем с ней замечено небыло.

Среди заявленных нововведений (по сревнению с предыдущей stable веткой):
Наконец-то появилось долгожданное кэширование! Динамический ресайз изображений тоже порадовал.

Собственно эти двум нововведением и их совместном использовании и будет посвящен дальнейший текст.

Чего хочется? Хочется сследующего:
  • ресайзинг изображений не на application-level, а на server-level (в любой момент можно сгенерировать тумбнайл необходимого размера, причем это будет сделано "по запросу")
  • контролированое (в разумных по времени пределах) кэширование полученных тумбнайлов на frontend-серверах
Для реализации этой задачи, разумеется средствами nginx, создадим два дополнительных сервера:
  • backend-сервер изображений (ресайзит оригинальны)
  • frontend-сервер thumbnail'ов (сохраняет у себя в кэше)
Словами nginx.conf (оба сервера на одной машине) это выглядит вот так:
http {
proxy_cache_path /var/nginx/cache levels=1:2 keys_zone=thumbnail:10m;
...
# backend dynamic resizing
server {
listen *:80;
server_name i175d.site.domain;
root /var/www/site.domain/public_html/images;
location / {
image_filter_jpeg_quality 95;
image_filter crop 175 120;
}
}

# сервер thumbnail'ов
server {
listen *:80;
server_name i175.site.domain;

location / {
proxy_cache thumbnail;
proxy_cache_valid 60m;
proxy_pass http://i175d.site.domain;
}
}
}
Отдача статического изображения напрямую:
#ab -n 1000 -c 100 http://i.site.domain/image_static.jpg

Concurrency Level: 100
Time taken for tests: 0.144 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 13765478 bytes
HTML transferred: 13544630 bytes
Requests per second: 6942.18 [#/sec] (mean)
Time per request: 14.405 [ms] (mean)
Time per request: 0.144 [ms] (mean, across all concurrent requests)
Transfer rate: 93322.66 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 1 4 1.5 3 8
Processing: 3 10 4.0 9 20
Waiting: 2 7 3.5 6 15
Total: 5 14 4.6 13 25
Ресайзинг без кэширования (закоментированы proxy_cache_valid и proxy_cache):
#ab -n 1000 -c 100 http://i175.site.domain/image.jpg

Concurrency Level: 100
Time taken for tests: 13.863 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 13389000 bytes
HTML transferred: 13175000 bytes
Requests per second: 72.13 [#/sec] (mean)
Time per request: 1386.347 [ms] (mean)
Time per request: 13.863 [ms] (mean, across all concurrent requests)
Transfer rate: 943.14 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 3 8.5 0 32
Processing: 58 1275 602.5 1187 3303
Waiting: 39 1274 602.5 1187 3302
Total: 71 1277 603.2 1189 3303
С вкюченным кэшированием (1-й запуск)
#ab -n 1000 -c 100 http://i175.site.domain/image.jpg

Concurrency Level: 100
Time taken for tests: 1.617 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 13367000 bytes
HTML transferred: 13175000 bytes
Requests per second: 618.55 [#/sec] (mean)
Time per request: 161.670 [ms] (mean)
Time per request: 1.617 [ms] (mean, across all concurrent requests)
Transfer rate: 8074.31 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.9 0 8
Processing: 8 160 335.3 20 1502
Waiting: 8 160 335.3 20 1502
Total: 8 160 336.5 20 1506
С включенным кэшироваием (2-й запуск):
#ab -n 1000 -c 100 http://i175.site.domain/image.jpg

Concurrency Level: 100
Time taken for tests: 0.151 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 13367000 bytes
HTML transferred: 13175000 bytes
Requests per second: 6601.23 [#/sec] (mean)
Time per request: 15.149 [ms] (mean)
Time per request: 0.151 [ms] (mean, across all concurrent requests)
Transfer rate: 86170.50 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 4 4.0 3 17
Processing: 2 10 4.6 9 21
Waiting: 1 7 3.9 7 19
Total: 5 14 6.3 11 34
Как видно по результатам отдача закэшированного изображения происходит практически с такой же скоростью как и отдача статического файла напрямую. Большим плюсом в такой схеме является то, что nginx сам следит за устареванием кэша и чистит его по истечении заданного в конфиге времени.

5.12.2009

Python code completion в gedit

Небольшая комманда для установки необходимых плагинов:

#!/usr/bin/env bash
cd ~/.gnome2/gedit/plugins && \
curl -L http://github.com/fenrrir/geditpycompletion/tarball/master > pycompletion.tar.gz && \
tar xzf pycompletion.tar.gz && \
rm pycompletion.tar.gz && \
rm -Rf ./fenrrir-geditpycompletion* && \
git clone git://github.com/fenrrir/geditpycompletion.git && \
mv geditpycompletion/* . && \
rm -Rf ./geditpycompletion && \
wget http://users.tkk.fi/~otsaloma/gedit/completion.gedit-plugin \
wget http://users.tkk.fi/~otsaloma/gedit/completion.png \
wget http://users.tkk.fi/~otsaloma/gedit/completion.py


После этого в настройках gedit включаем плагины "Python Code Completion" и "Word Completion"

4.28.2009

Отправка rss/atom лент на почту

Давно хотел взяться за изучение python'a, но всё как то ноги не доходили, а тут по работе потребовалось написать софтику по отправке rss ленты по базе email'ов. Собственно после недолгого анализа требований и исходя из подручных средств отсановился на python.

В результате получилась небольшая функция feed2mime которая и занимается получением ленты новостей, преобразованием её в html, дальнейшим разбором средствами html5lib для нахождения изображений и последующим их внедрением в тело сообщения. На выходе - MIMEMultipart который можно рассылать уже с помощью smtplib.


#!/usr/bin/env python
"""
Convert RSS/ATOM feed in HTML'ed MIMEMessage (with images embedding)
"""

import urllib2, feedparser, smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.MIMEImage import MIMEImage
from html5lib import HTMLParser, treebuilders, sanitizer
from html2text import html2text
from hashlib import md5

def feed2mime(url, decorator, limit):
feed = feedparser.parse(url)
msgRoot = MIMEMultipart('related')
msgAlternative = MIMEMultipart('alternative');

msgRoot['Subject'] = feed.feed.title
msgRoot.preamble = 'This is a multi-part message in MIME format.'

html = decorator.get(feed.entries, limit)
_md5 = md5();
parser = HTMLParser( tree=treebuilders.getTreeBuilder("dom"), tokenizer=sanitizer.HTMLSanitizer )
dom = parser.parse(html)
i = 0
_images = [];
for node in dom.getElementsByTagName('img'):
i += 1
if node.hasAttribute('src'):
_md5.update(node.getAttribute('src'))
cid = _md5.hexdigest()
try:
url = urllib2.urlopen(node.getAttribute('src'), timeout = 5)
try:
mimeImage = MIMEImage(url.read(), _subtype="jpg");
except Exception, e:
pass
url.close();
mimeImage.add_header('Content-ID', '<' + cid + '>')
node.setAttribute('src', 'cid:' + cid)
_images.append(mimeImage)
except Exception, e:
print "[", i, "] ", e, "\n"

msgAlternative.attach(MIMEText(dom.toxml().encode('utf-8'), 'html', _charset="utf-8")) #, _charset="utf-8"
msgRoot.attach(msgAlternative)
msgRoot.attach(MIMEText(html2text(html).encode('utf-8')))

for i in _images:
msgRoot.attach(i)

return msgRoot;

if __name__ == '__main__':
import getopt, sys
opts, args = getopt.getopt(sys.argv[1:], "", ["url=", "smtp-host=", "smtp-user=", "smpt-pass=", "limit=", "from=", "to="])

smtp = smtplib.SMTP()
smtpUser = False
smtpPass = False
iLimit = 10
sUrl = False
for o,a in opts:
if o == "--smtp-host":
smtp.connect(a)
elif o == "--smtp-user":
smtpUser = a
elif o == "--smtp-pass":
smtpPass = a
elif o == '--url':
sUrl = a
elif o == '--from':
sFrom = a
elif o == "--to":
sTo = a
elif o == "--limit":
iLimit = int(a)

class FeedDecorator:
def get(self, entries, limit):
html = ''
i = 0;
for entry in entries:
if i>=limit:
break;
html += '<p style="clear:both">'
html += '<b><a href="' + entry.link + '" target="_blank">' + entry.title + '</a></b><br />'
if(len(entry.enclosures) > 0):
html += '<img src="' + entry.enclosures[0].href + '" align="left" hspace="5" vspace="5"/>'
html += entry.description + '</p>'
i += 1

return html

msg = feed2mime(sUrl, FeedDecorator(), iLimit)
if (smtpUser and smtpPass):
smtp.login(smtpUser, smtpPass)
msg.__delitem__('From')
msg.__delitem__('To')
msg['From'] = sFrom
msg['To'] = sTo
smtp.sendmail(sFrom, sTo, msg.as_string())
smtp.quit()