Побудова RESTful API з Martini

.collapse">Зміст

Чому Martini?

Іншою ключовою особливістю є те, що як тільки Martini здасться вам «магічним» (мені не подобається магія), вам абсолютно необхідно буде поглянути на його вихідні тексти. Це

400 рядків вихідних текстів, невеликі числом і добре контрольовані (так це було сьогодні вранці), з однією зовнішньою залежністю, пакетом inject, таким же «худим», що складається всього з

100 рядків вихідних текстів.

Майте на увазі, що зараз Martini в активній розробці, і деякі приклади можуть відмовитися працювати в міру внесення в нього змін. Я намагатимусь підтримувати вихідні тексти моїх прикладів у актуальному стані.

Опишемо завдання

Приклад програми надаватиме доступ до одного-єдиного ресурсу, музичних альбомів, за URI /albums . І підтримуватиме наступний функціонал:

  • GET /albums - список всіх доступних альбомів, з можливою фільтрацією за виконавцем, назвою або роком з передачею параметрів у рядку запиту;
  • GET /albums/:id - отримання конкретного альбому;
  • POST / albums - створення альбому;
  • PUT /albums/:id - оновлення альбому;
  • DELETE /albums/:id — Видалення альбому.
Щоб зробити цікавіше, передбачимо, щоб відповіді могли б запитуватись у форматах JSON, XML або у вигляді простого тексту. Формат відповіді буде визначатися по закінченню (за розширенням: .json, .xml або .text, за умовчанням JSON).

Оскільки реалізація підсистеми зберігання даних не відноситься до цілей створення програми, то будемо використовувати керований через mutex асоціативний масив пар «ключ-значення» (прим. пров.: в ориг. "(read-write) mutex-controlled map"), який будемо використовувати як базу даних в оперативній пам'яті, і реалізуємо це через наступний інтерфейс:

Визначимо структуру для зберігання даних альбому так:

Тепер подивимося, як з цим можна використовувати Martini.

