Вы находитесь на странице: 1из 59

Министерство образования и науки Российской Федерации

федеральное государственное автономное образовательное учреждение


высшего образования
«Санкт-Петербургский политехнический университет Петра Великого»

Институт компьютерных наук и технологий


Высшая инженерная школа

Проект допущен к защите


Директор ИКНТ, проф., д.т.н.
_________ Заборовский В.С.
«____» ____________ 2016 г.

ВЫПУСКНАЯ РАБОТА БАКАЛАВРА

РАЗРАБОТКА КОДОГЕНЕРАТОРА КЛИЕНТА RESTFUL


СЛУЖБ ДЛЯ PYTHON
направление: 09.03.01 «Информатика и вычислительная техника»
(профиль «Вычислительные машины, комплексы, системы и сети»)

Выполнила:
Мелякова Екатерина Валерьевна
Подпись______________
Руководитель:
старший преподаватель
Кетов Дмитрий Владимирович
Подпись______________

Санкт-Петербург 2016
СОДЕРЖАНИЕ

ВВЕДЕНИЕ .................................................................................................................. 3 

1 ОБЗОР ПРЕДМЕТНОЙ ОБЛАСТИ ..................................................................... 4

1.1  Использование языков описания веб-служб для генерации кода ............... 4 


1.2  Использование WADL для описания RESTful веб-службы ......................... 7 
1.3  Генерация клиента веб-службы на основе WADL ........................................ 9 
2  ЦЕЛЬ И ЗАДАЧИ РАБОТЫ ................................................................................ 15 

3  РАЗРАБОТКА КОДОГЕНЕРАТОРА ДЛЯ PYTHON ........................................ 17 

3.1  Обоснование выбора языка программирования и описание алгоритма .. 17 


3.2  Реализация синтаксического анализа файла WADL .................................. 20 
3.3  Обработка элементов resource и генерация кода классов .......................... 21 
3.4  Обработка элементов method и генерация кода функций .......................... 24 
4  ИСПЫТАНИЯ И ТЕСТИРОВАНИЕ ................................................................. 29 

4.1  Испытание работы кодогенератора .............................................................. 29 


4.2  Тестирование сгенерированного кода клиента с веб-службой
Thomas Bayer ......................................................................................................... 30 
4.3  Тестирование сгенерированного кода клиента с веб-службой
AppDirect ................................................................................................................ 36 
4.4  Тестирование сгенерированного кода клиента с веб-службой
Launchpad ............................................................................................................... 37 
ЗАКЛЮЧЕНИЕ ......................................................................................................... 41 

СПИСОК ИСПОЛЬЗОВАННОЙ ЛИТЕРАТУРЫ .................................................. 43 

Приложение 1 ............................................................................................................ 46 

Приложение 2 ............................................................................................................ 55 

Приложение 3 ............................................................................................................ 56 

Приложение 4 ............................................................................................................ 58 


ВВЕДЕНИЕ

Выпускная работа бакалавра на тему: «Разработка кодогенератора


клиента RESTful служб для Python». Целью данной работы является разработка
программы, принимающей в качестве входных данных файл с описанием
RESTful веб-службы и генерирующей на его основе модуль на языке Python,
позволяющий взаимодействовать с соответствующей веб-службой: отправлять
запросы посредством поддерживаемых службой методов и принимать ответы в
выбранном формате из числа поддерживаемых службой.
В первом разделе работы произведен обзор существующих языков
описания RESTful веб-служб и возможности их применения для генерации кода
клиентских приложений. Был рассмотрен более подробно WADL, как один из
распространенных языков описания, соответствующий всем требованиям для
описания RESTful веб-служб. Были исследованы существующие утилиты
генерации кода на основе WADL, в ходе чего были обнаружены серьезные
недостатки существующего генератора кода на языке Python, что обусловило
актуальность разработки кодогенератора именно для данного языка описания.
Во втором разделе сформулированы задачи, выполнение которых
необходимо для достижения поставленной цели работы, а также выделены
основные требования к разрабатываемому кодогенератору.
Разработанный кодогенератор представляет собой утилиту командной
строки на языке Python. В третьем разделе отражена общая схема работы
кодогенератора, а также рассмотрены основные функции, обеспечивающие
синтаксический анализ входного файла на языке WADL, выделение и обработку
необходимой информации для установления соединения, отправки запроса и
получения ответа от веб-службы в необходимом поддерживаемом формате.
В четвертом разделе отражено проведенное тестирование кодогенератора,
в том числе испытание вариантов некорректного запуска программы и
тестирование работы сгенерированного кода для трех сторонних веб-служб.

3
1 ОБЗОР ПРЕДМЕТНОЙ ОБЛАСТИ

1.1 Использование языков описания веб-служб для генерации кода

Публикация веб-службы (как поставщика услуги, то есть серверной


части) предполагает создание сопровождающей документации для
разработчиков клиентов (потребителей сервиса). Документация в данном
случае подразумевает не только документы в привычном смысле, но и
документы, составленные на языке описания веб-служб. Такой документ в
машиночитаемом формате описывает структуру веб-службы и интерфейсов
взаимодействия с ней, устанавливает типы данных, принимаемые параметры и
названия функций, а также указывает URI для доступа к службе. [4, 29]

Рисунок 1.1 – Взаимодействие веб-службы и клиента


В случае с веб-службами, основанными на протоколе SOAP,
общепринятым протоколом описания является WSDL (Web Services Description
Language). WSDL документ содержит описание типов и элементов данных,
используемых веб-службой, а также список операций, которые могут быть
выполнены с сообщениями и способы их доставки. WSDL соответствующей

4
веб-службы разработчик клиента может получить с помощью реестра UDDI
(Universal Description, Discovery and Integration). [30]
Для RESTful веб-служб в настоящее время невозможно выделить один
общепринятый язык описания. Более того, зачастую разработчики
предоставляют только литературное описание веб-службы и параметров,
необходимых для разработки клиента. Для составления машиночитаемого
описания RESTful веб-службы в настоящее время используются такие языки
описания, как WSDL, упомянутый выше, WADL (Web Application Description
Language), RAML (RESTful API Modeling Language), OpenAPI Specification
(ранее называвшийся Swagger), Blueprint, Google Discovery Document, I/O Docs,
Hypermedia и др. [7, 8, 9, 14] Основные сведения о языках описания RESTful
веб-служб приведены в таблице 1.1. [6, 12, 13, 15, 19, 27, 28]
Таблица 1.1
Основные сведения о языках описания RESTful веб-служб
WSDL WADL OpenAPI Blueprint RAML
Дата первого
2000 2009 2011 2013 2013
релиза
Текущая версия 2.0 20090831 2.0 1A9 1.0
Формат
представления XML XML JSON Markdown YAML
данных
IBM,
Sun
Разработчик Microsoft, Reverb Apiary Mulesoft
Microsystems
Ariba

Из перечисленных выше языков только WSDL утвержден в качестве


рекомендации Консорциум Всемирной паутины [28], WADL был представлен на
рассмотрение Консорциуму, однако пока не утвержден в качестве рекомендации
[27], остальные языки в настоящее время также не утверждены.
Основной задачей языка описания RESTful веб-служб можно считать
возможность определить:
– Точки входа для данного приложения (веб-службы);
– Пути к предоставляемым ресурсам;

5
– Методы, используемые для доступа к данным ресурсам (GET,
POST, PUT и т.д.);
– Параметры, которые должны быть применены для использования
данных методов (Query, Template, HTTP Header и т.д.);
– Формат входящих и исходящих сообщений и представлений
данных (JSON Schema, XML Schema, Relax NG и т.д.);
– Коды статусов и сообщений об ошибках;
– Информацию обо всех вышеперечисленных элементах (их
описание) [10, 11, 16, 20, 21].
Однако основной сферой применения документов, составленных на
языке описания веб-службы, является использование их для генерации кода
клиентских приложений и настройки имеющихся клиентов. Наличие широких
возможностей по автоматической генерации кода с помощью готовых утилит
можно считать одним из ключевых факторов широкого применения веб-служб,
основанных на использовании протокола SOAP. Различные среды разработки
(Integrated Development Environment, IDE) позволяют генерировать как сам
WSDL-файл для уже разработанной веб-службы, так и код клиента веб-службы
на основе соответствующего WSDL-файла. [5] Был произведен обзор
имеющихся утилит для генерации кода на основе языков описания RESTful веб-
служб, результаты приведены в таблице 1.2. [15, 19, 22, 24, 25]
Таблица 1.2
Наличие генераторов кода на основе языков описания веб-служб

WADL Blueprint RAML OpenAPI


А 1 2 3 4
Генерация кода Java, Java, Node.js, Node.js, PHP,
веб-службы JavaScript, Python, .NET, Python,
Ruby, C#, Go Ruby, Scala,
ASP.NET Java, Haskell,
ASP.NET

6
Продолжение таблицы 1.2
А 1 2 3 4
Генерация кода Java, PHP, Java, Java, Java,
клиента Ruby, Python, JavaScript, JavaScript, JavaScript,
C# Ruby, C# Go, Python, Scala,
PHP, Ruby Objective-C,
Android, C#,
Flash, Python,
PHP, Ruby,
Dart, Go, Perl
Автоматическая Java ASP.NET Java Java, Node.js
генерация (go, python)
файла описания

Несмотря на то, что WSDL версии 2.0 был дополнен поддержкой всех
запросов HTTP (версия 1.1 поддерживала только методы GET и POST), что
позиционируется как улучшенная поддержка REST [28], на практике он редко
применяется для описания RESTful веб-служб. В то же время WADL
изначально разрабатывался сотрудниками компании Sun Microsystems для
RESTful веб-служб. [26]

1.2 Использование WADL для описания RESTful веб-службы

В документе WADL веб-служба описывается в виде набора ресурсов


(элемент resource), объединенных корневым элементом resources, имеющим
атрибут base, указывающий базовый URI веб-службы. Элементы resource, в
свою очередь, имеют атрибут path, указывающий путь к ресурсам относительно
базового адреса. Кроме того, WADL позволяет ресурсам иметь динамическую

