|
|
|
October 31st, 2010
10:41 pm - Не забудь сделать escape!
Originally published at Блог Андрея Смирнова. Please leave any comments there. Когда я только начинал программировать в web, правильно сделать escape данных было непростой задачей: никаких хороших библиотек не было или приходилось писать что-то свое, при этом на каждом шагу не забывая поставить нужный escape. Сегодня отличные библиотеки, такие как Ruby on Rails, позволяют «расслабиться» и забыть о том, что такое escaping (по крайней мере до какой-то степени). Не смотря на это, все еще необходимо понимать, что такое escaping, зачем он нужен, когда и какой.
Отсутствие правильного escaping (впрочем, как и избыточный и неуместный escaping) приводит к ошибкам и уязвимостям (проблемам безопасности) в web-приложениях. Обычно уязвимость состоит в том, что приложение получает данные из различных внешних источников (от пользователя, из других приложений), эти данные приложение вставляет строчку, которая впоследствие будет обработана третьей системой (базой данных, браузером, интерпретатором и т.п.) При этом при передаче особым образом подготовленных данных удается совершить действие, которое не должно было произойти.
SQL
Типичная уязвимость: SQL Injection.
Пример кода (авторизация по логину и паролю):
runQuery("SELECT id FROM users WHERE login='$login' AND password='$password'")
Если значения переменных $login и $password получены от пользователя (например, через форму авторизации), можно в поле password ввести значение вида: ' OR '' = ', тогда после подстановки получится такой запрос:
SELECT id FROM users WHERE login='login' AND password='' OR '' = ''
Условие WHERE всегда истинно, для любой строчки БД. В зависимости от вида запроса, способа авторизации такое поведение приведет к возможности авторизации, не зная пароля.
Проблема состоит в том, что при прямой подстановке значения переменной $password мы смогли изменить смысл исходного запроса.
( Read the rest of this entry » )
|
October 30th, 2010
10:26 pm - UDF в MySQL, json или то, как забрать обновления данных из БД
Originally published at Блог Андрея Смирнова. Please leave any comments there. Иногда необходимо забирать данные из БД MySQL в режиме реального времени во внешнюю систему, которая никак не связана с MySQL. Существует множество возможных решений, например, можно реализовать «слейва» MySQL, который бы хранил полученные данные во внешней системе.
Одно из возможных решений – сделать «выгрузку» данных из MySQL с помощью UDF (User Defined Functions) и триггеров. Для этого необходимо поставить слейв MySQL, на котором уже повесить на интересующие таблицы триггеры, которые с помощью UDF будут выгружать поток изменений таблиц во внешнюю систему. Слейв необходим, т.к. если триггеры поставить на мастере, то в случае отката транзакции действия, уже сделанные триггерами, откатить не получится, а на слейв попадают только зафиксированные транзакции. Второе,чтобы триггеры работали на слейве, тип репликации должен быть выставлен на STATEMENT-based.
Порывшись в одном интересном архиве UDF для MySQL я нашел несколько функций, которые мне подошли:
- преобразование строки MySQL в json;
- интерфейс с memcached.
В результате получился следующий план действий: данные модифицируются на мастере, реплицируются на слейв с помощью STATEMENT-репликации. В процессе репликации на слейве запускаются триггеры, формируют с помощью UDF пакет обновлений в JSON, и передают его во внешнюю очередь (memcacheq) по memcached-протоколу. Конечно, это не единственный возможный способ, но все UDF уже были почти готовы. После доделывания напильником UDF получился вполне стабильно работающий вариант.
Триггеры выглядят примерно следующим образом:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| CREATE FUNCTION kick_photos (row_id INT) RETURNS INT
BEGIN
SELECT memc_set('queue_db', (json_object('insert' AS action, 'photos' AS table_name, photos.id AS id, json_members('data', json_object(photos.user_id AS `user_id`,photos.width AS `width`,photos.created_at AS `created_at`,photos.filename AS `filename`,photos.parent_id AS `parent_id`,photos.content_type AS `content_type`,photos.height AS `height`,photos.thumbnail AS `thumbnail`,photos.size AS `size`))))) INTO @dummy FROM photos WHERE id = row_id;
RETURN @dummy;
END
CREATE TRIGGER photos_INSERT AFTER INSERT ON photos FOR EACH ROW
SET @dummy = memc_set('queue_db', (json_object('insert' AS action, 'photos' AS table_name, NEW.id AS id, json_members('data', json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`)))));
CREATE TRIGGER photos_DELETE BEFORE DELETE ON photos FOR EACH ROW
SET @dummy = memc_set('queue_db', (json_object('delete' AS action, 'photos' AS table_name, OLD.id AS id, json_members('data', json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`)))));
CREATE TRIGGER photos_UPDATE AFTER UPDATE ON photos FOR EACH ROW
BEGIN
IF json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`) <> json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`) THEN
SET @dummy = memc_set('queue_db', (json_object('update' AS action, 'photos' AS table_name, OLD.id AS id, json_members('new', json_object(NEW.user_id AS `user_id`,NEW.parent_id AS `parent_id`,NEW.created_at AS `created_at`,NEW.filename AS `filename`,NEW.width AS `width`,NEW.content_type AS `content_type`,NEW.height AS `height`,NEW.thumbnail AS `thumbnail`,NEW.size AS `size`)), json_members('old', json_object(OLD.user_id AS `user_id`,OLD.parent_id AS `parent_id`,OLD.created_at AS `created_at`,OLD.filename AS `filename`,OLD.width AS `width`,OLD.content_type AS `content_type`,OLD.height AS `height`,OLD.thumbnail AS `thumbnail`,OLD.size AS `size`)))));
END IF;
END; |
Комментарии:
- функция
kick_photos позволяет скопировать строчку таблицы в очередь как пакет обновления типа «вставка», может использоваться для начального наполнения внешней системы;
- триггеры на удаление и вставку просто формируют соответствующие пакеты;
- триггер на обновление проверяет, действительно ли в пакете произошли изменения (например, мы можем использовать не все поля в пакете);
- необходимо учесть, что работе FOREIGN KEY CONSTRAINT триггеры не вызываются (очередной прикол MySQL), т.е., например, при если при выполнении запроса на удаление из таблицы
A будут по FOREIGN KEY удалятся записи из таблицы B, то в триггере на удаление из A необходимо отработать этот случай, т.к. триггеры на таблице B не будут вызваны.
Код UDF доступен на github, это – «подпиленный» код из репозитория UDF или собственные разработки:
|
October 28th, 2010
07:57 am - HighLoad-2010: Приемы разработки высоконагруженных приложений на Twisted/Python
Originally published at Блог Андрея Смирнова. Please leave any comments there. 25-26 октября состоялся HighLoad-2010, конференция получилось хорошей хотя бы потому, что было мало докладов ни о чем. Неплохой уровень, особенно было приятно увидеть «профессоров» PostgreSQL.
Я выступал с докладом «Приемы разработки высоконагруженных приложений на Twisted/Python». В докладе получилась (вполне сознательно) сборная солянка из советов и приемов о том, как писать приложения на Twisted (и похожих frameworkах). Из-за большого количества разных тем не получилось углубиться ни в одну, каюсь…
Тезисы:
- Запуск и шедулинг многих однопоточных процессов на одном сервере.
- Key-value storage и приемы работы с ним.
- Обслуживание сотен тысяч соединений на одном сервере.
- HTTP-сервисы и балансировка нагрузки, локализация нагрузки.
- Сбор статистики, интеграция с системой мониторинга.
- Шина обмена сообщениями на примере AMQP.
- Поиск и устранение memory leak.
- Оптимизация по времени отклика и пропускной способности.
- Мифы и правда о Python как языке разработки нагруженных приложений.
Презентация:
Утащить:
- В формате PDF
- Модуль py-numа, который упоминался в докладе
|
February 15th, 2010
11:11 pm - Профайлинг Twisted-приложений
Originally published at Блог Андрея Смирнова. Please leave any comments there. Часто сам забываю, как профилировать легко и быстро Twisted-приложения (с некоторым изменениями подойдет для любых Python-приложений). Кроме Twisted нам понадобится еще KCachegrind
Запускаем наше приложение с включенным профайлингом:
twistd -n --savestats --profile=myprog.hotshot myprog
Подаем нагрузку, профайл собирается. Теперь с помощью утилиты hotshot2cg из поставки KCachegrind превращаем hotshot-профайл в calltree-профайл, который уже умеет KCachegrind «кушать».
hotshot2cg myprog.hotshot > myprog.calltree
Запускаем KCachegrind, открываем в нем полученный профайл:
kcachegrind myprog.calltree
|
10:48 pm - MySQL, ROW/STATEMENT/MIXED-репликация и триггеры
Originally published at Блог Андрея Смирнова. Please leave any comments there. Описанная особенность MySQL попалась мне на глаза слишком поздно, пишу, чтобы кто-то не напоролся на те же грабли. Начнем с начала. Итак, необходимо было отслеживать изменения MySQL-базы данных и складывать эти изменения в очередь (не в БД) для дальнейшей обработки внешней системой. Для отслеживания изменений подходят триггеры, но они активируются в процессе выполнения запросов транзакции и в случае последующего «rollback» не будут откатываться (что совершенно нормально для триггеров, влияющих только на состояние БД, т.к. состояние БД будет корректно откатываться). Поэтому необходимо выполнять триггеры только для успешных транзакций: проще всего это достигнуть с помощью репликации – на слейв передаются только запросы зафиксированных транзакций. Таким образом, мастер-БД не содержит триггеров, после репликации данные попадают на слейв, таблицы на котором обвешаны триггерами, те активируются и данные попадают в очередь. Казалось бы, все замечательно?
( Read the rest of this entry » )
|
October 13th, 2009
09:42 pm - HL++ (2009): Twisted Framework
Originally published at Блог Андрея Смирнова. Please leave any comments there. Сегодня выступал на HighLoad++ с докладом Twisted Framework – фреймворк для написания сетевых приложений в Python.
Введение
Последнее время в области web происходит смещение внимания с тяжелых application-серверов, которые тратят на обработку запроса сотни миллисекунд, а то и секунды, к более легковесным сервисам, передающим меньшие объемы данных с минимальной задержкой. Переход от генерации десятков и сотен килобайт HTML-кода в ответ на запрос к передаче изменений в данных, запакованных в JSON и измеряемых сотнями байт. В качестве примеров таких сервисов можно привести Gmail, FriendFeed, Twitter Live Search и т.п.
Для обеспечения минимальной задержки для пользователя необходимо либо поддерживать постоянное соединение (например, Adobe Flash, RTMP) или использовать технику HTTP long polling в сочетании с keep alive. Так или иначе на стороне сервера это приводит к появлению большого количества одновременных соединений (тысячи, десятки тысяч), по каждому из которых передается не такой большой объем данных. Эту ситуацию называют обычно проблемой C10k.
( Read the rest of this entry » )
|
October 5th, 2009
10:41 pm - Mongrel vs. Phusion Passenger: выбор очевиден
Originally published at Блог Андрея Смирнова. Please leave any comments there. Предыдущая конфигурация:
- nginx (главный proxy), который раздает трафик в
- haproxy (ради возможности балансировать по нагрузке), который распределяет нагрузку по нескольким webapp-серверам
- с 16-ю mongrelами на каждом
Проблемы:
- «Утекающая» память, периодический out of memory на серверах, лечится только перезапуском mongrelов.
- Запросы, занимающие десятки секунд из-за неверной балансировки (в нагруженный mongrel все-таки попадает несколько «тяжелых» запросов).
- Сложность управления кластером монгрелов – постоянные проблемы при перезапуске, «не стартующие» mongrelы и т.п.
Новая конфигурация:
Результат:

