api_php.md 24 KB

API. PHP-сервер. GET-запрос.

API (программный интерфейс приложения, интерфейс прикладного программирования) (англ. application programming interface, API) — описание способов (набор классов, процедур, функций, структур или констант), которыми одна компьютерная программа может взаимодействовать с другой программой.

WEB-сервер — сервер, принимающий HTTP-запросы от клиентов, обычно веб-браузеров, и выдающий им HTTP-ответы, как правило, вместе с HTML-страницей, изображением, файлом, медиа-потоком или другими данными.

HTTP (англ. HyperText Transfer Protocol — «протокол передачи гипертекста») — протокол прикладного уровня передачи данных, изначально — в виде гипертекстовых документов в формате HTML, в настоящее время используется для передачи произвольных данных.

Основой HTTP является технология «клиент-сервер», то есть предполагается существование:

  • Потребителей (клиентов), которые инициируют соединение и посылают запрос;
  • Поставщиков (серверов), которые ожидают соединения для получения запроса, производят необходимые действия и возвращают обратно сообщение с результатом.

Структура 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&param2=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), если оно присутствует, используется для передачи тела объекта, связанного с запросом или ответом.

Языки для разработки WEB-серверов

В принципе WEB-сервер можно написать на любом языке. Но удобнее разрабатывать сервер на тех языках, где есть нативная (на уровне языка) или внешняя (с помощью подключаемых модулей) поддержка HTTP-протокола.

К таким относятся (список не полный, тут только то с чем я сам работал или "на слуху"):

  • PHP - скриптовый язык общего назначения, интенсивно применяемый для разработки веб-приложений. В настоящее время поддерживается подавляющим большинством хостинг-провайдеров и является одним из лидеров среди языков, применяющихся для создания динамических веб-сайтов. В своем составе имеет библиотеки для работы с базами данных, поэтому его мы в дальнейшем и будем изучать.
  • Java - строго типизированный объектно-ориентированный язык программирования общего назначения.
  • Node или Node.js - программная платформа, основанная на движке V8 (транслирующем JavaScript в машинный код), превращающая JavaScript из узкоспециализированного языка в язык общего назначения. Node.js добавляет возможность JavaScript взаимодействовать с устройствами ввода-вывода через свой API, написанный на C++, подключать другие внешние библиотеки, написанные на разных языках, обеспечивая вызовы к ним из JavaScript-кода. Node.js применяется преимущественно на сервере, выполняя роль веб-сервера.
  • Python - (в русском языке встречаются названия пито́н или па́йтон) — высокоуровневый язык программирования общего назначения с динамической строгой типизацией.

Синтаксис PHP

Пробежимся по верхушкам:

Переменные - зяык динамически типизируемый, поэтому типы при объявлении переменных можно не использовать. Переменной одного типа в любой момент может быть присвоено значение другого типа. Ключевых слов для объявления переменных тоже нет - переменная создается в момент присваивания ей значения (но если попытаться считать переменную до её объявления, то получим исключение). Первым символом в названии переменной должен быть знак "$"

$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('раз', 'два');

Обратите внимание, для склеивания строк используется символ точки, знак "+" используется только с числовыми переменными.

Классы

class ApiServer extends ParentClass
{
    // свойство класса
    private $var;

    // конструктор класса
    public function __construct(){
        // ЛОКАЛЬНАЯ переменная
        $var = 0;

        $this->var = 1;
    }
}

Обратите внимание, обращение к свойствам и методам класса производится через ключевое слово $this

Разработка API-сервера на PHP

API будем писать похожее на то, что использовалось для проекта "база" (то апи написано на Node.js, в конце я приведу исходный код). Отличия обусловлены тем, что сервер на PHP является stateless (не хранящим состояние). Поэтому без использования дополнительных механизмов (Redis, Mongo) нам негде хранить токен и будем использовать "базовую" авторизацию.

Таким образом, запросы login и logout нам не понадобятся, сразу реализуем методы получения данных (примеры запросов в формате плагина REST Client редактора VSCode).

### Запрос списка продукции
GET {{url}}/Product
Authorization: Basic ZXNtaXJub3Y6MTExMTAz

Обратите внимание, вместо токена используется заголовок Authorization. В этом заголовке первое слово обозначает алгоритм авторизации (они бывают разные), а второе это закодированная base64 строка логин:пароль (позже, когда мы вернёмся к C#, я покажу как сформировать эту строку программно, а пока можете её получить используя онлайн кодировщики base64).

WEB-сервер

Точкой входа сервера по-умолчанию являются файлы index.html или index.php.

Создайте файл index.php:

<?php

    // тут можно писать код

?>

Описываем класс сервера и создаём его (при этом вызовется конструктор)

class ApiServer
{
    public function __construct(){
        print_r($_SERVER);
    }
}
new ApiServer();

Функция print_r выводит в консоль содержимое переменной

Переменная $_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 $response = ['notice'=>[]];

    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) {
            $this->response['notice']['answer'] = $th->getMessage();
        }

        // выводим в stdout JSON-строку
        echo json_encode($this->response, JSON_UNESCAPED_UNICODE);
    }

    private function processGet($path)
    {
        switch($path)
        {
            case '/Product':
                $this->auth();
                
                // получаем данные
                $this->response['notice']['data'] = $this->db
                    ->query("SELECT * FROM Product")
                    ->fetchAll(PDO::FETCH_ASSOC);
                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('Не задан логин/пароль');

        // пытаемся подключиться к 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-запроса

Параметры GET-запроса передаются прямо в URL. Отделяются от пути знаком вопроса. Между собой разделяются знаком &. Представляют собой пары ключ=значение. Например, так может выглядеть запрос материала по нужному продукту:

GET {{url}}/Material?product_id=1

PHP автоматически разбирает URL и параметры GET-запроса нам доступны через глобальную переменную $_GET:

$productId = $_GET['product_id'];

POST-запросы

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);
    

Docker

Рассказать про докер...

В каталоге data этого репозитория лежит архив DockerPhp.zip, в нём настроены контейнеры PHP и NGINX.

PHP проект складывать в подкаталог www/yotc.kei - он будет доступен на локальной машине по адресу http://localhost:8080. Если вам нужно более одного проекта, то нужно добавить в NGINX конфиг для нового проекта:

  • в каталоге hosts скопировать файл настроек yotc_kei.conf в, например, myproject.conf и исправить в нём 2 строки (я их специально вынес в начало конфига)

    server {
        listen 8081;
        root /var/www/html/myproject;
    
    • listen 8081; - порты до 1024 считаются зарезервированными, поэтому используйте 8081 и т.д. (общепринятая практика)
    • root /var/www/html/myproject; - путь к вашему проекту в каталоге www (/var/www/html/ - на этот путь каталог монтируется в контейнере)
  • в файле docker-compose.yml в секцию nginx -> ports добавьте порт вашего нового проекта

    services:
        nginx:
            # используем последний стабильный образ nginx
            image: nginx:latest
            # маршрутизируем порты
            ports:
                - "8080:80"
                - "8081:8081"
    

Как запустить/остановить контейнеры написано в read.me (находится в архиве)