7
переменную в строке адреса, которая будет подставлена во время выполнения
запроса – за счет значения template у атрибута style. [23, 27]
<resources base="http://www.thomas-bayer.com">
<resource path="/CUSTOMER/{CustomerId}">
<param name="CustomerId" style="template" type="xsd:int"/>
...
</resource>
</resources>
Каждый ресурс может содержать элемент method, который содержит
дочерний элемент request, описывающий запрос к веб-службе, и может
содержать элементы response, описывающие ответы веб-службы. Формат
запросов и ответов может быть определен с помощью элемента representation c
атрибутом mediaType.
<resource path="/namesincountry.groovy">
<method name="GET">
<request>
<param name="country" style="query" type="xs:string"/>
</request>
<response status="200">
<representation mediaType="application/xml/>
</response>
<response status="400">
<representation mediaType="application/xml/>
</response>
</method>
</resource>

Запросы могут иметь параметры, описываемые дочерними элементами


param. Параметры могут быть отмечены как обязательные (атрибут required),
могут иметь значение по умолчанию (атрибут default) или фиксированное
значение (атрибут fixed), могут повторяться (атрибут repeating), а также могут
принимать только заданные варианты значений, указываемых с помощью
значений атрибута value у дочерних элементов option.
<method name="GET" id="ItemSearch">
<request>
<param name="AppId" type="xsd:int" style="query" required="true"/>
<param name="Operation" style="query" fixed="ItemSearch"/>
<param name="Type" style="query" default="all">

8
<option value="all"/>
<option value="any"/>
<option value="phrase"/>
</param>
<param name="RespSize" style="query" type="xsd:string" repeating="true">
<option value="Small"/>
<option value="Medium"/>
<option value="Large"/>
<option value="Images"/>
</param>
</request>
<response status="200">
<representation mediaType="application/xml/>
</response>
</method>

WADL, как и другие языки описания, подвергается критике со стороны


одних разработчиков и имеет своих приверженцев среди других. Однако
следует отметить, что он соответствует всем необходимым требованиям для
описания RESTful веб-служб. [26] Возможно, одним из факторов недостаточно
широкого распространения данного языка является то, что он не утвержден в
качестве стандарта, что, в свою очередь, может быть обусловлено тем, что сам
REST является лишь подходом и формально не стандартизован.

1.3 Генерация клиента веб-службы на основе WADL

Несмотря на критику автоматической генерации кода, она позволяет


разработчику не только сэкономить время на написание типовых функций, но и
предоставляет ряд других преимуществ. В случае с веб-службами, изменение
интерфейса взаимодействия с серверной частью требует соответствующего
изменения клиента веб-службы. В данном случае использование генерируемого
кода на основе файла описания веб-службы позволило бы минимизировать
время, необходимое на изменение клиента. [4, 5]
В настоящее время существует несколько утилит, позволяющих
генерировать кода клиента на основе WADL-файла. Поскольку язык описания
9
WADL изначально разрабатывался сотрудниками Sun Microsystems (в
настоящее время Oracle), наибольшее количество кодогенераторов представлено
для языка Java. Данные утилиты подключаются в качестве плагинов к средам
разработки (таким как NetBeans IDE, Eclipse IDE и др.) или инструментам
сборки проектов (Apache Maven, Apache Ant) и могут быть использованы из их
интерфейса. Кроме того, существует проект, поддерживающий генерацию
клиентов на нескольких языках с использованием шаблонов.[17, 18, 24, 25]
Таблица 1.3
Основные сведения о существующих кодогенераторах на основе WADL
Glassfish Apache FIWARE REST
Wadl2java Maven REST Client Describe &
wadl2java Generator Compile
plugin
Версия v.1.1.6 v.3.1.6 v.1.0 v.0.7.2
Февраль Март 2016 Ноябрь 2014 Февраль
2014 2008
Лицензия CDDL-1.0 ASL 2.0 EPL v.1.0 ASL 2.0
Поддерживаемые Java Java Java PHP, Ruby,
языки Python, Java,
C#
Интерфейс Command-line, Command-line, Из Web-
из интерфейса из интерфейса интерфейса интерфейс
Maven или Maven Eclipse IDE
Ant

Использование Нет Нет Нет Да


шаблонов
Требования Java SDK, Java SDK, Java SDK, Поддерживае
Apache Apache Eclipse IDE мый
Maven* Maven браузер**

10
* При необходимости генерации кода во время сборки Java проекта с
помощью Maven. Возможно использование напрямую без Maven.
** Выявлена несовместимость с браузером Mozilla Firefox.
Рассмотрим один из генераторов кода клиента на языке Java – wadl2java,
разработанный в рамках проекта Glassfish. [25]

Рисунок 1.2 – Интерфейс кодогенератора wadl2java при использовании


утилиты из командной строки
Рассмотрим фрагмент файла описания WADL для ресурса,
поддерживающего метод GET с одним параметром.
<resources base="http://www.thomas-bayer.com"> (1)
<resource path="/restnames"> (2)
<resource path="/name.groovy"> (3)
<method name="GET"> (4)
<request>
<param name="name" style="query" type="xs:string"/> (5)
</request>
<response status="200">
<representation mediaType="application/xml"/> (6)
</response>
</resource>
</resource>
</resources>

Ниже представлен соответствующий ему фрагмент кода клиента,


сгенерированный утилитой wadl2java.
public class WwwThomasBayerCom { (1)
public static class Restnames {
public Restnames (...) {...} (2)
public static class NameGroovy { (3)
public NameGroovy(...) {...}
public<T >T getAsXml(String name, ...) {...} (4,6,5)
}
}
11
}

Сгенерированный код представляет собой класс, название которого


соответствует элементу resources в исходном файле описания WADL – (1).
Данный класс содержит вложенные статические классы, соответствующие
элементам resource (2), (3). В данных классах содержатся функции, отвечающие
за создание экземпляра клиента для подключения к ресурсу (название такой
функции совпадает с названием класса). Кроме того, при наличии у элемента
resource дочерних элементов method, класс будет содержать функции,
соответствующие данным элементам (4). Имя такой функции генерируется из
названия метода (в данном примере – GET) и предполагаемого формата ответа –
(6), в данном примере это XML. Если у элемента request есть дочерние
элементы param, они будут использованы в качестве аргументов данной
функции (5). Если параметр имеет варианты значений опций, для него создается
отдельный файл с перечислением (enum).
Работа данной утилиты была проверена на 36 файлах описания WADL
для различных веб-служб, при этом код не был сгенерирован для 10 файлов
описания (возникали исключения Transformer Exception, Wrapped Runtime
Exception и др.). У двух файлов были выявлены синтаксические ошибки (в
одном из них у некоторых элементов отсутствовали закрывающие тэги, в
другом некорректно было использовано пространство имен у закрывающего
тэга), в данном случае возникало исключение SAX Parse Exception. Таблица с
результатами тестирования приведена в Приложении 2.
Рассмотрим кодогенератор Rest Describe & Compile. Дистрибутив данной
утилиты представлен на сайте GitHub, кроме того, можно воспользоваться веб-
интерфейсом, доступным на сайте разработчика. Веб-интерфейс позволяет
загрузить свой файл описания WADL или воспользоваться возможностью
онлайн-генерации и корректировки файла по предоставленному URI ресурса.
[18]

12
Рисунок 1.3 – Веб-интерфейс кодогенератора Rest Describe & Compile
При генерации кода по фрагменту WADL, представленному выше, был
получен следующий фрагмент кода клиента.
class RestRequest:
def __init__(self, ): ...
def do_get_call(self, uri): ...
def do_post_call(self, uri, post_args): ...
class NameGroovy(RestRequest):
def __init__(self, name = None):
RestRequest.__init__(self, )
self.name = name
def prepare_params(self):
import urllib
params = {}
if self.name:
params['name'] = self.name
return urllib.urlencode(params)
def submit(self):
request_uri = 'http://www.thomas-bayer.com//restnames//name.groovy'
return self.do_get_call(request_uri + '?' + self.prepare_params())

13
Сгенерированный код представляет собой один суперкласс RestRequest,
содержащий функции do_get_call и do_post_call, ответственные за генерацию
запроса методами GET и POST соответственно. Код данного класса
генерируется для любых исходных файлов описания WADL, независимо от
того, какие методы поддерживает ресурс. Данная особенность объясняется
использованием шаблона для генерации кода.
Также генерируются подклассы, соответствующие элементам method.
Имя подкласса соответствует атрибуту path родительского элемента resource,
поэтому если у элемента resource несколько дочерних элементов method –
классы будут сгенерированы с одинаковым именем. Элементы param
используются в качестве аргументов конструктора класса, при этом
анализируется только наличие у параметра атрибута required.
Работа данной программы была проверена на имеющихся файлах
описания WADL, при этом 32 файла не были проанализированы из-за
использования тэгов, которые программа воспринимала как некорректные,
несмотря на то, что они относились к указанному в документе пространству
имен, и 2 файла были проанализированы, но не был сгенерирован код (вероятно
по причине того, что файлы были объемом более 10 тысяч строк). Результаты
проверки приведены в Приложении 2.
Таким образом, количество генераторов кода клиента веб-службы на
основе файла описания WADL невелико. Обзор существующих разработок
показал, что генераторы кода для языка Java более проработаны, нежели для
других языков. Рассмотренный кодогенератор для Python обладает рядом
недостатков:
– ограничение использования тэгов в файле описания;
– недостаточная гибкость ввиду использования шаблона (генерация
кода, который не будет использован);
– ограниченная поддержка методов, отличных от GET и POST;
– генерация классов с одинаковым именем;
– не учитываются свойства параметров.
14
2 ЦЕЛЬ И ЗАДАЧИ РАБОТЫ

Целью дипломного проекта является разработка программы,


принимающей в качестве входных данных файл с описанием RESTful веб-
службы на языке описания WADL и генерирующей на его основе модуль на
языке Python, позволяющий взаимодействовать с соответствующей веб-
службой: отправлять запросы посредством поддерживаемых службой методов и
принимать ответы в выбранном формате из числа поддерживаемых службой.
Для достижения поставленной цели в рамках дипломной работы
необходимо решить следующие задачи:
1. Разработка алгоритма и выбор языка программирования для
разработки кодогенератора;
2. Реализация возможности загрузки необходимой для генерации
кода информации из предоставляемого описания веб-службы на
языке WADL;
3. Реализации функции генерации кода для взаимодействия с веб-
службой с учетом следующих требований:
– Избегание генерации избыточного кода (повторяющегося или
неиспользуемого);
– Генерация отдельных классов для каждого из элементов resource,
представляющих точки подключения к веб-службе (для
возможности их использования по отдельности);
– Обеспечение использования уникальных имен для генерируемых
классов и функций;
– Использование документации (при наличии таковой в файле
WADL) в качестве комментариев для создаваемых объектов;
– Генерация функций для взаимодействия с веб-службой в
зависимости от поддерживаемых ею методов;

