Не так давно была опубликована статья о ICQ на Python , что и побудило меня развивать тему, хоть и немного в другом направлении.
Несколько лет назад у меня были трудности с домашним интернетом: доступ только в локальную сеть, для связи с внешним миром только ICQ и локальный Jabber-сервер; другого выхода не было.
В результате родилась идея туннелировать HTTP-трафик в XMPP.
Схема
Схема основана на трех основных компонентах:- бот-сервер : получает сообщения с HTTP-запросами, выполняет, кодирует и отправляет результат клиенту
- бот-клиент : отправляет на сервер информацию о HTTP-запросах, которые необходимо выполнить, ждет результата, обрабатывает и возвращает результат запроса, готовый для дальнейшего использования
- http-прокси : прокси-сервер, обрабатывающий HTTP-запросы с помощью клиента-бота.
На локальный хост запускаются бот-клиент и прокси; клиентские приложения настроены на использование нашего прокси, например:
local +-----------------------------------------------------------------------------------+ | http-client (browser, wget) -> http-proxy -> bot-client | +-----------------------------------------------------------------------------------+ /\ || \/ remote +-----------------------------------------------------------------------------------+ | bot-server | +-----------------------------------------------------------------------------------+Для взаимодействия с клиентским ботом и серверным ботом используется простой протокол на основе XML. Запрос на загрузку индексной страницы example.com :
$ http_proxy="localhost:3128" wget .
Отвечать:
<url> http://example.com </url>
Ответ состоит из нескольких частей, кусков.
Здесь кусок — номер чанка, считать — общее количество кусков, на которые был разбит ответ на запрос.
закодированные_данные — фрагмент ответа, закодированный в base64.
Для большей наглядности представлю схему графически: <answer chunk="2" count="19"><data>encoded_data</data></answer>
Выполнение
Общая информация
Используется для работы с XMPP xmpppy .Никаких особых функций не требуется, вам просто нужно обрабатывать входящие сообщения и отправлять ответы.
XML анализируется и генерируется с использованием стандартной библиотеки — xml.dom.minidom .
Бот-сервер
Задача сервера — получать запросы на загрузку, отправлять их в библиотеку, которая сама выяснит, что нужно скачать, и вернет результат, а сервер перешлет этот результат клиенту.
В упрощенной схеме обработка сообщений на стороне сервера выглядит так: import xmpp
from Fetcher import Fetcher
fetcher = None
def message_callback(con, msg):
global fetcher
if msg.getBody():
try:
ret = fetcher.process_command(msg.getBody())
except:
ret = ["failed to process command"]
for i in ret:
reply = xmpp.Message(msg.getFrom(), i)
reply.setType('chat')
con.send(reply)
if __name__ == "__main__":
jid = xmpp.JID("[email protected]")
user = jid.getNode()
server = jid.getDomain()
password = "secret"
conn = xmpp.Client(server, debug=[])
conres = conn.connect()
authres = conn.auth(user, password, resource="foo")
conn.RegisterHandler('message', message_callback)
conn.sendInitPresence()
fetcher = Fetcher()
while True:
conn.Process(1)
Я намеренно удалил обработку ошибок и жестко запрограммировал значения, чтобы сделать код более компактным и удобным для чтения.
Так что же здесь происходит? Подключаемся к jabber-серверу и прикрепляем обработчик сообщений: conn.RegisterHandler('message', message_callback)
Таким образом, для каждого нового входящего сообщения будет вызываться наша функция message_callback (кон, сообщение) , аргументами которого будут дескриптор соединения и само сообщение.
Сама функция вызывает обработчик команды из класса Сборщик , который выполняет всю грязную работу и возвращает список фрагментов, переданных клиенту.
Вот и все, на этом работа сервера заканчивается.
Сборщик
Сорт Сборщик реализует саму логику выполнения и кодирования HTTP-запросов.
Я не буду приводить весь код; его можно посмотреть в архиве, прикрепленном к статье; Опишу только основные моменты: def process_command(self, command):
doc = xml.dom.minidom.parseString(command)
url = self._gettext(doc.getElementsByTagName("url")[0].
childNodes)
try:
f = urllib2.urlopen(url)
except Exception, err:
return ["%s" % str(err)]
lines = base64.b64encode(f.read())
ret = []
chunk_size = 1024
x = 0
n = 1
chunk_count = (len(lines) + chunk_size - 1) / chunk_size
while x < len(lines):
ret.append(self._prepare_chunk(n, chunk_count, lines[x:x + chunk_size]))
x += chunk_size
n += 1
return ret
Функция команда_процесса , как вы, наверное, помните, вызывает наш бот-сервер.
Он анализирует XML-запрос, определяет, какой URL-адрес ему необходимо запросить, и делает это, используя urllib2 .
Загруженное сообщение кодируется в формате Base64, чтобы не возникло неожиданных проблем со специальными символами, и делится на равные части, чтобы не достичь ограничения длины сообщения.
Затем каждый фрагмент упаковывается в XML и отправляется.
Клиент
Клиент, по сути, — это всего лишь обратный вызов, который объединяет данные и декодирует их из base64: def message_callback(con, msg):
global fetcher, output, result
if msg.getBody():
message = msg.getBody()
chunks, count, data = fetcher.parse_answer(message)
output.append(data)
if chunks == count:
result = base64.b64decode(''.
join(output))
Прокси
Для прозрачного использования туннеля реализован HTTP-прокси.Прокси-сервер привязывается к порту 3128/tcp и ожидает запросов.
Поступившие запросы передаются на сервер бота для обработки, результат расшифровывается и передается клиенту.
С точки зрения клиентских приложений наши прокси ничем не отличаются от «обычных».
Чтобы создать TCP-сервер, используйте SocketServer.StreamRequestHandler из стандартной библиотеки.
class RequestHandler(SocketServer.StreamRequestHandler):
def handle(self):
data = self.request.recv(1024)
method, url, headers = parse_http_request(data)
if url is not None:
response = fetch_file(server_jid, client_jid, password, url)
self.wfile.write(response)
self.request.close()
Функция parse_http_request() анализирует HTTP-запрос, извлекая из него URL-адрес, заголовки и версию http; выборка_файла() - спрашивает URL с помощью бот-клиента.
Заключение
Доступен полный исходный код Здесь в виде шар-архива (нужно запустить файл и выполнить его как шелл-скрипт).Конечно, это скорее прототип, чем полноценное приложение, но прототип работает и, по крайней мере, без проблем скачивает небольшие файлы.
Этого должно быть достаточно для основной цели статьи: продемонстрировать «неинтерактивное» применение IM-бота.
В проекте есть много чего, что можно улучшить — от добавления аутентификации, нормальной поддержки типов запросов, до работы над производительностью.
Очень интересно, какой производительности можно добиться с помощью такой архитектуры, которую я, возможно, скоро займусь исследованием.
Теги: #python #bot #jabber bot #туннелирование #python
-
Полный Обзор Возможностей Motorola Zine Zn5
19 Oct, 24 -
Обнаружен Первый Кольцевой Астероид
19 Oct, 24 -
Способ Игры: Играйте С Незнакомцем
19 Oct, 24