Содержание
Список примеров
Copyright (c) 2007 Connexion project, Peter V. Saveliev.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be found on the GNU site[1].
Ядро Connexion не несёт в себе механизмов аутентификации (вполне умышленно), поэтому за предоставление авторизационных данных отвечает или клиент, или ядро операционной системы. Для того, чтобы полученным данным можно было верить, канал, по которому они получены, должен быть защищённым. На данный момент сервер Connexion может работать на UNIX-сокетах и на TCP-сокетах. В силу малой возможности защитить TCP-сокет, в текущей реализации его нужно использовать только в отладочных целях.
По умолчанию, Connexion создаёт untrusted сокет в TMPDIR, но можно также и явно задать путь. Untrusted означает, что клиент не должен передавать авторизационные данные. Вместо этого, Connexion будет их брать через интерфейсы ядра операционной системы, получая EUID, EGID и PID процесса клиента.
По умолчанию, Connexion не создаёт trusted сокет, но можно явно попросить это сделать. Trusted означает, что авторизационные данные будут переданы клиентом. Для того, чтобы этим данным можно было доверять, программа-клиент должна работать из-под доверенного аккаунта, а сам сокет должен быть защищён правами на файловой системе.
Этот интерфейс полезен, когда одна программа-клиент обслуживает нескольких операторов, скажем, это может быть веб-сервер консоли управления. В этом случае аутентификация проводится на стороне интерфейса.
По умолчанию, Connexion не создаёт TCP сокет. Параметр `-n` указывает, на каком адресе слушать. Если не указано явно, сервер будет слушать на всех доступных адресах. Параметр `-p` указывает порт. Если порт не указан, TCP сокет не будет создан.
TCP сокет по умолчанию является trusted, так как нет простой возможности проверить авторизационные данные отправителя запроса.
XML-RPC интерфейс Connexion на TCP сокете в текущей реализации можно использовать только для целей отладки
Если вы не увереы в работе реализации XML-RPC или сами пишете свою реализацию для необычного языка (например,
для XGawk), то можно запускать Connexion со включённым отладочным выводом протокола. Для этого нужно использовать
ключ -d
Пример 4. Отладка XML-RPC
$ ./connexion.py -x -p 8090 -d Debug shell started. Autocomplete does not work. See 'help' for details Executable file was started from ./connexion.py # <?xml version='1.0'?> <methodCall> <methodName>start</methodName> <params> <param> <value><struct> <member> <name>pid</name> <value><int>0</int></value> </member> <member> <name>group</name> <value><string>peet</string></value> </member> <member> <name>user</name> <value><string>peet</string></value> </member> </struct></value> </param> </params> </methodCall> <?xml version='1.0'?> <methodResponse> <params> <param> <value><string>d65e991ade9a3ae966fd0080d50be0f6</string></value> </param> </params> </methodResponse>
creds = { "user": string, "group": string, "pid": int }
Эту структуру ядро Connexion получает первым аргументом при каждом вызове со стороны интерфейса. Однако, разработчику нужно её использовать только в случае доверенных соединений, как на UNIX-, так и на TCP-сокетах. Именно в этих случаях ядро ожидает, что аутентификацию пользователя будет производить интерфейс, и доверяет переданной от интерфейса информации.
По умолчанию же ядро не доверяет этой информации и не ожидает её получения со стороны интерфейса (untrusted sockets). В случае, если вызов делается через untrusted socket, необходимо передавать ядру на один аргумент меньше, т.к. ядро Connexion получит эту информацию из операционной системы.
В полях нужно передать имена пользователя и группы в виде строк (например, "nobody") и (необязательно пока) process id.
Реализацию доступных интерфейсу методов можно найти в файле gateway/core.py
,
класс proxy
.
sid = start(creds)
Возвращает идентификатор сессии (sid) в виде строкового идентификатора (UUID) В дальнейшем, во всех обращениях в рамках этой сессии нужно использовать полученный sid. Можно использовать и уже полученный идентификатор незаконченной когда-то сессии. Использовать чужой session id не получится, т.к. пользователю доступны только открытые им сессии.
result = init(creds,opts) result = { "prmopt": string } opts = { ## # параметр передавать не обязательно, он зарезервирован # для грядущих нужд ## }
Этот вызов на данный момент не требует никаких аргументов (аргумент opts
является необязательным и в текущей версии не анализируется). Вызов init()
возвращает словарь, на данный момент несущий один ключ: prompt
, это приглашение
командной строки и имя основной иерархии модулей.
dict = list(creds,sid,text)
Получить список возможных команд или параметров для текущего состояния (если не указан text) или для состояния с префиксом text.
result = parse(creds,sid,clist) ### # clist — список команд на исполнение ### clist = [ cspec, cspec, ... ] ### # cspec — описание команды ### cspec = [ command line, strings ] ### # strings — список строк многострочного ввода ### strings = [ string, string, ... ]
Команда принимает в качестве аргументов идентификатор сессии и список команд на исполнение. Каждая позиция в списке команд состоит из непосредственно строчки вызова и списка строк многострочного ввода.
После вызова `parse()` всегда надо делать вызов `input()`, чтобы получить как минимум код возврата (в случае синтаксической ошибки он всегда 255).
Дальше идёт необработанный текст из wiki, в процессе импортирования... Be patient.
=== Получение и передача данных === '''dict input(creds,sid,text)''' Переменная text несёт в себе данные, которые нужно передать сервису (нужно для интерактивных команд). Она может быть пустая. Работа с сервисами (потенциально запускающими и интерактивные команды) через синхронный протокол (каким и является XML-RPC) должна осуществляться по следующей схеме: 1. вызов `parse()` 1. если поле `error` присутствует, информируем пользователя 1. вызов `input()` 1. анализ: если queue == 0 и есть поле retval, это была последняя транзация, рвём цикл 1. если нет, продолжаем в цикле опрашивать `input()` с возможным вводом от пользователя либо без такового 1. если есть управляющий ввод, вызываем `interrupt()` См. также пример сессии дальше. === Управляющие команды === '''boolean interrupt(creds,sid,code)''' Послать управляющую команду сервису. Например, при нажатии Ctrl-C можно вызывать `interrupt(creds,sid,"c")`, что в сервисах по умолчанию означает отсылку SIGINT текущему процессу. Метод всегда возвращает True. Если на исполнении нет сервисов, то управляющие команды буферизуются (FIXME: нужно ограничить размер очереди, потенциальная атака) и будут отправлены первому же запущенному сервису. === Завершение сессии === '''boolean terminate(creds,sid)''' Ядро Connexion не удаляет сессии, если об этом не попросить явно. Это пока что :) Соответственно, чтобы не было утечек (а каждая сессия занимает память, пусть и немного), ненужные сессии необходимо терминировать. Возвращает True (если такая сессия была) или False (если не было доступной сессии по переданным данным) == Примеры == === Пример клиентской сессии === Для примера был взят Python. Коментарии записаны с `#`. {{{ [peet@localhost ~]$ python Python 2.4.4 (#1, May 15 2007, 20:42:45) [GCC 4.1.1 20070105 (ALT Linux, build 4.1.1-alt11)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import xmlrpclib >>> session = xmlrpclib.Server("http://192.168.1.248:8100") >>> creds = {"user": "peet", "group": "peet", "pid": 0} # стартуем сессию >>> sid = session.start(creds) # запускаем команду show interfaces address >>> session.parse(creds,sid,[["show interfaces address",[]]]) {'prompt': 'xshell'} # начинаем в цикле забирать вывод >>> session.input(creds,sid,"") {'queue': 1, 'data': 'MIME-Version: 1.0\nContent-Type: text/plain; charset="utf-8"\nContent-Transfer-Encoding: base64\n\nMTogbG86IDxMT09QQkFDSyxVUCxMT1dFUl9VUD4gbXR1I DE2NDM2IHFkaXNjIHNmcSAKICAgIGxpbmsvbG9vcGJhY2sgMDA6MDA6 # # пропущена часть вывода # QLExPV0VSX1VQPiBtdHUgMTI4MCBxZGlzYyBwZmlmb19mYXN0IHFsZW 4gMwogICAgbGluay9wcHAgCiAgICBpbmV0IDEwLjAuMC4yIHBlZXIgM TAuMC4wLjEvMzIgc2NvcGUgZ2xvYmFsIHBwcDAK'} # очередь исчерпана, получаем queue == 0, а заодно exit status >>> session.input(creds,sid,"") {'queue': 0, 'retval': 0} # если при пустой очереди спросить input(), то получаем False после # секундного таймаута >>> session.input(creds,sid,"") {'queue': 0, 'output': False} }}} === Ещё один пример запуска `parse()` === Агрегация нескольких команд в одну используется при буферизации ввода, полезно для ускорения работы скриптов: вместо 150, скажем, вызовов `parse()` можно делать один. Например, см. source:/interfaces/console/branches/0.4.4/shell.py@558#L251 {{{ session.parse(creds,sid,[ [ "show interfaces address",[]], [ "show ip route",[]], [ "!", ["that's a multiline input","for a comment"]] ]) }}} === Пример простейшего клиента === Он ничего не умеет, зато работает :) {{{ #!python #!/usr/bin/python import xmlrpclib creds = { "user": "peet", "group": "peet", "pid": 0 } session = xmlrpclib.Server("http://localhost:8090") sid = session.start(creds) lst = session.list(creds,sid) for i in lst[1].keys(): print "%-16s - %s" % (i,lst[1][i]["description"]) session.terminate(creds,sid) }}} {{{ #!html </div> }}}