15
– Обработка свойств параметров запроса к службе (обязательность,
повторяемость, наличие вариантов значений и др.).
4. Реализация записи полученного кода в указанный пользователем
файл;
5. Проведение испытаний работы кодогенератора;
6. Анализ результатов, полученных в ходе испытаний.

16
3 РАЗРАБОТКА КОДОГЕНЕРАТОРА ДЛЯ PYTHON

3.1 Обоснование выбора языка программирования и описание алгоритма

Для написания кодогенератора был выбран язык программирования


Python версии 3.4. Стандартная библиотека Python, включенная в пакет
установки, содержит большинство модулей, обеспечивающих решение задач,
предполагаемых данным дипломным проектом:
– для взаимодействия с файловой системой используется модуль os;
– для работы с аргументами командной строки используется модуль
argparse;
– для обработки исключений используется модуль sys;
– для работы с регулярными выражениями используется модуль re;
– для сетевого взаимодействия используются модули urllib и
http.client. [1, 2, 3, 31]
Для работы с файлами описания WADL используется сторонний модуль
defusedxml, который содержит модифицированные подклассы,
соответствующие аналогичным подклассам модулей стандартной библиотеки.
Данная сторонняя библиотека была выбрана в связи с тем, что модуль
стандартной библиотеки xml.etree считается уязвимым по отношению к файлам
XML, способным нанести вред компьютеру. [32] Поскольку файл описания
WADL может быть получен из недоверенного источника, решено использовать
более надежную библиотеку.
Разработанный кодогенератор представляет собой утилиту командной
строки, принимающую при запуске 2 обязательных параметра – путь к
исходному файлу описания веб-службы на языке WADL и путь к выходному
файлу (при указании несуществующего файла, он будет создан по указанному
пути). Если параметры не указаны или имеют неверные значения (не задан или

17
неверно указан путь к файлу WADL), выдается сообщение об ошибке и справка
об используемых параметрах.

Рисунок 3.1 – Запуск кодогенератора из командной строки

Проверка переданных параметров

Проверка существования входного файла

Открытие выходного файла на запись

Запись в файл общей информации (импорт


модулей, дисклеймер, общие функции)

Синтаксический анализ входного файла


WADL

Вызов рекурсивной функции для обработки


элементов resource и генерации кода

Запись полученного кода в файл

Рисунок 3.2 – Общая схема работы кодогенератора


Общая схема работы разработанного кодогенератора представлена на
рисунке. После проведения проверок (проверка количества переданных
параметров, проверка наличия входного файла и возможности записи в
выходной файл) в выходной файл записывается общая для всех генерируемых в
18
последующем функций часть. К ней относятся дисклеймер (информация о том,
что модуль был сгенерирован автоматически, и заявление об отказе от
ответственности автора кодогенератора за некорректное использование
сгенерированных функций), строки, отвечающие за импорт необходимых
модулей Python, а также код 2 общих функций, которые будут использоваться в
дальнейшем: setconnection и interact.
Функция setconnection предназначена для установления соединения по
протоколам HTTP/HTTPS. Функция принимает в качестве аргумента URI веб-
службы, из которого посредством функции urlparse из библиотеки urllib.parse
выделяется протокол взаимодействия, и в зависимости от него устанавливается
соединение с помощью функций модуля http.client – HTTPConnection и
HTTPSConnection соответственно. В качестве результата работы функция
возвращает установленное соединение.
Функция interact предназначена для отправки запроса и получения
ответа от веб-службы. Функция принимает в качестве первого параметра
установленное соединение, которое будет использоваться для отправки запроса
к веб-службе, используя остальные входные параметры - название метода,
относительный адрес ресурса, а также, при наличии, данные, которые должны
быть переданы (для методов POST, PUT и др.) и заголовок HTTP в формате
словаря. При этом часть относительного адреса ресурса при необходимости
может выделяться из URI веб-службы (зачастую в файле описания WADL
атрибут baseurl содержит не только адрес источника, но и часть относительного
пути). Отправка запроса реализуется с помощью функции request модуля
http.client. Затем с помощью установленного соединения принимается ответ от
веб-службы в формате byte, которые декодируются и возвращаются
пользователю в качестве результата работы функции.

19
3.2 Реализация синтаксического анализа файла WADL

После записи общей функции в выходной файл, начинается работа с


файлом описания WADL, который был передан на вход кодогенератору.
Синтаксический анализ (парсинг) файла WADL осуществляется с помощью
функции fromstring модуля defusedxml.ElementTree. В результате выполнения
данной функции мы получаем элемент, соответствующий корневому элементу
application в файле WADL и можем обращаться к его дочерним элементам и
атрибутам. Выделяется пространство имен, используемое для корневого
элемента. Производится поиск дочерних элементов resources у полученного
корневого элемента. В рамках цикла for каждый из полученных элементов
resources передается на обработку рекурсивной функции processing_node.
Функция processing_node принимает на вход элемент WADL (это может
быть элемент resources или элемент resource), пространство имен, адрес службы
(baseurl) и относительный адрес обрабатываемого ресурса (requrl). Выполняется
поиск дочерних элементов resource. В рамках цикла for для каждого из
найденных элементов resource выполняется проверка наличия дочерних
элементов method. Если элемент resource имеет атрибут type, выполняется
поиск соответствующего элемента resource_type, и если такой элемент найден,
производится поиск дочерних элементов method. Если дочерние элементы
method найдены у элемента resource или у соответствующего ему элемента
resource_type, то такой элемент resource передается на обработку в функцию
processing_class для генерации кода соответствующего класса, при этом
найденный элемент resource_type передается в функцию в виде параметра.
После этого выполняется проверка наличия дочерних элементов resource. Если
такие элементы найдены – функция processing_node рекурсивно применяется к
ним.

20
Обработка элемента функцией
processing node

Получить список дочерних элементов resource

Для каждого элемента resource в


списке:

Нет
У resourse или resource_type естьт
дочерние элементы method?

Да

Передача элемента resource в функцию processing_class

Нет
Есть дочерние
элементы resource?

Да

Передача элемента resource в функцию processing_node

Завершение работы функции

Рисунок 3.3 – Алгоритм работы функции processing_node

3.3 Обработка элементов resource и генерация кода классов

Для обработки элементов resource, имеющих хотя бы один дочерний


элемент method или относящихся к типу resource_type, имеющему хотя бы один
дочерний элемент method используется функция processing_class.

21
Обработка элемента функцией
processing class

Создание экземпляра класса ResourceClass

Присвоение значений атрибутам baseurl, requrl, doc,


params

Выполнение функции генерации имени, присвоение


значения атрибуту name

Выполнение функции генерации кода класса

Запись сгенерированного кода в выходной файл

Получение списка дочерних элементов method

Для каждого элемента method


в списке:

Обработка элемента method функцией


processing_method

Завершение обработки элемента

Рисунок 3.4 – Алгоритм работы функции processing_class


На вход функции передается корневой элемент, рассматриваемый
элемент resource, найденный элемент resource_type (или None, если элемент
resource_type не найден), адрес службы (baseurl) и относительный адрес
обрабатываемого ресурса (requrl). Для описания получаемого на входе элемента
используется класс ResourceClass, имеющий ряд атрибутов и 3 метода:
конструктор класса, функцию генерации имени класса (generate_class_name) и
функцию, генерирующую текст кода соответствующего класса
(generate_class_code).
22
Для обрабатываемого элемента resource с помощью конструктора
создается новый экземпляр класса ResourceClass. Далее заполняются значения
атрибутов данного экземпляра класса:
– baseurl – URI веб-службы;
– requrl – относительный адрес ресурса;
– doc – документация к ресурсу;
– param_names – параметры, используемые в относительном адресе
ресурса в качестве переменных значений.
Имя для класса, которое будет присвоено атрибуту name созданного
экземпляра, генерируется с помощью функции generate_class_name из
относительного адреса ресурса относительно адреса веб-службы в целом. Т. о.
если ресурс является дочерним для другого элемента ресурс, в имени класса,
соответствующего дочернему ресурсу будет присутствовать адрес
родительского. При этом символы «/» заменяются на «_», а остальные символы
(кроме цифр и латинских букв) удаляются. К началу имени прибавляется «SEI»
– Service Endpoint Interface, что отражает, что каждый класс относится к
конкретному интерфейсу подключения к веб-службе (ресурсу),
идентифицируемому URI.
С помощью функции generate_class_code генерируется код для
выходного файла – класс, соответствующий ресурсу веб-службы.
class SEI_sqlrest_PRODUCT_ProductId:
def __init__(self, ProductId):
self.baseurl = 'http://www.thomas-bayer.com'
self.requrl = '/sqlrest/PRODUCT/{ProductId}'
self.param_names = ['ProductId']
self.param_values = [ProductId]
for i in range(len(self.param_names)):
self.requrl = re.sub('{'+self.param_names[i]+'}', str(self.param_values[i]),
self.requrl, flags=re.I)
self.connection = setconnection(self.baseurl)

В данном примере, в относительном пути используется значение


ProductId, которое будет запрашиваться у пользователя при создании экземпляра
класса и использоваться для подключения к ресурсу. Когда такие изменяющиеся
23
значения не используются, код для них генерируется и класс выглядит
следующим образом:
class SEI_sqlrest_PRODUCT:
def __init__(self):
self.baseurl = 'http://www.thomas-bayer.com'
self.requrl = '/sqlrest/PRODUCT'
self.connection = setconnection(self.baseurl)

Таким образом, генерируемый конструктор класса будет присваивать


значения атрибутам, которые будут использоваться для подключения и
устанавливает соединение с веб-службой, которое может использоваться для
отправки запросов и получения ответных данных.

3.4 Обработка элементов method и генерация кода функций

После генерации кода атрибутов класса у обрабатываемого элемента


