Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Простой пример RESTful JSON сервера на Delphi/Pascal с использованием libmicrohttpd

Delphi , Интернет и Сети , Интернет

Создание простого RESTful JSON сервера на Delphi/Pascal с libmicrohttpd

В этой статье мы рассмотрим, как создать простой, но функциональный RESTful JSON сервер на Object Pascal (Delphi/Free Pascal) с использованием библиотеки libmicrohttpd. Этот пример послужит хорошей основой для разработки более сложных микросервисов.

Проблема и решение

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

Основные проблемы исходного кода:

  1. Неполная обработка POST-данных
  2. Отсутствие правильного управления контекстом соединения
  3. Проблемы с потокобезопасностью при завершении работы сервера

Улучшенное решение

Рассмотрим улучшенную версию кода, которая решает эти проблемы:

program restfulhttpserverthreadsafe;

{$mode objfpc}{$H+}
{$define THREADPOOL}

uses
  cthreads,
  Classes,
  SysUtils,
  cmem,
  libmicrohttpd,
  fpjson,
  jsonparser,
  syncobjs;

const
  PORT = 8888;

type
  TRestResponse = record
    StatusCode: cuint;
    ResText: string;
  end;

  TClientContextInfo = record
    Playload_AsText: string;
    PlayLoadIsJson: boolean;
    PlayLoad_AsJson: TJSONObject;
    Response: TRestResponse;
  end;
  PClientContextInfo = ^TClientContextInfo;

var
  ShouldStop: boolean = False;
  Daemon: PMHD_Daemon;
  StopLock: TCriticalSection;

function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;
  url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;
  con_cls: PPointer): cint; cdecl;
var
  response: PMHD_Response;
  jsonObj: TJSONObject;
  con_info: PClientContextInfo;
begin
  Result := 0;

  // Инициализация контекста соединения при первом вызове
  if con_cls^ = nil then
  begin
    New(con_info);
    con_info^.Playload_AsText := '';
    con_info^.PlayLoadIsJson := False;
    con_info^.PlayLoad_AsJson := nil;
    con_info^.Response.StatusCode := MHD_HTTP_OK;
    con_info^.Response.ResText := '';
    con_cls^ := con_info;
    Exit(MHD_YES);
  end;

  con_info := PClientContextInfo(con_cls^);

  // Обработка POST-данных
  if (method = MHD_HTTP_METHOD_POST) and (upload_data_size^ <> 0) then
  begin
    SetString(con_info^.Playload_AsText, upload_data, upload_data_size^);

    // Попытка разбора JSON, если указан соответствующий Content-Type
    if Assigned(MHD_lookup_connection_value(connection, MHD_HEADER_KIND,
      MHD_HTTP_HEADER_CONTENT_TYPE)) then
      if (Pos('application/json',
        MHD_lookup_connection_value(connection, MHD_HEADER_KIND,
        MHD_HTTP_HEADER_CONTENT_TYPE)) > 0) then
      begin
        if con_info^.Playload_AsText <> '' then
        try
          con_info^.PlayLoadIsJson := True;
          con_info^.PlayLoad_AsJson :=
            GetJSON(con_info^.Playload_AsText) as TJSONObject;
        except
          con_info^.Response.StatusCode := MHD_HTTP_INTERNAL_SERVER_ERROR;
          con_info^.Response.ResText := '{"error": "Invalid JSON format"}';
        end;
      end;

    upload_data_size^ := 0;
    Exit(MHD_YES);
  end;

  // Обработка запросов и формирование ответа
  if con_info^.Response.StatusCode = MHD_HTTP_OK then
  begin
    if url = '/shutdown' then
    begin
      jsonObj := TJSONObject.Create;
      jsonObj.Add('status', 'shutting down');
      con_info^.Response.ResText := jsonObj.AsJSON;
      jsonObj.Free;

      StopLock.Enter;
      try
        ShouldStop := True;
      finally
        StopLock.Leave;
      end;
    end
    else if (url = '/api/status') and (method = 'GET') then
    begin
      jsonObj := TJSONObject.Create;
      jsonObj.Add('status', 'ok');
      jsonObj.Add('uptime', FormatDateTime('hh:nn:ss', Now));
      con_info^.Response.ResText := jsonObj.AsJSON;
      jsonObj.Free;
    end
    else if url = '/api/echo' then
    begin
      if method = 'GET' then
      begin
        con_info^.Response.ResText := '{"method": "GET", "data": "echo"}';
      end
      else if method = 'POST' then
      begin
        if con_info^.PlayLoadIsJson then
        begin
          if Assigned(con_info^.PlayLoad_AsJson) then
            con_info^.Response.ResText := con_info^.PlayLoad_AsJson.AsJSON;
        end
        else
        begin
          con_info^.Response.ResText := con_info^.Playload_AsText;
        end;

        if con_info^.Response.ResText = '' then
        begin
          con_info^.Response.StatusCode := MHD_HTTP_NO_CONTENT;
          con_info^.Response.ResText := '{"error": "No data received"}';
        end;
      end;
    end;
  end;

  // Формирование и отправка ответа
  response := MHD_create_response_from_buffer(Length(con_info^.Response.ResText),
    PChar(con_info^.Response.ResText), MHD_RESPMEM_MUST_COPY);
  MHD_add_response_header(response, 'Content-Type', 'application/json');
  Result := MHD_queue_response(connection, con_info^.Response.StatusCode, response);

  // Освобождение ресурсов
  MHD_destroy_response(response);
  if Assigned(con_info^.PlayLoad_AsJson) then
    con_info^.PlayLoad_AsJson.Free;
  Dispose(con_info);
  con_cls^ := nil;