Комментарий: переход на Phusion Passenger на Week 39, объем занятой памяти – это белая область на графике, растущая сверху вниз. До перехода на Passenger объем свободной памяти стремительно уменьшался, иногда доходя до нуля, после перехода остается более-менее стабильным. Использование CPU осталось на прежнем уровне (как и ожидалось).
После перехода исчезли запросы, которые по непонятным причинам занимали десятки секунд – время выполнения коррелирует со сложностью запроса.
Так что если вы еще не переключились, мы идем к вам
P.S. Отдельное спасибо glebpom за подсказку.
|
July 12th, 2009
04:56 pm - Qik Push Engine API: приглашаем разработчиков
Originally published at Блог Андрея Смирнова. Please leave any comments there. Qik - это сервис стриминга (вещания) и загрузки видео с мобильных телефонов. Загруженное видео можно посмотреть на сайте или на его специальной версии с мобильного телефона. Доступна интеграция с другими сервисами, такими как Twitter, Facebook и другие. Клиенты для практически всех современных моделей телефонов: iPhone, Windows Mobile, Symbian, Android, Blackberry и другие.
Qik Push Engine - это механизм, который позволяет получать мгновенные оповещения о новых/изменившихся Qik-видео. Например, можно посмотреть постоянно обновляющийся список live-видео, все видео из района Новопеределкино или все видео со словом “кошка”. На основе Qik Push Engine API можно построить интересные приложения, интегрированные с Qik, или добавить функциональность в уже существующие. Можно написать собственную систему нотификации, desktop-widget
или что-то еще.
Сегодня мы открываем API для работы c Qik Push Engine. Это первая ласточка в большом списке API, открывающих доступ к платформе стриминга Qik. Если вам интересно посмотреть Qik Push Engine в действии, заходите на одну из страниц примеров.
( Read the rest of this entry » )
|
June 8th, 2009
10:33 am - AMQP по-русски
Originally published at Блог Андрея Смирнова. Please leave any comments there. Сегодня довольно мало информации о протоколе AMQP (Advanced Message Queueing Protocol) и его применении, особенно русском языке. А вообще это – замечательный, уже достаточно широко поддерживаемый открытый протокол для передачи сообщений между компонентами системы с низкой задержкой и на высокой скорости. При этом семантика обмена сообщениями настраивается под нужды конкретного проекта. Такие решения существовали и ранее, но это первый стандарт, для которого существует большое количество свободных реализаций.
Основная идея состоит в том, что отдельные подсистемы (или независимые приложения) могут обмениваться произвольным образом сообщениями через AMQP-брокер, который осуществляет маршрутизацию, возможно гарантирует доставку, распределение потоков данных, подписку на нужные типы сообщений. В качестве классических примеров обычно приводятся финансовые приложения, связанные, например, с доставкой потребителям информации о курсах ценных бумаг в режиме реального времени, также возможно RPC-взаимодействие двух подсистем, которые не имеют связи друг с другом (взаимодействие через общий протокол AMQP) и так далее и тому подобное.
Сегодня тема доставки информации в реальном времени является крайне актуальной (достаточно вспомнить хотя бы Twitter, Google Wave). И здесь системы передачи сообщений могут служить внутренним механизмом обмена данными, который обеспечивает доставку данных (изменений данных) клиентам.
Я не ставлю своей целью сегодня рассказать о том, как писать приложения для AMQP. Хочу лишь немного рассказать о том, что это совсем не страшно, не очень сложно, и действительно работает, хотя стандарт находится еще в развитии, выходят новые версии протокола, брокеров и т.п. Но это уже вполне production-quality. Расскажу лишь базовые советы, чтобы помочь «въехать» в протокол.
( Read the rest of this entry » )
|
June 7th, 2009
09:30 pm - FMSPy, релиз Alpha (0.1)
Originally published at Блог Андрея Смирнова. Please leave any comments there. Flash Media Server written in Python (FMSPy) - это еще один RTMP-сервер для приложений на Adobe Flash/Flex/Air. FMSPy является аналогом Adobe Flash Media Server, с гораздо меньшими возможностями, однако FMSPy - совершенно бесплатный проект с открытым исходным кодом. Проект находится на ранней стадии развития, но в активной разработке.
Итак, что есть на сегодняшний день:
- Реализация RTMP-протокола: кодирование/декодирование пакетов, разрезание и склеивание из chunks и т.п.
- Поддержка базового RPC (Invoke) клиент-сервер и сервер-клиент. То есть из Flash-приложения можно вызывать с помощью класса NetConnection методы приложения на стороне сервера, и наоборот со стороны сервера вызывать методы приложения.
- Инфраструктура для написания приложений (в качестве плагинов к FMSPy) со своим API на Python.
( Read the rest of this entry » )
|
|
|