(прим. пров. — гра слів «Dry Martini» з одного боку посилання на назву відомого сухого вермуту «Martini Extra Dry», з іншого боку посилання на відомий принцип розробки ПО DRY — Don't Repeat Yourself, при тому, що слово « dry» в англійській мові означає «сухий»)

Серцем пакету martini є тип martini.Martini , який реалізує інтерфейс http.Handler , і, отже, змінна типу martini.Martini може бути передана виклик http.ListenAndServe() як звичайнісінький обробник, очікуваний стандартної бібліотекою. Іншим важливим поняттям є те, що Martini використовує підхід, заснований на безлічі шарів проміжної обробки (прим. пер.: ориг. Middleware). Отже, ви можете налаштувати список функцій, що підлягають дзвінку в певному порядку, до того, як буде викликаний обробник, який обслуговуватиме конкретний URI. Це дуже зручно для використання налаштування таких речей як ведення логів, автентифікація, керування сесіями і т.д., і дозволяє зберігати вихідні тексти «СУХИМИ» (прим. в ориг. гра слів з абревіатурою «DRY», про яку я написав абзацом вище).

На щастя, це лише зручна функція, у нас завжди є можливість створити порожній екземпляр Martini і налаштувати його вручну так, як необхідно. Наша версія виглядає так:

Пропустимо поки проміжний обробник MapEncoder, повернемося до нього за хвилину. Наступний крок – це налаштування обробників URI (прим. пров.: ориг. «routes»). Martini забезпечує гарний чистий спосіб зробити це: вінпідтримує мітки параметрів (прим. пров.: ориг. «placeholders», мається на увазі частина URI, позначена «двокрапкою»), і, більше того, ви можете використовувати деякі регулярні вирази (прим. пров.: ориг. «you can even throw деякі регулярні експозиції в них, що вказує на те, що буде закінчуватися anyway»). Другим параметром у Get/Post/Put/пр. є функція-обробник, яка буде викликана обслуговування цього URI. На обслуговування одного й того самого URI можуть бути призначені кілька функцій-обробників. Вони будуть виконані по черзі. Їх виклик у цій черзі буде припинено як тільки який-небудь один з них видасть відповідь на запит (прим. пров.: ориг." (this is a variadic parameter), and they will be executed in order, until one of them writes a response").

Синтаксис другого параметра може бути дивним. Ми наводимо nil до типу pointer-to-DB-interface тому, що все, що потрібно знати механізму впровадження залежностей, - це тип, що прив'язується до першого параметра.

Фінальний крок: m.Action() додає підготовлений раніше список обробників URI, які може викликати Martini.

Проміжний обробник MapEncoder

Повернемося назад до проміжного обробника MapEncoder. Його завдання впровадити в поточний запит реалізацію інтерфейсу Encoder (прим. пров.: використовується вся та ж технологія «впровадження залежності», що й вище використовувалася для впровадження в Martini бази даних), яка відповідає запитаному формату відповіді:

Тут функція MapTo() викликається для martini.Context , це означає, що вбудована залежність буде видно лише в межах одного конкретного запиту. Крім того, тут же виставляється коректний заголовок "Content-Type", отже, починаючи з цього моменту навіть помилки будуть повернуті ззапиту кінцевого користувача нашого API у відповідному форматі.

Обробники URI

Я не буду вдаватися в подробиці щодо всіх обробників URI (з ними з усіма можна ознайомитися, подивившись у файл api.go , що є частиною цього прикладу програми), але я покажу один, щоб поговорити про те, як Martini обробляє значення, що повертаються. Обробник GET для одного альбому виглядає так:

І нарешті, якщо все піде добре, буде повернуто код стану 200 разом із закодованим вмістом альбому. Якщо обробник URI повертає 2 значення, і перше значення є цілим числом, Martini буде використовувати перше значення як код стану і запише друге значення як рядок в http.ResponseWriter . Якщо ж перше значення не є цілим числом або буде повернено лише одне значення, то перше (або єдине) значення буде записано http.ResponseWriter .

Виклики curl

Давайте подивимося, як наше API буде працювати з реальними викликами:

$ curl -i -k -u token: "localhost:8001/albums" HTTP/1.1 200 OK Content-Type: application/json Date: Wed, 27 Nov 2013 02:31:46 GMT Content-Length: 201

[,,] Параметр -k потрібний, якщо ви використовуєте самопідписані сертифікати. За допомогою параметра -u передається ім'я користувача та пароль, який у нашому випадку є простим token: (користувач «token» і порожній пароль). Параметр -i необхідний висновку повної відповіді, включаючи заголовки. Відповідь включає повний список альбомів (база даних ініціалізується з цими трьома альбомами).

$ curl -i -k -u token: "localhost:8001/albums.text?band=Slayer" HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Дата: Wed, 27 Nov 2013 02:36:46 GMT Content-Length: 68

Slayer- Reign In Blood (1986) Slayer - Seasons In The Abyss (1990) У цьому випадку запитаний текстовий формат і використовується фільтр за виконавцем Slayer. Давайте спробуємо запит POST:

$ curl -i -k -u token: -X POST --data "band=Carcass&title=Heartwork&year=1994" "localhost:8001/albums" HTTP/1.1 201 Created Content-Type : application/json Location: /albums/4 Date: Wed, 27 Nov 2013 02:38:55 GMT Content-Length: 57

Код стану 201 - "Створено". Заголовок “Location” містить URL із створеним новим ресурсом (зверніть увагу, що URL повинен бути абсолютним, я тут полінувався), і ресурс повертається у форматі за замовчуванням (JSON). Спробуємо створити той же ресурс знову, для різноманітності у форматі XML:

$ curl -i -k -u token: -X POST --data "band=Carcass&title=Heartwork&year=1994" "localhost:8001/albums.xml" HTTP/1.1 409 Conflict Content -Type: application/xml Дата: Wed, 27 Nov 2013 02:41:36 GMT Content-Length: 171

the album 'Heartwork' from 'Carcass' already exists Помилка повертається в коректному форматі з кодом статусу 409. Оновлення (PUT) теж нормально працюють (сподіваюся, все в курсі, що альбом Heartwork був випущений 1993 році?):

$ curl -i -k -u token: -X PUT --data "band=Carcass&title=Heartwork&year=1993" "localhost:8001/albums/4" HTTP/1.1 200 OK Content -Type: application/json Дата: Wed, 27 Nov 2013 02:45:29 GMT Content-Length: 57

$ curl -i -k -u token: -X DELETE «localhost:8001/albums/1» HTTP/1.1 204 No Content Content-Type: application/json Date: Wed, 27 Nov 2013 02:46:59 GMT Content-Length: 0

Потрібно https

Чого бракує

Це простий приклад програми API, але він містить багато обробників.загальних завдань API. Martini робить це легко та елегантно завдяки своїм механізмам обробки URI та впровадження залежностей. За той час, що я писав цю статтю, Martini розвивався, зокрема деякі обробники були додані в martini-contrib.

Якщо ви маєте намір побудувати повноцінний придатний для серйозної експлуатації API, то майте на увазі, що в цьому простому прикладі програми немає кількох важливих речей, а саме:

Очевидно, більш потужний механізм аутентифікації (не по одному маркеру доступу!) Підтримка кодування тіл запитів у JSON (і/або XML) для операцій POST і PUT (і PATCH) Підтримка 405 — Метод не підтримується (зараз API буде повертати 404, коли непідтримуваний метод використовується для підтримуваного URI). залежно від ваших вимог!