end;

begin
  StopLock := TCriticalSection.Create;
  try
    {$if Defined(THREADPOOL)}
    Daemon := MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD or
      MHD_USE_DEBUG, PORT, nil, nil, @HandleRestfulRequest,
      nil, MHD_OPTION_THREAD_POOL_SIZE, 4, MHD_OPTION_END);
    {$else}
    Daemon := MHD_start_daemon(
      MHD_USE_THREAD_PER_CONNECTION or MHD_USE_DEBUG or MHD_USE_INTERNAL_POLLING_THREAD,
      PORT,
      nil,
      nil,
      @HandleRestfulRequest,
      nil,
      MHD_OPTION_END
    );
    {$ifend}

    if Daemon = nil then
    begin
      WriteLn('Error starting HTTP server!');
      Halt(1);
    end;

    WriteLn('REST server started at: http://localhost:', PORT);
    WriteLn('Endpoints:');
    WriteLn('  GET /api/status - Server status');
    WriteLn('  GET /api/echo - Simple echo');
    WriteLn('  POST /api/echo - Echo back POST data');
    WriteLn('  GET /shutdown - Stop server');

    while not ShouldStop do
      Sleep(100);

    MHD_stop_daemon(Daemon);
    WriteLn('Server stopped.');
  finally
    StopLock.Free;
  end;
end.

Ключевые особенности решения

  1. Правильная обработка контекста соединения:
    Для каждого соединения создается структура TClientContextInfo
    Данные сохраняются между вызовами callback-функции
  2. Корректная обработка POST-запросов:
    Данные собираются полностью перед обработкой
    Поддержка JSON с автоматическим парсингом

  3. Потокобезопасность:
    Использование TCriticalSection для защиты общих ресурсов
    Возможность выбора модели потоков (пул или поток на соединение)

  4. Гибкая система ответов:
    Поддержка различных HTTP-статусов
    Автоматическое формирование JSON-ответов

Примеры использования

Сервер поддерживает несколько endpoint'ов:

# Получить статус сервера
curl -X GET http://localhost:8888/api/status

# Эхо GET-запрос
curl -X GET http://localhost:8888/api/echo

# Эхо POST-запрос с JSON
curl -X POST -H "Content-Type: application/json" -d '{"message":"test"}' http://localhost:8888/api/echo

# Остановка сервера
curl -X GET http://localhost:8888/shutdown

Дальнейшие улучшения

  1. Логирование - добавление системы логирования запросов и ошибок
  2. Конфигурация - вынесение параметров сервера в конфигурационный файл
  3. Маршрутизация - реализация более сложной системы маршрутизации запросов
  4. Аутентификация - добавление базовой аутентификации
  5. HTTPS - поддержка защищенного соединения

Этот пример демонстрирует, как с помощью libmicrohttpd и Object Pascal можно создать легковесный, но функциональный RESTful сервер, который может служить основой для более сложных решений.

Создано по материалам из источника по ссылке.

Статья описывает создание RESTful JSON сервера на Delphi/Pascal с использованием libmicrohttpd, включая решение проблем с обработкой POST-запросов, управлением контекстом и потокобезопасностью.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




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


:: Главная :: Интернет ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 20:14:06
2025-07-03 08:51:51/0.0061290264129639/0