resource (и соответствующего элемента resource_type, если такой был найден)
выполняется поиск дочерних элементов method.
В рамках цикла for для каждого из найденных элементов method
передается на обработку функции processing_method, в рамках которой
создается новый экземпляр класса MethodClass. Данный класс имеет одну
функцию для генерации кода, соответствующего рассматриваемому элементу
method и атрибуты:
– id – идентификатор метода;
– name – название метода;
– doc – документация к методу;
– request_params – словарь с информацией о параметрах запроса

24
Обработка элемента функцией
processing method

Создание экземпляра класса MethodClass

Генерация имени, присвоение значений атрибутам id и name

Есть дочерние Нет


элементы doc?

Да

Обработка дочерних элементов с документацией

Есть дочерний Нет


элемент request?

Да

Поиск и обработка дочерних элементов с параметрами

Есть дочерние
элементы response?

Для каждого элемента response:

Поиск и обработка дочерних элементов representation

Выполнение функции генерации кода функции

Запись сгенерированного кода в выходной файл

Завершение обработки элемента

Рисунок 3.5 – Алгоритм работы функции processing_method


После создания экземпляра класса присваиваются значения атрибутам
name и doc (при наличии соответствующей документации к элементу в файле

25
WADL), после чего производится поиск дочернего элемента request (согласно
документации WADL, элемент method может содержать только один дочерний
элемент request и ноль и более элементов response). [27] У элемента request
анализируются дочерние элементы param и присваивается значение атрибута
request_params.
Параметры запроса могут быть повторяемыми, обязательными, иметь
варианты значений, значение по умолчанию или фиксированное значение. Для
хранения и обработки информации о свойствах параметров была выбрана
структура словаря, в которой генерируемому имени параметра соответствует
список свойств в следующем порядке:
[Original_name, Required=True/False, Repeating=True/False,
Default=Value/None, Fixed=Value/None, Option_values=[value1, value2]/None]

Изначальное имя параметра хранится отдельно и обрабатывается уже в


ходе работы сгенерированной функции, поскольку в качестве параметров могут
быть использованы словосочетания, использующие знаки препинания и
специальные символы, а также слова, зарезервированные языком Python, такие
как «for», «return», «global» и другие.
Рассмотрим пример описания параметров в файле:
<param name="AppId" type="xsd:int" style="query" required="true"/>
<param name="Operation" style="query" fixed="ItemSearch"/>
<param name="Type" style="query" default="all">
<option value="all"/>
<option value="any"/>
<option value="phrase"/>
</param>

В данном случае в словаре request_params указанным параметрам будут


соответствовать значения вида:
{‘p_AppId’ : [‘AppId’, True, False, None, None, None],
‘p_Operation’ : [‘Operation’, False, False, None, ‘ItemSearch’, None],
‘p_Type’ : [‘Type’, False, False, ’all’, None, [‘all’, ‘any’,
‘phrase’]]}

После этого у обрабатываемого элемента method производится поиск


дочерних элементов response, у которых рассматривается наличие дочерних

26
элементов representation. У найденных дочерних элементов representation
анализируется наличие атрибута mediaType. Кроме того, может присутствовать
атрибут href, ссылающийся на другой объект representation с соответствующим
значением атрибута id, и значение атрибута mediaType может быть присвоено
там. Значения атрибутов mediaType собираются в список types, и, если он не
окажется пустым, то формат представления ответа от службы рассматривается
как необязательный параметр, с фиксированными вариантами значений и
значением по умолчанию, и к словарю request_params добавляется элемент с
ключом «_format_», при этом полученный список types добавляется в качестве
вариантов значений параметра:
{‘_format_’ : [‘_format_’, False, False, 'application/json', None,
['application/json', 'application/vnd.sun.wadl+xml']]}

Затем с помощью функции generate_method_code генерируется код для


выходного файла – код функции, соответствующей рассматриваемому методу.
Функция generate_method_code анализирует наличие параметров запроса
и при их наличии генерирует код функции так, чтобы необходимые параметры
запрашивались при вызове функции.
def SEI_restnames_namegroovy_GET (self, p_name, _format_='application/xml'):
global param_value
query_param_names = {'p_name' : 'name', '_format_' : '_format_'}
query_param_values = {'p_name' : p_name, '_format_' : _format_}
query_params_repeating = []
query_params_with_options = {'_format_': ['application/xml', 'text/html']}
header = ''
params = ''
for param in query_param_values.keys():
exec('global param_value\r\nparam_value = '+param)
if (param_value is None): continue
if (param not in query_params_repeating and isinstance(param_value, list)):
print('Parameter '+param+' cannot take multiple values.')
exit(1)
if (not isinstance(param_value, list)): param_value = [param_value]
if (param in query_params_with_options.keys()):
if (set(param_value).issubset(query_params_with_options[param])):
for p in param_value:
if (param == '_format_'): header = {'Accept':p}
27
else: params += query_param_names[param]+'='+str(p)+'&'
else:
print('Parameter '+param+' has incorrect value')
exit(1)
else:
for p in param_value:
params += query_param_names[param]+'='+str(p)+'&'
requrl = self.requrl if (params == '') else self.requrl
+'?'+parse.quote(params[:-1], safe='=&')
response = interact(self.connection, self.baseurl, 'GET', requrl,
headers=header)
return response

В данном примере, используется один обязательный параметр запроса –


p_name, который будет запрашиваться у пользователя при вызове функции и
использоваться для отправки запроса к ресурсу, а также необязательный
параметр _format_, имеющий возможные варианты значений application/xml и
text/html, со значением по умолчанию application/xml. Сгенерированная
функция проверяет, что в качестве параметра name и _format_ передано по
одному значению, а не списки (списки могут использоваться для
повторяющихся параметров), а также что значение параметра _format_
находится в списке возможных вариантов значений. После этого генерируется
строка параметров params вида “parameter1=value1&parameter2=value2”, и с
помощью функции quote модуля urllib.parse специальные символы и пробелы,
содержащиеся в данной строке, заменяются на соответствующие коды символов
в кодировке ASCII.
Для функций POST, PUT и UPDATE в качестве обязательного параметра
запрашиваются данные, которые следует передать:
def SEI_sqlrest_CUSTOMER_POST (self, _data_):
data = _data_
response = interact(self.connection, self.baseurl, 'POST', self.requrl, data)
return response

Когда параметров нет, функция будет выглядеть следующим образом:


def SEI_restnames_countriesgroovy_GET (self):
response = interact(self.connection, self.baseurl, 'GET', self.requrl)
return response

28
4 ИСПЫТАНИЯ И ТЕСТИРОВАНИЕ

4.1 Испытание работы кодогенератора

Были проверены несколько сценариев работы кодогенератора, в том


числе:
– испытание работы программы в разных операционных системах;
– запуск программы при отсутствии файла common.py;
– запуск программы с неправильными параметрами;
– передача на обработку некорректного файла описания WADL.
Работа программы была проверена на компьютерах под управлением
операционных систем Windows 7, 8.1 и 10 и Ubuntu версий 14.04, 15.10 и 16.04.
Поскольку операционная система Ubuntu имеет встроенные интерпретаторы
языка Python версий 2 и 3, необходимо либо вручную запускать необходимый
интерпретатор, либо добавлять права на запуск файла и указывать в тексте
программы, какой интерпретатор должен использоваться. В код кодогенератора
была добавлена строка, задающая интерпретатор: «#!/usr/bin/python3».
При отсутствии файла common.py, содержащего общую часть кода,
которая должна быть записана в генерируемый файл, в директории с
кодогенератором, при попытке запуска возникает исключение
FileNotFoundError, для которого была добавлена обработка с помощью
конструкции try – except.
Были проверены сценарии запуска программы с указанием
неправильных параметров:
– запуск без параметров;
– запуск без указания пути или с неправильным путем к исходному
файлу описания WADL;
– запуск без указания или с неправильным путем к выходному
файлу;

29
– указание в качестве входного или выходного файлов с запретом на
чтение и запись.
По результатам тестирования было выявлено возникновение
исключений TypeError, NameError и PermissionError, и была добавлена
обработка исключений посредством конструкций try – except.
Была проверена работа кодогенератора при подаче на вход
некорректного файла описания WADL. В данном случае возникает ошибка
синтаксического анализа – исключение xml.etree.ElementTree.ParseError. Была
добавлена обработка данного исключения с выдачей пользователю информации
о строке и позиции, на которой возникла ошибка синтаксического анализа.
Работа кодогенератора была проверена на 36 файлах WADL, полученных
из репозитория PayPal на сайте веб-сервиса для хостинга IT-проектов GitHub, а
также непосредственно с сайтов разработчиков веб-служб. Сбой работы
кодогенератора возник при обработке 2 файлов, которые были составлены с
синтаксическими ошибками (например, у некоторых элементов отсутствовали
закрывающие тэги). Результаты проверки приведены в Приложении 2.

4.2 Тестирование сгенерированного кода клиента с веб-службой Thomas


Bayer

Для проверки работоспособности сгенерированного кода клиента было


проведено тестирование на нескольких работающих RESTful веб-службах.
Были рассмотрены REST-ресурсы на сайте немецкого разработчика и
преподавателя Томаса Байера (Thomas Bayer): учебные веб-службы sqlrest и
restnames. Был составлен документ WADL, описывающий данные службы.
Корневой элемент resources с помощью атрибута base указывает URI для
подключения к веб-службе: http://www.thomas-bayer.com. Рассматриваемые
учебные веб-службы работают по протоколу HTTP, формат загружаемых и
получаемых ответных данных – XML.

30
Ресурс sqlrest располагается по относительному адресу /sqlrest и имеет
ряд дочерних ресурсов: CUSTOMER, PRODUCT, INVOICE, ITEM,
предоставляющих информацию о заказчиках, продуктах, счетах и заказах
соответственно (с помощью метода GET). Можно добавлять новые записи,
отправляя соответствующие запросы с корректными данными c помощью
метода POST. Кроме того, можно получать и удалять о конкретных заказчиках,
продуктах, счетах и заказах по относительным адресам вида /resource/{id} с
помощью методов GET и DELETE. Фрагмент WADL документа,
соответствующего ресурсу CUSTOMER и дочернему CustomerId приведен
ниже.
<resource path="/sqlrest">
<resource path="/CUSTOMER">
<method name="POST">
<request>
<representation mediaType="application/xml"/>
</request>
<response status="201"/>
</method>
<resource path="/{CustomerId}">
<param name="CustomerId" style="template" type="xs:int"/>
<method name="GET">
<request/>
<response status="200"/>
</method>
<method name="DELETE">
<request/>
<response status="200"/>
</method>
...
</resource>

