Child pages
  • Программный интерфейс API
Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Current »

С 1.22.00.

В данной статье описано программный интерфейс всего API, как и со стороны модуля так и ядра, так и флоу.


Принцип работы и флоу

C самого начала, api.cgi проверяет ли включена опция на сваггер и ли это был вызов на OpenAPI
Если да - клиент получает bundle_user.yaml или bundle_admin.yaml в зависимости от реквеста.

Загружается центральный английский словарь, если это не выставлено конфигурацией API_CONF_LANGUAGE

Создаётся главный объект Abills::Api::Handle, в который записывается вся необходимая информация для полной изоляции контекста.

Вызывается api_call, с которого мы получаем ответ, статус, и Content-Type (по умолчанию application/json; charset=utf-8, если это не определено иначе)

Внутри api_call, с получаемым контекстом производится проверка, ли API_ENABLE включена.
Если нет - клиент получает ответ, что API отключено.

Проверяется данные авторизации ADMIN API

Создаётся объект Abills::Api::Router, в который помещают весь полученный контекст.

При инициализации, проверяется ли HTTP метод соответствует полученому (GET, POST, PATCH, PUT, DELETE)
Если нет - клиент получает ответ с HTTP статусом 405.

Дальше разбивается url на несколько сегментов по /

Система запоминает первый сегмент
Запоминает второй сегмент, но если этот сегмент число - система запоминает третий сегмент.

Создаётся объект Abills::Api::Paths, который приобретает частичный контекст.

Система проверяет,
если HTTP метод это GET, DELETE - то создаётся и присваивается query_params.
если есть Content-Type: multipart/form-data, то создаётся и присваивается query_params, ведь body уже обработано
если есть буфер, то система попытается его декодировать с помощью JSON.
    если происходит ошибка декодирования - клиент получает ошибку декодирования и статус 400.
система сканирует query_params на предмет непозволительных символов, очищает буфер (для оптимизации памяти)

Дальше система проверяет первый сегмент URL - если это 'user', то попытается загрузить модуль или микромодуль как USER API на основе второго сегмента, и если загрузчик не определил модуль или микромодуль - система загружает User_core.pm
Если это не 'user' - загружает модуль или микромодуль как ADMIN API

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

Сам загрузчик получает с функции _extra_api_modules() названия микромодулей ядра, и смешивает с ними названия модулей.
Если префикса нет в этом списке - клиент получает 404.
На основе пришедших параметров (имя ресурса, 'user' или 'admin' api) попытается загрузить <module>::Api со всем заданным контекстом. Тоесть, первым приоритетом стоит название модуля.

Если загрузка успешная - создаёт объект и забирает список роутов.
Если нет - система попытается загрузить Abills::Api::Paths::<module>
   если загрузка успешная - создаёт объект и забирает список роутов
   если нет - клиент получает ошибку.
Список роутов определяется уже внутри объекта.
Изнутри объекта пробрасывается наружу словарь ошибок.

Если загружаемый модуль не оказался в списке модулей администратора - клиент получит 403.
Если загружаемого модуля не нашлось, система возвращает "остальные" системные пути на проверку.

После полной инициализации Abills::Api::Router система проверяет, ли есть за ним ошибка.
Если да - клиент получит ошибку от роутера, скорее всего 403 или 400.

В роутер иньектятся хуки на авторизацию (credentials).

Дальше вызывается функция handle, которая должна уже выполнить роут.
Но сначала внутри вызывается метод parse_request, который:

  1. Проверяет пришедший объект роута, если неправильный - переходит до следующего
  2. Парсит ключи внутри роута, подставляет их в path_params
  3. Проверяет опции на camelCase и придаёт им SCREAMING_UPPER_CASE
  4. Производит проверку формата
  5. Возвращает найденный путь, хэндлер (если он есть), path_params и query_params

Если роут не найден - клиент получает 404.
Если есть handler (старый метод) - запоминается.

На основе найденного пути производится вызов нужных хуков на авторизацию
Первый успешный хук позволяет системе пройти дальше.
Если хук на USER API - дополнительно в path_params присваивается uid.
Если нет успеха - клиент получает 401.

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

Система проверяет, если это ADMIN API, и в пути нужен uid, то система грузит модуль работы Users,
проверяет ли пользователь с таким uid существует.
Если нет - возвращается ошибка, что пользователя не существует.
Если есть - в path_params присваивается user_object.

Если есть у роута опция module (присуще для ядра), то система попытается его загрузить и присвоить как третий аргумент для хэндлера.
Если есть ошибка - клиент её получает.

