API (программный интерфейс приложения, интерфейс прикладного программирования) (англ. application programming interface, API) — описание способов (набор классов, процедур, функций, структур или констант), которыми одна компьютерная программа может взаимодействовать с другой программой.
WEB-сервер — сервер, принимающий HTTP-запросы от клиентов, обычно веб-браузеров, и выдающий им HTTP-ответы, как правило, вместе с HTML-страницей, изображением, файлом, медиа-потоком или другими данными.
HTTP (англ. HyperText Transfer Protocol — «протокол передачи гипертекста») — протокол прикладного уровня передачи данных, изначально — в виде гипертекстовых документов в формате HTML, в настоящее время используется для передачи произвольных данных.
Основой HTTP является технология «клиент-сервер», то есть предполагается существование:
- Потребителей (клиентов), которые инициируют соединение и посылают запрос;
- Поставщиков (серверов), которые ожидают соединения для получения запроса, производят необходимые действия и возвращают обратно сообщение с результатом.
Каждое HTTP-сообщение состоит из трёх частей, которые передаются в указанном порядке:
- Стартовая строка (англ. Starting line) — определяет тип сообщения;
- Заголовки (англ. Headers) — характеризуют тело сообщения, параметры передачи и прочие сведения;
- Тело сообщения (англ. Message Body) — непосредственно данные сообщения. Обязательно должно отделяться от заголовков пустой строкой.
Стартовые строки различаются для запроса и ответа. Строка запроса выглядит так:
Метод URI HTTP/Версия
Здесь:
- Метод (англ. Method) — тип запроса, одно слово заглавными буквами. Cписок методов для версии 1.1 представлен ниже.
- URI определяет путь к запрашиваемому документу.
- Версия (англ. Version) — пара разделённых точкой цифр. Например: 1.0.
Чтобы запросить страницу данной статьи, клиент должен передать строку (задан всего один заголовок):
GET /wiki/HTTP HTTP/1.0
Host: ru.wikipedia.org
Стартовая строка ответа сервера имеет следующий формат: HTTP/Версия КодСостояния Пояснение
, где:
- Версия — пара разделённых точкой цифр, как в запросе;
- Код состояния (англ. Status Code) — три цифры. По коду состояния определяется дальнейшее содержимое сообщения и поведение клиента;
- Пояснение (англ. Reason Phrase) — текстовое короткое пояснение к коду ответа для пользователя. Никак не влияет на сообщение и является необязательным.
Например, стартовая строка ответа сервера на предыдущий запрос может выглядеть так:
HTTP/1.0 200 OK
Метод HTTP (англ. HTTP Method) — последовательность из любых символов, кроме управляющих и разделителей, указывающая на основную операцию над ресурсом. Обычно метод представляет собой короткое английское слово, записанное заглавными буквами. Обратите внимание, что название метода чувствительно к регистру.
Сервер может использовать любые методы, не существует обязательных методов для сервера или клиента. Если сервер не распознал указанный клиентом метод, то он должен вернуть статус 501 (Not Implemented). Если серверу метод известен, но он неприменим к конкретному ресурсу, то возвращается сообщение с кодом 405 (Method Not Allowed). В обоих случаях серверу следует включить в сообщение ответа заголовок Allow со списком поддерживаемых методов.
GET
Используется для запроса содержимого указанного ресурса.
Клиент может передавать параметры выполнения запроса в URI целевого ресурса после символа «?»:
GET /path/resource?param1=value1¶m2=value2 HTTP/1.1
POST
Применяется для передачи пользовательских данных заданному ресурсу.
Код состояния является частью первой строки ответа сервера. Он представляет собой целое число из трёх цифр. Первая цифра указывает на класс состояния. За кодом ответа обычно следует отделённая пробелом поясняющая фраза на английском языке, которая разъясняет человеку причину именно такого ответа. Примеры:
201 Webpage Created
403 Access allowed only for registered users
507 Insufficient Storage
Заголовки HTTP (англ. HTTP Headers) — это строки в HTTP-сообщении, содержащие разделённую двоеточием пару параметр-значение. Заголовки должны отделяться от тела сообщения хотя бы одной пустой строкой.
Примеры заголовков:
Server: Apache/2.2.11 (Win32) PHP/5.3.0
Last-Modified: Sat, 16 Jan 2010 21:16:42 GMT
Content-Type: text/plain; charset=windows-1251
Content-Language: ru
Тело HTTP-сообщения (message-body), если оно присутствует, используется для передачи тела объекта, связанного с запросом или ответом.
Это способ взаимодействия сайтов и веб-приложений с сервером. Его также называют RESTful.
Термин состоит из двух аббревиатур, которые расшифровываются следующим образом. API (Application Programming Interface) — это код, который позволяет двум приложениям обмениваться данными с сервера. На русском языке его принято называть программным интерфейсом приложения. REST (Representational State Transfer) — это способ создания API с помощью протокола HTTP. На русском его называют «передачей состояния представления».
Технологию REST API применяют везде, где пользователю сайта или веб-приложения нужно предоставить данные с сервера. Например, при нажатии иконки с видео на видеохостинге REST API проводит операции и запускает ролик с сервера в браузере. В настоящее время это самый распространенный способ организации API. Он вытеснил ранее популярные способы SOAP и WSDL.
У RESTful нет единого стандарта работы: его называют «архитектурным стилем» для операций по работе с серверов.
У RESTful есть 7 принципов написания кода интерфейсов.
Отделения клиента от сервера (Client-Server). Клиент — это пользовательский интерфейс сайта или приложения, например, поисковая строка видеохостинга. В REST API код запросов остается на стороне клиента, а код для доступа к данным — на стороне сервера. Это упрощает организацию API, позволяет легко переносить пользовательский интерфейс на другую платформу и дает возможность лучше масштабировать серверное хранение данных.
Отсутствие записи состояния клиента (Stateless). Сервер не должен хранить информацию о состоянии (проведенных операций) клиента. Каждый запрос от клиента должен содержать только ту информацию, которая нужна для получения данных от сервера.
Кэшируемость (Casheable). В данных запроса должно быть указано, нужно ли кэшировать данные (сохранять в специальном буфере для частых запросов). Если такое указание есть, клиент получит право обращаться к этому буферу при необходимости.
Единство интерфейса (Uniform Interface). Все данные должны запрашиваться через один URL-адрес стандартными протоколами, например, HTTP. Это упрощает архитектуру сайта или приложения и делает взаимодействие с сервером понятнее.
Многоуровневость системы (Layered System). В RESTful сервера могут располагаться на разных уровнях, при этом каждый сервер взаимодействует только с ближайшими уровнями и не связан запросами с другими.
Предоставление кода по запросу (Code on Demand). Серверы могут отправлять клиенту код (например, скрипт для запуска видео). Так общий код приложения или сайта становится сложнее только при необходимости.
Начало от нуля (Starting with the Null Style). Клиент знает только одну точку входа на сервер. Дальнейшие возможности по взаимодействию обеспечиваются сервером.
Сам по себе RESTful не является стандартом или протоколом. Разработчики руководствуются принципами REST API для создания эффективной работы с сервером для своих сайтов и приложений. Принципы позволяют выстраивать серверную архитектуру с помощью других протоколов: HTTP, URL, JSON и XML.
Это отличает REST API от метода простого протокола доступа к объектам SOAP (Simple Object Access Protocol), созданного Microsoft в 1998 году. В SOAP взаимодействие по каждому протоколу нужно прописывать отдельно только в формате XML. Также в SOAP нет кэшируемости запросов, более объемная документация и реализация словаря, отдельного от HTTP. Это делает стиль REST API более легким в реализации, чем стандарт SOAP.
Несмотря на отсутствие стандартов, при создании REST API есть общепринятые лучшие практики, например:
- использование защищенного протокола HTTPS
- использование инструментов для разработки API Blueprint и Swagger
- применение приложения для тестирования Get Postman
- применение как можно большего количества HTTP-кодов (список)
- архивирование больших блоков данных
REST API основывается на протоколе передачи гипертекста HTTP (Hypertext Transfer Protocol). Это стандартный протокол в интернете, созданный для передачи гипертекста. Сейчас с помощью HTTP отправляют любые другие типы данных.
Каждый объект на сервере в HTTP имеет свой уникальный URL-адрес в строгом последовательном формате. Например, второй модуль обучающего видео про Python будет храниться на сервере по адресу http://school.ru/python/2
.
В REST API есть 4 метода HTTP, которые используют для действий с объектами на серверах:
- GET (получение информации о данных или списка объектов)
- DELETE (удаление данных)
- POST (добавление или замена данных)
- PUT (регулярное обновление данных)
Такие запросы еще называют идентификаторами CRUD: create (создать), read (прочесть), update (обновить) delete (удалить). Это стандартный набор действий для работы с данными. Например, чтобы обновить видео про Python по адресу http://school.ru/python/2
REST API будет использовать метод PUT, а для его удаления — DELETE.
В каждом HTTP-запросе есть заголовок, за которым следует описание объекта на сервере — это и есть его состояние.
В принципе WEB-сервер можно написать на любом языке. Но удобнее разрабатывать сервер на тех языках, где есть нативная (на уровне языка) или внешняя (с помощью подключаемых модулей) поддержка HTTP-протокола.
К таким относятся (список не полный, тут только то с чем я сам работал или "на слуху"):
-
PHP - скриптовый язык общего назначения, интенсивно применяемый для разработки веб-приложений. В настоящее время поддерживается подавляющим большинством хостинг-провайдеров и является одним из лидеров среди языков, применяющихся для создания динамических веб-сайтов. В своем составе имеет библиотеки для работы с базами данных, поэтому его мы в дальнейшем и будем изучать.
-
Java (Spring) - строго типизированный объектно-ориентированный язык программирования общего назначения.
-
Node.js (Express) - программная платформа, основанная на движке V8 (транслирующем JavaScript в машинный код), превращающая JavaScript из узкоспециализированного языка в язык общего назначения. Node.js добавляет возможность JavaScript взаимодействовать с устройствами ввода-вывода через свой API, написанный на C++, подключать другие внешние библиотеки, написанные на разных языках, обеспечивая вызовы к ним из JavaScript-кода. Node.js применяется преимущественно на сервере, выполняя роль веб-сервера. Сервер для проекта
cinema.kolei.ru
написан на этом языке и код, если интересно, можно посмотреть в этом репозитории в каталогеcinema
. -
Python Django - (в русском языке встречаются названия пито́н или па́йтон) — высокоуровневый язык программирования общего назначения с динамической строгой типизацией.
-
C# (Asp.NET Core) - представляет технологию для создания веб-приложений на платформе .NET, развиваемую компанией Microsoft. В качестве языков программирования для разработки приложений на ASP.NET Core используются C# и F#.
Пробежимся по верхушкам:
Переменные - язык динамически типизируемый, поэтому типы при объявлении переменных можно не использовать. Переменной одного типа в любой момент может быть присвоено значение другого типа. Ключевых слов для объявления переменных тоже нет - переменная создается в момент присваивания ей значения (но если попытаться считать переменную до её объявления, то получим исключение). Первым символом в названии переменной должен быть знак $
(доллар)
$myVariable = 0;
$myVariable = "а может не 0";
Массивы - пустой массив можно создать либо функцией array, либо просто присвоив пустой массив
$myArray = array();
$myArray = [];
Массивы бывают обычные и ассоциативные (пара ключ - значение)
$simpleArray = [1, 2, 3];
$associativeArray = [
'one' => 'value',
'two' => 'value'
];
Литералы - строки могут быть как в одинарных, так и в двойных кавычках. Двойные кавычки отличаются тем, что в них можно использовать управляющие символы и переменные
$string = 'это строка';
$anotherString = "это тоже строка, но она поддерживает перенос\n и может включать переменные $string";
Функции - функции объявляются ключевым словом function, тело функции заключается в фигурные скобки - обычный Си-подобный синтаксис
function someFunction($firstParam, $secondParam)
{
return $firstParam.$secondParam;
}
$concat = someFunction('раз', 'два');
Обратите внимание, для склеивания строк используется символ .
(точка), знак +
(плюс) используется только с числовыми переменными. Это проистекает из динамической типизации, PHP не знал бы что делать в следующем случае:
$a = 1;
$b = 2;
$c = $a + $b; // 3 или 12 ?
Конкатенация (операция .
) интерпретирует данные как строки
$c = $a . $b; // 12
Классы
Объявление класса начинается с ключевого слова class, если класс является наследником какого-то класса, то родительский класс указывается после ключевого слова extends
Конструктором класса является функция с именем __construct
class ApiServer extends ParentClass
{
// свойство класса
private $var;
// конструктор класса
public function __construct(){
// ЛОКАЛЬНАЯ переменная
$var = 0;
// свойство класса
$this->var = 1;
// метод класса
$this->someName();
}
private function someName($someVar = null) {
...
}
}
Обратите внимание, обращение к свойствам и методам класса производится через ключевое слово $this
API будем писать похожее на то, что использовалось для проекта "база". Отличия обусловлены тем, что сервер на PHP является stateless (не хранящим состояние). Поэтому без использования дополнительных механизмов (база данных, Redis, Mongo) нам негде хранить токен и будем использовать "базовую" авторизацию.
Таким образом, запросы login и logout нам не понадобятся, сразу реализуем методы получения данных (примеры запросов в формате плагина REST Client редактора VSCode).
### Запрос списка продукции
GET {{url}}/Product
Authorization: Basic ZXNtaXJub3Y6MTExMTAz
Обратите внимание, вместо токена используется заголовок Authorization. В этом заголовке первое слово обозначает алгоритм авторизации (они бывают разные), а второе это закодированная base64 строка логин:пароль
(позже, когда мы вернёмся к C#, я покажу как сформировать эту строку программно, а пока можете её получить используя онлайн кодировщики base64).
Точкой входа сервера по-умолчанию являются файлы index.html
или index.php
.
Создайте файл index.php
:
<?php
// тут можно писать код
?>
Описываем класс сервера и создаём его (при этом вызовется конструктор)
<?php
// декларация класса
class ApiServer
{
public function __construct(){
print_r($_SERVER);
}
}
// создание экземпляра
new ApiServer();
?>
Функция print_r выводит в stdout (стандартный поток вывода) содержимое переменной
Переменная $_SERVER внутренняя глобальная переменная языка PHP, она содержит параметры запроса и возвращает примерно такое:
Array
(
[DOCUMENT_ROOT] => /home/kei/[ЙОТК]/API_PHP
[REMOTE_ADDR] => 127.0.0.1
[REMOTE_PORT] => 39956
[SERVER_SOFTWARE] => PHP 7.4.3 Development Server
[SERVER_PROTOCOL] => HTTP/1.1
[SERVER_NAME] => localhost
[SERVER_PORT] => 8000
[REQUEST_URI] => /Product
[REQUEST_METHOD] => GET
[SCRIPT_NAME] => /index.php
[SCRIPT_FILENAME] => /home/kei/[ЙОТК]/API_PHP/index.php
[PATH_INFO] => /Product
[PHP_SELF] => /index.php/Product
[HTTP_USER_AGENT] => vscode-restclient
[HTTP_AUTHORIZATION] => Basic ZXNtaXJub3Y6MTExMTAz
[HTTP_ACCEPT_ENCODING] => gzip, deflate
[HTTP_HOST] => localhost:8000
[HTTP_CONNECTION] => close
[PHP_AUTH_USER] => esmirnov
[PHP_AUTH_PW] => 111103
[REQUEST_TIME_FLOAT] => 1638434071.8106
[REQUEST_TIME] => 1638434071
)
Нам, для начала, интересны параметры:
- REQUEST_METHOD - метод запроса (GET, POST и т.д.)
- PATH_INFO - путь запроса (что именно мы хотим получить, в нашем случае
/Product
). Есть ещё параметр REQUEST_URI, но в нём хранится путь вместе с параметрами запроса (например,/Product?id=1
) - PHP_AUTH_USER - логин пользователя (если использовалась базовая авторизация)
- PHP_AUTH_PW - пароль пользователя (если использовалась базовая авторизация)
Разберём на примере простой скрипт:
class ApiServer
{
private $db = null;
public function __construct(){
// результат в формате JSON
header('Content-Type: application/json; utf-8');
try {
switch($_SERVER['REQUEST_METHOD'])
{
case 'GET':
$this->processGet($_SERVER['PATH_INFO']);
break;
// case 'POST':
// $this->processPost($_SERVER['PATH_INFO']);
// break;
}
} catch (\Throwable $th) {
header("HTTP/1.1 500 Server error");
$response['error'] = $th->getMessage();
// выводим в stdout JSON-строку
echo json_encode($response, JSON_UNESCAPED_UNICODE);
}
}
private function processGet($path)
{
switch($path)
{
case '/Product':
$this->connect();
$this->auth();
// получаем данные
$response = $this->db
->query("SELECT * FROM Product")
->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($response, JSON_UNESCAPED_UNICODE);
break;
default:
header("HTTP/1.1 404 Not Found");
}
}
private function auth()
{
if(!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']))
throw new Exception('Не задан логин/пароль');
// подгатавливаем шаблон запроса (вместо реальных данных - алиасы)
$query = $this->db
->prepare("SELECT * FROM User WHERE login=:login")
// меняем алиасы на реальные данные
$query->bindValue(':login', $_SERVER['PHP_AUTH_USER']);
// отправляем запрос на сервер
$query->execute();
// читаем полученный результат
$user = $query->fetch(PDO::FETCH_ASSOC);
if ($user == null)
throw new Exception('Пользователь не найден');
if ($user->password != $_SERVER['PHP_AUTH_PW'])
throw new Exception('Не верный пароль');
return $user;
}
private function connect()
{
// пытаемся подключиться к MySQL серверу
$this->db = new PDO(
"mysql:host=kolei.ru;
port=3306;
dbname=ТУТ-НАЗВАНИЕ-БАЗЫ;
charset=UTF8",
"ТУТ-ЛОГИН-MYSQL",
"ТУТ-ПАРОЛЬ-MYSQL"
);
}
}
-
private $db = null
- ссылка на базу данных, получается после успешной авторизации -
header
- встроенный метод PHP, добавляет строку в заголовок ответа -
echo
- встроенная команда PHP, выводит данные в stdout (всё, что попадёт в выходной поток, станет телом ответа) -
json_encode($response, JSON_UNESCAPED_UNICODE)
- встроенный метод PHP, преобразует данные в JSON-строку -
isset
- встроенный метод PHP, проверяет существует ли указанная переменная -
запрос данных из бд (список всех строк)
$response = $this->db ->query("SELECT * FROM Product") ->fetchAll(PDO::FETCH_ASSOC);
-
Запрос с параметрами
подгатавливаем шаблон запроса (вместо реальных данных - алиасы)
$query = $this->db ->prepare("SELECT * FROM User WHERE login=:login")
меняем алиасы на реальные данные. Это необходимо для защиты от SQL-иньекций (злоумышленник может в качестве логина послать строку
'ха-ха-ха'; drop table User;
)$query->bindValue(':login', $_SERVER['PHP_AUTH_USER']);
выполняем запрос на сервер
$query->execute();
читаем полученный результат (метод fetch возвращает одну запись (строку))
$user = $query->fetch(PDO::FETCH_ASSOC);
-
подключение к БД mysql
$this->db = new PDO( "mysql:host=kolei.ru;port=3306;dbname={$_SERVER['PHP_AUTH_USER']};charset=UTF8", $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
Запустить локальный сервер для отладки можно из командной строки в каталоге проекта
php -S 127.0.0.1:8080
Либо, если нам нужен доступ к этому АПИ с другого устройства (а эмулятор андроида это другое устройство)
php -S 0.0.0.0:8080
Но в этом случае нужно и обращаться к апи не по localhost, а по IP-адресу (можно узнать командой ipconfig)
Параметры GET-запроса передаются прямо в URL. Отделяются от пути знаком вопроса. Между собой разделяются знаком &
. Представляют собой пары ключ=значение
. Например, так может выглядеть запрос материала по нужному продукту:
GET {{url}}/Material?product_id=1
PHP автоматически разбирает URL и параметры GET-запроса нам доступны через глобальную переменную $_GET:
$productId = $_GET['product_id'];
POST-запросы отличаются тем, что содержат данные в "теле" запроса
Формат данных определяется заголовком Content-Type
-
application/x-www-form-urlencoded - формат по-умолчанию, представляет собой те же пары ключ=значение, что и в GET-запросе (только без знака вопроса). Автоматически распознается PHP и заносится в глобальный массив переменных $_POST
-
application/json - данные передаются в виде JSON-строки (сериализованы). Автоматически не распознаются, приходится программно читать из потока данных:
$rawData = file_get_contents('php://input'); $json = json_decode($rawData);
В переменной $json будет JSON-объект. Данные из него извлекаются как из класса
->
стрелочным синтаксисом.Если кому-то удобнее работать с ассоциативными массивами, то можно в функции json_decode добавить второй параметр true
$json = json_decode($rawData, true);