Сгенерированные для данного фрагмента коды классов,


соответствующее данным ресурсам будут выглядеть следующим образом:
class SEI_sqlrest_CUSTOMER:
def __init__(self):
self.baseurl = 'http://www.thomas-bayer.com'
self.requrl = '/sqlrest/CUSTOMER'
self.connection = setconnection(self.baseurl)

31
def SEI_sqlrest_CUSTOMER_POST (self, _data_):
data = _data_
response = interact(self.connection, self.baseurl, 'POST', self.requrl,
data)
return response

class SEI_sqlrest_CUSTOMER_CustomerId:
def __init__(self, CustomerId):
self.baseurl = 'http://www.thomas-bayer.com'
self.requrl = '/sqlrest/CUSTOMER/{CustomerId}'
self.param_names = ['CustomerId']
self.param_values = [CustomerId]
for i in range(len(self.param_names)):
self.requrl = re.sub('{'+self.param_names[i]+'}', str(self.param_values[i]),
self.requrl, flags=re.I)
self.connection = setconnection(self.baseurl)

def SEI_sqlrest_CUSTOMER_CustomerId_GET (self):


response = interact(self.connection, self.baseurl, 'GET', self.requrl)
return response

def SEI_sqlrest_CUSTOMER_CustomerId_DELETE (self):


response = interact(self.connection, self.baseurl, 'DELETE', self.requrl)
return response

Для проверки работы полученного кода можно написать функцию,


которая отправит запросы к веб-службе на создание записи о клиенте,
получение информации о клиенте с указанным в качестве параметра id и
удалении данного клиента с использованием функций сгенерированного класса,
соответствующих методам POST, GET и DELETE:
if __name__ == '__main__':
data='<CUSTOMER><ID>777</ID><FIRSTNAME>Ekaterina</FIRSTNAME>
<LASTNAME>Meliakova</LASTNAME><STREET>Mayakovskogo,36</STREET>
<CITY>Saint-Petersburg</CITY></CUSTOMER>'
print('Creating new customer with id 777.')
newcustomer.SEI_sqlrest_CUSTOMER_POST(data)
print('Show customer 777 information:\r\n')
result = SEI_sqlrest_CUSTOMER_CustomerId(777)
print(result.SEI_sqlrest_CUSTOMER_CustomerId_GET())
print('\r\nDeleting customer with id 777.\r\n')

32
print(result.SEI_sqlrest_CUSTOMER_CustomerId_DELETE())

Результат выполнения данных функций (отправки запросов к службе


restnames) представлен на рисунке.

Рисунок 4.1 – Результат отправки запроса к ресурсу sqlrest с использованием


сгенерированного кода клиента
Ресурс restnames характеризуется относительным адресом /restnames и
имеет ряд дочерних ресурсов: countries.groovy, name.groovy и
namesincountry.groovy, предоставляющих информацию о странах, именах и
именах, распространенных в этих странах соответственно. Данные ресурсы
поддерживают только метод GET, при этом для обращения к ресурсам
name.groovy и namesincountry.groovy в запросе должны передаваться
обязательные параметры (имя и название страны соответственно). Фрагмент
WADL документа, соответствующего ресурсу namesincountry.groovy веб-
службы приведен ниже.
<resource path="/restnames">
<resource path="/namesincountry.groovy">
<method name="GET">
<request>
<param name="country" style="query" type="xs:string" required="true"/>
<option value="Albania"/>
...
<option value="other countries"/>
</param>
</request>
<response status="200">
33
<representation mediaType="application/xml"/>
</response>
</method>
</resource>
...
</resource>

Сгенерированный для данного фрагмента код класса, соответствующего


данному ресурсу будет выглядеть следующим образом:
class SEI_restnames_namesincountrygroovy:
def __init__(self):
self.baseurl = 'http://www.thomas-bayer.com'
self.requrl = '/restnames/namesincountry.groovy'
self.connection = setconnection(self.baseurl)

def SEI_restnames_namesincountrygroovy_GET (self, p_country,


_format_='application/xml'):
global param_value
query_param_names = {'p_country' : 'country', '_format_' : '_format_'}
query_param_values = {'p_country' : p_country, '_format_' : _format_}
query_params_repeating = []
query_params_with_options = {'p_country': ['Albania', 'Arabia/Persia',
'Austria', 'Belarus', 'Belgium', 'Bosnia and Herzegovina', 'Bulgaria', 'China',
'Croatia', 'Czech Republic', 'Denmark', 'Estonia', 'Ex-U.S.S.R. (Asian)',
'Finland', 'France', 'Frisia', 'Germany', 'Great Britain', 'Greece', 'Hungary',
'Iceland', 'India/Sri Lanka', 'Ireland', 'Israel', 'Italy', 'Japan', 'Korea',
'Kosovo', 'Latvia', 'Lithuania', 'Luxembourg', 'Macedonia', 'Malta', 'Moldova',
'Montenegro', 'Netherlands', 'Norway', 'Poland', 'Portugal', 'Romania',
'Russia', 'Serbia', 'Slovakia', 'Slovenia', 'Spain', 'Sweden', 'Swiss',
'Turkey', 'U.S.A.', 'Ukraine', 'Vietnam', 'other countries'], '_format_':
['application/xml']}
header = ''
params = ''
for param in query_param_values.keys():
exec('global param_value\r\nparam_value = '+param)
if (param_value is None): continue
if (param not in query_params_repeating and isinstance(param_value,
list)):
print('Parameter '+param+' cannot take multiple values.')
exit(1)
if (not isinstance(param_value, list)): param_value = [param_value]
if (param in query_params_with_options.keys()):

34
if (set(param_value).issubset(query_params_with_options[param])):
for p in param_value:
if (param == '_format_'): header = {'Accept':p}
else: params += query_param_names[param]+'='+str(p)+'&'
else:
print('Parameter '+param+' has incorrect value')
exit(1)
else:
for p in param_value:
params += query_param_names[param]+'='+str(p)+'&'
requrl = self.requrl if (params == '') else self.requrl
+'?'+parse.quote(params[:-1], safe='=&')
response = interact(self.connection, self.baseurl, 'GET', requrl,
headers=header)
return response

Для проверки работы полученного кода можно написать функцию,


которая отправит запрос к веб-службе для получения данных об используемых в
стране именах с использованием метода GET и указанием названия страны в
качестве параметра запроса:
if __name__ == '__main__':
instance = SEI_restnames_namesincountrygroovy()
result = instance.SEI_restnames_namesincountrygroovy_GET('Russia')
print(result)

Результат выполнения данной функции представлен на рисунке.

Рисунок 4.2 – Результат отправки запроса к ресурсу restnames с использованием


сгенерированного кода клиента

35
4.3 Тестирование сгенерированного кода клиента с веб-службой AppDirect

AppDirect – это торговая площадка для продажи облачных приложений,


основанная в 2009 году. AppDirect предоставляет платформу управления,
которая позволяет распространять веб-сервисы, а также RESTful API для
управления подписками и доступом в режиме реального времени. Компания
предоставляет описание API в виде файла описания WADL, а также текстовую
документацию. Веб-служба работает по протоколу HTTPS, формат
загружаемых и получаемых ответных данных – XML.
Файл описания данной веб-службы отличается значительным объемом –
6908 строк, использованием методов GET, POST, PATCH и DELETE, а также
использованием для доступа к ресурсам параметров запроса с вариантами
значений. При генерации кода клиента для подключения к ресурсам была
выявлена необходимость хранения названий параметров в виде текстовых
данных и подстановки их непосредственно при формировании строки адреса,
по которому будет отправлен запрос, поскольку в именах параметров данной
службы использовались знаки препинания (дефисы и точки), а также в качестве
имен использовались зарезервированные слова языка Python: «for» и «from».
Данная веб-служба использует аутентификацию OpenID, однако
некоторые ресурсы для проверки интеграции являются открытыми. Фрагмент
WADL документа, соответствующего одному из таких ресурсов веб-службы
приведен ниже. Данный ресурс выдает в формате json информацию о
предполагаемых именах доменов и о том, свободно ли данные имена.
<resource path="/integration/v1">
<resource path="/dummy/domain/suggestions">
<method name="GET">
<request>
<representation mediaType="application/xml"/>
<representation mediaType="application/json"/>
<param name="currency" style="query" type="xs:string"/>
<param name="sleep" style="query" type="xs:long"/>
<param name="q" style="query" type="xs:string"/>

36
<param name="max" style="query" type="xs:long"/>
<param name="domainAvailable" style="query" type="xs:boolean"/>
</request>
<response status="204">
</response>
</method>
</resource>
</resource>

Сгенерированный для данного фрагмента код класса, соответствующего


данному ресурсу можно проверить с помощью простой функции, которая
отправит запрос к веб-службе с использованием функции сгенерированного
класса:
if __name__ == '__main__':
instance = SEI_api_integration_v1_dummy_domain_suggestions()
result = instance.SEI_api_integration_v1_dummy_domain_suggestions_GET(p_q='acme', p_max=5)
print(result)

Результат выполнения данной функции (отправки запроса с указанием


названия компании «acme» и выводом 5 результатов) представлен на рисунке.

Рисунок 4.3 – Результат отправки запроса к службе AppDirect

4.4 Тестирование сгенерированного кода клиента с веб-службой


Launchpad

Launchpad – это веб-служба, созданное для совместной работы над


свободным ПО, разработанная и находящаяся под управлением компании
Canonical. Предоставляется описание API в виде текстовой документации, а
также файл описания WADL. Кроме того, предоставляется библиотека для
работы с ресурсами данной веб-службы – launchpadlib, однако разработчики