Если у полученного роута есть опция controller и endpoint, то система попытается загрузить контроллер со всем наданным контекстом ПЛЮС встроенный словарь ошибок.

Загружаемый контроллер загружается с названием метода от опции endpoint
Если контроллера нет - загружается handler.

Хэндлер в пути запускается.

Если во время запуска случилась ошибка - клиент получает ошибку.
Если во время загрузки модуля ошибка - клиент получает ошибку.

Получаем конечный результат с хэндлера.
Присваиваем его роутеру.

Если результат пустой - присваивается пустой объект.
Система смотрит на полученный от роута content_type и присваивает его хэндлу.

Если авторизация успешная и хэндл успешный - система проверяет результат хэндлера, и старается удалить с него потенциально лишние данные.

Система на основе проверенных формирует JSON ответ.

Если включенно логирование - весь ответ и полученный реквест попадает в лог.

Клиент получает ответ сервера, статус и нужный content_type.

Объект пути

{
  # HTTP метод, GET, POST, PUT, PATCH, DELETE
  method      => 'POST',
  # Абсолютный путь, за которым можно будет достучаться, например billing.url/api.cgi/portal/articles
  # для USER API обязательно должно начинаться с /user/* 
  # В пути определяются основные параметры, по типу :id, :uid, которое отобразятся в пути
  path        => '/portal/articles/',
  # "Контроллер" для API /portal/articles/*
  controller  => 'Portal::Api::admin::Articles',
  # Ссылка на функцию-эндпойнт контроллера
  endpoint    => \&Portal::Api::admin::Articles::post_portal_articles,
  credentials => [
    # Параметры авторизации ADMIN API
    # ADMIN    - API_KEY
    # ADMINSID - admin_sid по cookie (в том числе для api_call)
    'ADMIN', 'ADMINSID'

    # Параметры авторизации USER API
    # USER    - через header, полученном с /user/login
    # USERSID - через cookie (в том числе для api_call)
    # USERBOT - через API Bots
    # PUBLIC  - без авторизации 

    # Тоесть, в данном случае путь может работать как и с авторизованными пользователями, так и нет.
    # Внутри хэндлера можно определять какой пользователь.
      ]
    },


Контроллер

# Пэкэдэ <Module>::Api::(admin | user)::<Controller>
package Portal::Api::admin::Articles;

=head1 NAME

  Portal articles manage

  Endpoints:
    /portal/articles/*

=cut

use strict;
use warnings FATAL => 'all';

# Словарь ошибок
use Control::Errors;

# Грузим нужные депенденси для этого контроллера
use Portal;

# Создаём объект словаря ошибок
my Control::Errors $Errors;

# Создаём объект(ы) депенденси
my Portal $Portal;

# Дополнительно: определяем permissions
my %permissions = ();

#**********************************************************
=head2 new($db, $admin, $conf)

=cut
#**********************************************************
sub new {
  my ($class, $db, $admin, $conf, $attr) = @_;

  my $self = {
    db    => $db,
    admin => $admin,
    conf  => $conf,
    attr  => $attr
  };

  %permissions = %{$attr->{permissions} || {}};

  bless($self, $class);

  # Определяем депенденси
  $Portal = Portal->new($db, $admin, $conf);

  # Забираем "предустановленный" словарь ошибок
  $Errors = $self->{attr}->{Errors};

  return $self;
}

# И дальше пишем пути #

1;


Определение пути

#**********************************************************
=head2 delete_portal_articles_id($path_params, $query_params)

  Endpoint DELETE /portal/articles/:id/

=cut
#**********************************************************
sub delete_portal_articles_id {
  my $self = shift;
  my ($path_params, $query_params) = @_;
  # В $path_params находятся переменные которые определены путём
  # Например, /portal/articles/:id/ будет иметь параметр $path_params->{id}

  # в $query_params находятся параметры которые пришли с query
  # например: /portal/articles/?title=test
  # или c json/form body
  # например: POST /portal/articles { "title": "test" }

  my $list = $Portal->portal_articles_list({ ID => $path_params->{id}, COLS_NAME => 1 });

  if (!($list && scalar(@$list))) {
    # Пример пробрасывания ошибки
    return $Errors->throw_error(1440002, { lang_vars => { ID => $path_params->{id} }});
  }

  my $result = $Portal->portal_article_del({ ID => $path_params->{id} });
  if (!$Portal->{errno}) {
    $Attachments->delete_attachment($path_params->{id});
  }

  # Стандартный результат при удалении, должен быть описан в Swagger 
  return $result;
}
  • No labels