37
указывают, что для некоторых целей она может не подходить или быть
избыточной, поэтому для возможности написания собственных клиентских
модулей предоставляется файл описания WADL.
Однако предоставляемый файл описания WADL отличается тем, что,
несмотря на объем в почти 50 тысяч строк, не содержит информации о
конкретных адресах расположения ресурсов. Элемент resources указывает URI
веб-службы, а единственный дочерний элемент resource содержит пустой
относительный путь, то есть является корневым. Однако файл содержит
элементы resource_type, которые описывают типы ресурсов и содержат
дочерние элементы method, характеризующие поддерживаемые данным типом
ресурсов методы.
Таким образом, если добавить к данному файлу WADL описание
необходимых элементов resource с указанием соответствующих им типов
(элементов resource_type), то можно сгенерировать код классов для отправки
запросов к данным ресурсам.
В результате анализа элементов resource_type был реализован поиск
дочерних элементов method не только у элементов resource, но и, при наличии у
элемента resource ссылки на тип ресурса, у соответствующего элемента
resource_type. Кроме того, было обнаружено, что методы могут носить
одинаковые значения атрибута name (то есть названия, например ресурс может
поддерживать несколько разных методов GET), но иметь разные id, в связи с
чем была изменена процедура присвоения имени для функции,
соответствующей методу.
Был составлен фрагмент документа WADL для описания ресурсов,
относящихся к данным о зарегистрированных пользователях и их проектах.
<wadl:resource path="/people" type="#people"/>
<wadl:resource path="/~{Person}" type="#person">
<wadl:param name="Person" style="template" type="xs:string"/>
<wadl:resource path="/{Project}/+git/{Repository}/+ref/{Path}" type="#git_ref">
<wadl:param name="Project" style="template" type="xs:string"/>
<wadl:param name="Repository" style="template" type="xs:string"/>
<wadl:param name="Path" style="template" type="xs:string"/>
38
</wadl:resource>
</wadl:resource>
</wadl:resources>

За счет того, что каждый элемент resource был соотнесен с


соответствующим элементом resource_type с помощью атрибута type, для
ресурсов были сгенерированы классы с функциями, соответствующими
дочерним элементам method у элементов resource_type.
Предположим, что нам необходимо получить от веб-службы Launchpad
данные обо всех пользователях, зарегистрировавшихся в определенную дату в
виде файла csv в формате «nickname;display name;creation date».
За функцию поиска учетных записей пользователей отвечает элемент
method, описывающий специальный метод GET с идентификатором «people-
findPerson». Данный элемент method является дочерним по отношению к
элементу resource_type с идентификатором «people». Поскольку элемент
resource был соотнесен с элементом resource_type, была сгенерирована функция
поиска, принимающая в качестве обязательного параметра текст для поиска, а в
качестве необязательных – даты создания учетной записи (от и до) и формат
выходных данных.
Можно написать функцию для получения и представления данных в
необходимом формате с использованием сгенерированной функции:
if __name__ == '__main__':
outputfile = open('userlist.csv', 'a', encoding='utf-8')
instance = SEI_people()
dict = json.loads(instance.SEI_people_peoplefindPerson('', p_createdafter=
'2016-05-14',p_createdbefore='2016-05-15'))
for i in dict['entries']:
outputfile.write(str(i['name'])+';'+str(i['display_name'])+';'+
str(i['date_created'])+'\n')

Фрагмент полученного файла userlist.csv приведен ниже:


andy-lehmann;AL;2016-05-14T19:02:54.649281+00:00
chirith-ungol;ALFREDO;2016-05-14T20:33:18.168489+00:00
abhijeet1289;Abhijeet;2016-05-14T03:55:32.255289+00:00
green-akiva;Akiva;2016-05-14T01:55:32.743596+00:00
carruth;Al Carruth;2016-05-14T16:13:23.271534+00:00

39
Таким образом, проведенное тестирование на трех работающих веб-
службах выявило несколько нестандартных ситуаций, потребовавших внести
изменения в генерируемый код. После того, как особенности ресурсов,
используемых для тестирования, были учтены, генерируемый код позволил
подключаться, отправлять корректные запросы и получать ожидаемые
корректные ответы от веб-служб.

40
ЗАКЛЮЧЕНИЕ

В результате проведенной работы был разработан генератор кода


клиента RESTful веб-службы на языке Python на основе файла описания в
формате WADL. Программа представляет собой утилиту, написанную на языке
Python и запускаемую из командной строки. Генерируемый код представляет
собой классы, которые могут использоваться для отправки корректных запросов
к ресурсам веб-службы.
В первом разделе была рассмотрена возможность использования языков
описания веб-служб для генерации кода, используемого в процессе разработки
клиентов, были рассмотрены особенности языков описания RESTful веб-служб:
WSDL, WADL, RAML, OpenAPI Specification, а также Blueprint. Был
рассмотрен более подробно язык описания WADL, как один из
распространенных языков описания, соответствующий всем требованиям к
языкам описания RESTful веб-служб. Были исследованы существующие
утилиты генерации кода на основе файлов на языке описания WADL, в ходе
чего были обнаружены серьезные недостатки существующего генератора кода
на языке Python, что обусловило актуальность разработки кодогенератора
именно для данного языка описания.
Во втором разделе были сформулированы задачи, выполнение которых
необходимо для достижения поставленной цели работы, а также выделены
основные требования к разрабатываемому кодогенератору, выделенные по
результатам анализа существующих кодогенераторов: избегание генерации
повторяющегося или неиспользуемого кода, генерация отдельных классов для
отдельных ресурсов, обеспечение уникальности имен для генерируемых
классов и функций, обеспечение обработки генерируемыми функциями
параметров запроса с учетом их свойств (обязательность, повторяемость,
наличие вариантов значений и др.)

41
В третьем разделе была разработана общая схема работы
кодогенератора, а также разработаны основные функции, обеспечивающие
синтаксический анализ входного файла на языке WADL, выделение и обработку
необходимой информации для установления соединения, отправки запроса и
получения ответа от веб-службы в необходимом поддерживаемом формате.
В четвертом разделе отражено проведенное тестирование
кодогенератора, в том числе испытания вариантов некорректного запуска
самого кодогенератора или подачи на вход некорректных данных, а также
результаты тестирования генерации кода для корректных файлов WADL. Кроме
того, отражены результаты тестирования самого сгенерированного кода для
трех реально-работающих сторонних веб-служб.
По результатам проверки работоспособности кодогенератора, а также
генерируемого кода, можно сделать вывод о том, что поставленные задачи по
разработке кодогенератора, а также требования к генерируемому коду были
выполнены.

42
СПИСОК ИСПОЛЬЗОВАННОЙ ЛИТЕРАТУРЫ

1. Доусон М. Программируем на Python [Текст]. - 3-е изд.: Пер. с англ. -


СПб.: Питер, 2014 - 416 с.
2. Лутц М. Python. Карманный справочник [Текст]. - 5-е изд.: Пер. с англ. -
М.: ООО "И.Д.Вильямс", 2015 - 320 с.
3. Лутц М. Изучаем Python [Текст]. - 4-е изд.: Пер. с англ. – СПб.: Символ-
Плюс, 2011. – 1280 с.
4. Веб-служба [Электронный документ]. – (https://ru.wikipedia.org/wiki/Веб-
служба). Проверено 18.05.2016.
5. Начало работы с веб-службами RESTful [Электронный документ]. –
(https://netbeans.org/kb/docs/websvc/rest_ru.html). Проверено 18.05.2016.
6. API Blueprint [Электронный документ]. –
(https://help.apiary.io/api_101/api_blueprint_tutorial). Проверено 18.05.2016.
7. ioDocs [Электронный документ]. – (https://github.com/mashery/iodocs).
Проверено 18.05.2016.
8. Kristopher Sandoval. Top Specification Formats for REST APIs
[Электронный документ]. – (http://nordicapis.com/top-specification-formats-
for-rest-apis). Проверено 18.05.2016.
9. Laura Heritage. API Description Languages [Электронный документ]. –
(http://www.slideshare.net/SOA_Software/api-description-languages).
Проверено 18.05.2016.
10. Martin W Brennan. What HTTP/2 Means for REST APIs [Электронный
документ]. – (http://www.programmableweb.com/news/what-http2-means-
rest-apis/elsewhere-web/2015/05/12). Проверено 18.05.2016.
11. Ole Lensmar. An Overview of REST Metadata Formats: [Электронный
документ]. – (http://apiux.com/2013/04/09/rest-metadata-formats). Проверено
18.05.2016.

43
12. OpenAPI Specification [Электронный документ]. –
(http://swagger.io/specification). Проверено 18.05.2016.
13. OpenAPI Specification [Электронный документ]. –
(https://en.wikipedia.org/wiki/OpenAPI_Specification). Проверено
18.05.2016.
14. Overview of RESTful API Description Languages [Электронный документ].
– (https://en.wikipedia.org/wiki/Overview_of_RESTful_API_Description_
Languages). Проверено 18.05.2016.
15. RAML Specification [Электронный документ]. – (http://raml.org/spec.html).
Проверено 18.05.2016.
16. Representational state transfer [Электронный документ]. –
(https://en.wikipedia.org/wiki/Representational_state_transfer). Проверено
18.05.2016.
17. REST Client Generator [Электронный документ]. –
(http://catalogue.fiware.org/enablers/rest-client-generator/documentation).
Проверено 18.05.2016.
18. REST Describe & Compile Documentation [Электронный документ]. –
(http://tomayac.de/rest-describe/latest/doc). Проверено 18.05.2016.
19. RESTful API Modeling Language [Электронный документ]. –
(https://en.wikipedia.org/wiki/RAML_(software)). Проверено 18.05.2016.
20. Roger L. Costello. Building Web Services the REST Way [Электронный
документ]. – (http://www.xfront.com/REST-Web-Services.html). Проверено
18.05.2016.
21. Roy Fielding. Representational State Transfer (REST) [Электронный
документ]. – (http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_
style.htm). Проверено 18.05.2016.
22. Swagger-Codegen [Электронный документ]. – (https://github.com/swagger-
api/swagger-codegen). Проверено 18.05.2016.

44
23. Understanding WADL Documents [Электронный документ]. –
(http://docs.oracle.com/cd/E41633_01/pt853pbh1/eng/pt/tibr/concept_Understa
ndingWADLDocuments-7b7fd7.html). Проверено 18.05.2016.
24. wadl2java command line tool [Электронный документ]. –
(http://cxf.apache.org/docs/jaxrs-services-description.html#JAXRSServices
Description-wadl2javacommandlinetool). Проверено 18.05.2016.
25. wadl2java Tool Documentation [Электронный документ]. –
(https://wadl.java.net/wadl2java.html). Проверено 18.05.2016.
26. Web Application Description Language [Электронный документ]. –
(https://en.wikipedia.org/wiki/Web_Application_Description_Language).
Проверено 18.05.2016.
27. Web Application Description Language [Электронный документ]. –
(https://www.w3.org/Submission/wadl). Проверено 18.05.2016.
28. Web Services Description Language (WSDL) Version 2.0 [Электронный
документ]. – (http://www.w3.org/TR/wsdl20). Проверено 18.05.2016.
29. Web Services Glossary [Электронный документ]. –
(https://www.w3.org/TR/2004/NOTE-ws-gloss-20040211). Проверено
18.05.2016.
30. Web services protocol stack [Электронный документ]. –
(https://en.wikipedia.org/wiki/Web_services_protocol_stack). Проверено
18.05.2016.
31. Writing a REST Client in Python [Электронный документ]. –
(https://developer.atlassian.com/fecrudev/integration-tutorials/writing-a-rest-
client-in-python). Проверено 18.05.2016.
32. XML vulnerabilities [Электронный документ]. –
(https://docs.python.org/3.4/library/xml.html#xml-vulnerabilities). Проверено
18.05.2016.

45
Приложение 1

Исходный код разработанного кодогенератора

#!/usr/bin/python3
import argparse
import os
import re
import sys
from defusedxml import ElementTree
#from xml.etree import ElementTree
#If you are sure in the safety of processing WADL and do not want to install
defusedxml, uncomment the line above and comment the one two lines above.
Please, keep in mind the XML vulnerabilities in xml.etree module.

generated_names = []
global outputfile

#ResourceClass is responsible for storing information about <resource> element


and generating appropriate code
class ResourceClass:
def __init__(self):
self.name = ''
self.baseurl = ''
self.requrl = ''
self.doc = ''
self.param_names = []

def generate_class_name(self, string):


name = 'SEI'
name += re.sub('[/]','_',re.sub('[^a-zA-Z\d/]', '', string))
counter = 0
initname = name
while name in generated_names:
counter += 1
name = initname+str(counter)
generated_names.append(name)
return name

def generate_class_code (self):

46
output = generating_string('class '+self.name+':')
if (self.doc != ''): output += generating_string('\'\'\''+self.doc[:-
2]+'\'\'\'', 1)
if (self.param_names != []): temp = ', '+', '.join(self.param_names)
else: temp = ''
output += generating_string('def __init__(self'+temp+'):', 1)
output += generating_string('self.baseurl = \''+self.baseurl+'\'', 2)
output += generating_string('self.requrl = \''+self.requrl+'\'', 2)
if (temp != ''):
temp = '[\''+'\', \''.join(self.param_names)+'\']'
output += generating_string('self.addr_param_names = '+temp, 2)
output += generating_string('self.addr_param_values =
'+re.sub('\'','',temp), 2)
output += generating_string('for i in
range(len(self.addr_param_names)):\n\t\t\tself.requrl =
re.sub(\'{\'+self.addr_param_names[i]+\'}\', str(self.addr_param_values[i]),
self.requrl, flags=re.I)', 2)
output += generating_string('self.connection =
setconnection(self.baseurl)\n', 2)
return output

#MethodClass is responsible for storing information about <method> element and


generating appropriate code
class MethodClass:
def __init__(self):
self.id = ''
self.name = ''
self.doc = ''
self.request_params = {}

def generate_method_code (self, classname):


def_line = ''
params_line = ''
optiondict_line = ''
repeatlist_line = ''
response_line = 'response = interact(self.connection, self.baseurl,
'+'\''+self.name+'\''
if (self.name in ['POST', 'PATCH', 'PUT']): def_line +=', _data_'
if (self.request_params != {}):
response_line += ', requrl'
params_line = 'params = '
namesdict_line = 'query_param_names = {'

47
valuesdict_line = 'query_param_values = {'
optiondict_line += 'query_params_with_options = {'
repeatlist_line += 'query_params_repeating = ['
for i in [0, 1]:
for param in self.request_params.keys():
if (i == 0 and self.request_params[param][1] == False or i == 1 and
self.request_params[param][1] == True): continue
if (namesdict_line[-1:] != '{'): namesdict_line += ', '
namesdict_line += '\''+param+'\' : \''+self.request_params[param][0]
+'\''
if (self.request_params[param][4] != None):
params_line += '\''+self.request_params[param][0]+'='+
self.request_params[param][4]+'&\''
else:
if (self.request_params[param][1] == True):
if (valuesdict_line[-1:] != '{'): valuesdict_line += ', '
valuesdict_line += '\''+param+'\' : '+param
def_line += ', '+param
else:
if (self.request_params[param][3] != None):
if (valuesdict_line[-1:] != '{'): valuesdict_line += ', '
valuesdict_line += '\''+param+'\' : '+param
def_line += ', '+param+'=\''+self.request_params[param][3]+'\''
else:
if (valuesdict_line[-1:] != '{'): valuesdict_line += ', '
def_line += ', '+param+'=None'
valuesdict_line += '\''+param+'\' : '+param
if (self.request_params[param][5] != None):
if (optiondict_line[-1:] != '{'): optiondict_line += ', '
optiondict_line += '\''+param+'\': [\''+'\',
\''.join(self.request_params[param][5])+'\']'
if (self.request_params[param][2] == True and
self.request_params[param][4] == None):
if (repeatlist_line[-1:] != '['): repeatlist_line += ', '
repeatlist_line += '\''+param+'\''
namesdict_line += '}'
valuesdict_line += '}'
optiondict_line += '}'
repeatlist_line += ']'
if (params_line == 'params = '): params_line += '\'\''
else: response_line += ', self.requrl'
title = self.id if (self.id != '') else self.name

48
output = generating_string('def '+classname+'_'+title+'
(self'+def_line+'):',1)
if (self.doc != ''): output += generating_string('\'\'\''+self.doc[:-
2]+'\'\'\'', 2)
if (self.name in ['POST', 'PATCH', 'PUT']):
output += generating_string('data = _data_',2)
response_line += ', data'
if (self.request_params != {}):
output += generating_string('global param_value',2)
output += generating_string(namesdict_line,2)
output += generating_string(valuesdict_line,2)
output += generating_string(repeatlist_line,2)
if (optiondict_line != 'query_params_with_options = {}'): output +=
generating_string(optiondict_line+'\n\t\theader = \'\'',2)
output += generating_string(params_line,2)
output += generating_string('for param in query_param_values.keys():',2)
output += generating_string('exec(\'global param_value\\r\\nparam_value =
\'+param)',3)
output += generating_string('if (param_value is None): continue',3)
output += generating_string('if (param not in query_params_repeating and
isinstance(param_value, list)):',3)
output += generating_string('print(\'Parameter \'+param+\' cannot take
multiple values.\')',4)
output += generating_string('exit(1)',4)
output += generating_string('if (not isinstance(param_value, list)):
param_value = [param_value]',3)
if (optiondict_line != 'query_params_with_options = {}'):
response_line += ', headers=header'
output += generating_string('if (param in
query_params_with_options.keys()):',3)
output += generating_string('if
(set(param_value).issubset(query_params_with_options[param])):',4)
output += generating_string('for p in param_value:',5)
output += generating_string('if (param == \'_format_\'): header =
{\'Accept\':p}',6)
output += generating_string('else: params +=
query_param_names[param]+\'=\'+str(p)+\'&\'',6)
output += generating_string('else:',4)
output += generating_string('print(\'Parameter \'+param+\' has incorrect
value\')',5)
output += generating_string('exit(1)',5)
output += generating_string('else:',3)

49
t=1
else: t=0
output += generating_string('for p in param_value:',3+t)
output += generating_string('params += query_param_names[param]+\'=\'+
str(p)+\'&\'',4+t)
output += generating_string('requrl = self.requrl if (params == \'\') else
self.requrl +\'?\'+parse.quote(params[:-1], safe=\'=&\')',2)
response_line += ')'
output += generating_string(response_line,2)
output += generating_string('return response\n',2)
return output

def generating_string(text, tab=0):


output = '\t'*tab+text+'\n'
return output

#processing_class function is responsible for processing <resources> and


<resource> elements
def processing_node(root, parentnode, namespace, parentrequrl='', baseurl=''):
resource_list = parentnode.findall(namespace+'resource')
for resource in resource_list:
requrl = resource.attrib['path']
typeobj =
root.find('.//*[@id=\''+resource.attrib['type'].split('#',1)[1]+'\']') if
('type' in resource.attrib) else None
if (resource.findall(namespace+'method') or typeobj != None and
typeobj.findall(namespace+'method')!= []):
processing_class(root, typeobj, resource, parentrequrl+requrl, baseurl)
if (resource.findall(namespace+'resource')):
processing_node(root, resource, namespace, parentrequrl+requrl, baseurl)

#processing_class function is responsible for processing <resource> elements


which have at least one child <method> element
def processing_class(root, typeobj, resource, requrl, baseurl):
newresourceclass = ResourceClass()
newresourceclass.baseurl = baseurl
newresourceclass.requrl = requrl
newresourceclass.name =
newresourceclass.generate_class_name(newresourceclass.requrl)
docs = resource.findall(namespace+'doc')
if(docs != []):
for d in docs:

50
newresourceclass.doc += newresourceclass.doc+d.text.lstrip()+'\n'
params = resource.findall(namespace+'param')
if(params != []):
for i in params:
newresourceclass.param_names.append(i.attrib['name'])
outputfile.write(newresourceclass.generate_class_code())
methods = resource.findall(namespace+'method')
if (typeobj != None): methods.extend(typeobj.findall(namespace+'method'))
if (methods != []):
for m in methods:
if 'name' in m.attrib:
processing_method(root, m, newresourceclass.name)
elif 'href' in m.attrib:
commonmethod = root.find('.//*[@id=\''+m.attrib['href'].split('#',
1)[1]+'\']')
if 'name' in commonmethod.attrib:
processing_method(root, commonmethod, newresourceclass.name)

#processing_method function is responsible for processing <method> elements


def processing_method(root, element, parentname):
global outputfile
newmethodclass = MethodClass()
if ('id' in element.attrib): newmethodclass.id = re.sub('[^a-zA-Z\d/]', '',
element.attrib['id'])
newmethodclass.name = element.attrib['name']
docs = element.findall(namespace+'doc')
if(docs != []):
for d in docs:
newmethodclass.doc += newmethodclass.doc+d.text.lstrip()+'\n'
request = element.find(namespace+'request')
if request != None:
params = request.findall(namespace+'param')
if(params != []):
for p in params:
normname = 'p_'+re.sub('[^a-zA-Z\d/]', '', p.attrib['name'])
newmethodclass.request_params[normname] = []
newmethodclass.request_params[normname].append(p.attrib['name'])
if('required' in p.attrib and p.attrib['required'] == 'true'):
newmethodclass.request_params[normname].append(True)
else: newmethodclass.request_params[normname].append(False)
if('repeating' in p.attrib and p.attrib['repeating'] == 'true'):
newmethodclass.request_params[normname].append(True)

51
else: newmethodclass.request_params[normname].append(False)
if('default' in p.attrib):
newmethodclass.request_params[normname].append(p.attrib['default'])
else: newmethodclass.request_params[normname].append(None)
if('fixed' in p.attrib):
newmethodclass.request_params[normname].append(p.attrib['fixed'])
else: newmethodclass.request_params[normname].append(None)
options = p.findall(namespace+'option')
if(options != []):
optionlist = []
for o in options:
optionlist.append(o.attrib['value'])
newmethodclass.request_params[normname].append(optionlist)
else: newmethodclass.request_params[normname].append(None)
responses = element.findall(namespace+'response')
if(responses != []):
for r in responses:
mediatypes = r.findall(namespace+'representation')
types = []
if (mediatypes != []):
for t in mediatypes:
if ('mediaType' in t.attrib):
types.append(t.attrib['mediaType'])
if ('href' in t.attrib):
typeobj =
root.find('.//*[@id=\''+t.attrib['href'].split('#',1)[1]+'\']')
if ('mediaType' in typeobj.attrib):
types.append(typeobj.attrib['mediaType'])
if (types != []):
newmethodclass.request_params['_format_'] =
['_format_',False,False,types[0],None]
newmethodclass.request_params['_format_'].append(types)
outputfile.write(newmethodclass.generate_method_code(parentname))

if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-i", dest='input', help="path to input WADL file")
parser.add_argument("-o", dest='output', help="path to output file")

arguments = parser.parse_args()
if arguments.input is None:
print ('No input file path provided.')

52
exit(1)
if not os.path.exists(arguments.input):
print ('Please, check provided input file path.')
exit(1)
if arguments.output is None:
print ('No output file path provided.')
exit(1)
try:
common = open('common.py')
except:
print('Unable to read file common.py')
exit(1)
try:
outputfile = open(arguments.output, 'w')
outputfile.write('\'\'\'This file is generated and the author of
codegenerator disclaims all warranties with regard to this software\'\'\'\n')
outputfile.write('import re\n')
outputfile.write(common.read())
common.close()
except:
print('Unable to write to output file')
exit(1)
try:
inputfile = open(arguments.input, encoding='utf-8', mode='r')
data = inputfile.read()
except UnicodeDecodeError:
try:
inputfile.close()
inputfile = open(arguments.input, mode='r')
data = inputfile.read()
except:
print('Unable to read input file')
exit(1)
try:
root = ElementTree.fromstring(data)
inputfile.close()
except ElementTree.ParseError:
type, value, traceback = sys.exc_info()
print('Please, check to syntax of the provided WADL file: string
'+str(value.position[0])+ ' position '+str(value.position[1]))
exit(1)
namespace = root.tag[0:-11]

53
resources_elements = root.findall(namespace+'resources')
if resources_elements != []:
for base in resources_elements:
processing_node(root, base, namespace, '', base.attrib['base'])
outputfile.close()
exit()
else:
print('No resources elements found. Check provided WADL file.')
exit(1)

54
Приложение 2

Результаты тестирования генераторов кода на основе WADL

REST Describe & Разработанный


Файл описания WADL wadl2java
Compile кодогенератор
Apigee.com.wadl − − +
Bing.wadl + − +
Bitly.wadl − − +
Delicious.wadl + − +
Etsy.wadl + − +
Etsy_Sandbox.wadl + − +
Facebook.wadl + − +
Foursquare.wadl + − +
Github.wadl + − +
Gowalla.wadl + − +
Groupon_api2.wadl + − +
Instagram.wadl − − +
Lastfm.wadl + − +
Linkedin.wadl + − +
Locomatix.com.wadl − − −*
Milo.wadl + − +
NYTimes.wadl − − +
Paypal.wadl − − +
Paypal_AdaptiveAccounts.wadl + − +
Paypal_AdaptivePayments.wadl + − +
Paypal_ExpressCheckout.wadl + − +
Paypal_Invoicing.wadl + − +
Paypal_Permissions_api.wadl + − +
Readability.com.wadl + − +
Reddit.wadl + − +
Salesforce.wadl + − +
Salesforcechatter.wadl + − +
Shopping.com.wadl − − +
Simplegeo.wadl + − +
Soundcloud.wadl + − +
Soundcloud_Sandbox.wadl + − +
Twilio.wadl + − +
Twitter.wadl − − −*
Twitter.Search.wadl + − +
Zappos.wadl + − +
Amazon.wadl − + +
Appdirect.wadl + − +
Launchpad.wadl − − +
Thomas-Bayer.wadl + + +
*Ошибка синтаксического анализа из-за некорректной структуры документа.
55
Приложение 3

Фрагмент сгенерированного кода для файла описания WADL


веб-службы AppDirect

'''This file is generated and the author of codegenerator disclaims all


warranties with regard to this software'''
import re
import http.client
from urllib import parse

def setconnection (baseurl):


# Identify supported protocol and establish a connection with server
connectionscheme = parse.urlparse(baseurl).scheme
connectionurl = parse.urlparse(baseurl).netloc
if connectionscheme == 'http':
connect = http.client.HTTPConnection(connectionurl)
elif connectionscheme == 'https':
connect = http.client.HTTPSConnection(connectionurl)
else:
print('Unsupported protocol')
return connect

def interact (connection, baseurl, method, requrl, data='', headers={}):


# Prepare relative path to send a request
requrl=parse.urlparse(baseurl).path+requrl
connection.request(method, requrl, data, headers)
# Get response in byte format and decode it
data = connection.getresponse().read().decode()
return data

class SEI_integration_v1_dummy_domain_suggestions:
def __init__(self):
self.baseurl = 'https://www.appdirect.com/api'
self.requrl = '/integration/v1/dummy/domain/suggestions'
self.connection = setconnection(self.baseurl)

def SEI_integration_v1_dummy_domain_suggestions_GET (self,


p_domainAvailable=None, p_sleep=None, p_currency=None, p_max=None, p_q=None):
global param_value

56
query_param_names = {'p_domainAvailable' : 'domainAvailable', 'p_sleep' :
'sleep', 'p_currency' : 'currency', 'p_max' : 'max', 'p_q' : 'q'}
query_param_values = {'p_domainAvailable' : p_domainAvailable, 'p_sleep' :
p_sleep, 'p_currency' : p_currency, 'p_max' : p_max, 'p_q' : p_q}
query_params_repeating = []
params = ''
for param in query_param_values.keys():
exec('global param_value\r\nparam_value = '+param)
if (param_value is None): continue
if (param not in query_params_repeating and isinstance(param_value,
list)):
print('Parameter '+param+' cannot take multiple values.')
exit(1)
if (not isinstance(param_value, list)): param_value = [param_value]
for p in param_value:
params += query_param_names[param]+'='+str(p)+'&'
requrl = self.requrl if (params == '') else self.requrl
+'?'+parse.quote(params[:-1], safe='=&')
response = interact(self.connection, self.baseurl, 'GET', requrl)
return response

57
Приложение 4

Фрагмент сгенерированного кода для файла описания WADL


веб-службы Launchpad

'''This file is generated and the author of codegenerator disclaims all


warranties with regard to this software'''
import re
import http.client
from urllib import parse

def setconnection (baseurl):


# Identify supported protocol and establish a connection with server
connectionscheme = parse.urlparse(baseurl).scheme
connectionurl = parse.urlparse(baseurl).netloc
if connectionscheme == 'http':
connect = http.client.HTTPConnection(connectionurl)
elif connectionscheme == 'https':
connect = http.client.HTTPSConnection(connectionurl)
else:
print('Unsupported protocol')
return connect

def interact (connection, baseurl, method, requrl, data='', headers={}):


# Prepare relative path to send a request
requrl=parse.urlparse(baseurl).path+requrl
connection.request(method, requrl, data, headers)
# Get response in byte format and decode it
data = connection.getresponse().read().decode()
return data

class SEI_people:
def __init__(self):
self.baseurl = 'https://api.launchpad.net/1.0'
self.requrl = '/people'
self.connection = setconnection(self.baseurl)

def SEI_people_peoplefindPerson (self, p_text, p_createdafter=None,


p_createdbefore=None, _format_='application/json'):
global param_value

58
query_param_names = {'p_text' : 'text', 'p_wsop' : 'ws.op', 'p_createdafter'
: 'created_after', 'p_createdbefore' : 'created_before', '_format_' :
'_format_'}
query_param_values = {'p_text' : p_text, 'p_createdafter' : p_createdafter,
'p_createdbefore' : p_createdbefore, '_format_' : _format_}
query_params_repeating = []
query_params_with_options = {'_format_': ['application/json']}
header = ''
params = 'ws.op=findPerson&'
for param in query_param_values.keys():
exec('global param_value\r\nparam_value = '+param)
if (param_value is None): continue
if (param not in query_params_repeating and isinstance(param_value,
list)):
print('Parameter '+param+' cannot take multiple values.')
exit(1)
if (not isinstance(param_value, list)): param_value = [param_value]
if (param in query_params_with_options.keys()):
if (set(param_value).issubset(query_params_with_options[param])):
for p in param_value:
if (param == '_format_'): header = {'Accept':p}
else: params += query_param_names[param]+'='+str(p)+'&'
else:
print('Parameter '+param+' has incorrect value')
exit(1)
else:
for p in param_value:
params += query_param_names[param]+'='+str(p)+'&'
requrl = self.requrl if (params == '') else self.requrl
+'?'+parse.quote(params[:-1], safe='=&')
response = interact(self.connection, self.baseurl, 'GET', requrl,
headers=header)
return response

59

Вам также может понравиться