
Préface
Notre petite équipe développe deux outils OpenSource pour les développeurs C ++ - le
framework d' acteur
SObjectizer et le serveur HTTP intégré RESTinio. Cependant, nous rencontrons régulièrement quelques questions non triviales:
- quelles fonctionnalités ajouter à la bibliothèque et lesquelles laisser «par dessus bord»?
- comment démontrer clairement les façons "idéologiquement correctes" d'utiliser la bibliothèque?
C'est bien lorsque les réponses à de telles questions apparaissent lors de l'utilisation de nos développements dans des projets réels, lorsque les développeurs viennent nous voir avec leurs plaintes ou leur liste de souhaits. En raison de la satisfaction des souhaits des utilisateurs, nous remplissons nos outils de fonctionnalités dictées par la vie elle-même et non «aspirées du doigt».
Mais l'information nous parvient loin de tous les problèmes et difficultés auxquels les utilisateurs sont confrontés. Et nous ne pouvons pas toujours utiliser les informations reçues, et en particulier les exemples de code, dans nos documents publics.
Par conséquent, nous pensons parfois de petits problèmes pour nous-mêmes, que nous sommes obligés de transformer des développeurs d'outils en utilisateurs. Cela nous permet de regarder nos propres outils avec des yeux différents et de comprendre par nous-mêmes ce qui est bon, ce qui ne l'est pas, ce qui manque et ce qui est trop.
Aujourd'hui, nous voulons parler d'une telle "petite" tâche, dans laquelle SObjectizer et RESTinio se sont naturellement rencontrés.
Mise à l'échelle et distribution des images. Pourquoi exactement ça?
Comme petite tâche de démonstration pour nous-mêmes, nous avons choisi un serveur HTTP qui distribue des images à l'échelle sur demande. Vous mettez les images dans un certain répertoire, démarrez le serveur HTTP, lui faites une requête de la forme:
curl "http://localhost:8080/my_picture.jpg?op=resize&max=1920"
et en retour, vous obtenez une image mise à l'échelle à 1920 pixels sur le long côté.
Le choix s'est porté sur cette tâche car elle illustre parfaitement les scénarios pour lesquels nous avons commencé à développer RESTinio à un moment donné: il existe un code de longue durée et débogué en C ou C ++ auquel vous devez attacher une entrée HTTP et commencer à répondre aux demandes entrantes. Dans le même temps, ce qui est important, le traitement de la demande par l'application peut prendre un temps considérable et il n'est donc pas rentable d'extraire le code d'application directement dans le contexte d'E / S. Le serveur HTTP doit être asynchrone: accepter et analyser la demande HTTP, donner la demande analysée quelque part pour un traitement ultérieur de l'application, passer à la maintenance de la demande HTTP suivante, revenir au retour de la réponse à la demande HTTP lorsque cette réponse est préparée par quelqu'un.
C'est exactement ce qui se passe lors du traitement des demandes de mise à l'échelle des images. Un serveur HTTP est capable de faire son travail direct (c'est-à-dire lire des données, analyser une requête HTTP) en une fraction de milliseconde. Mais la mise à l'échelle d'une image peut prendre des dizaines, des centaines, voire des milliers de millisecondes.
Et comme la mise à l'échelle d'une image peut prendre beaucoup de temps, vous devez vous assurer que le serveur HTTP peut continuer à fonctionner pendant la mise à l'échelle de l'image. Pour ce faire, nous devons répartir le travail du serveur HTTP et la mise à l'échelle des images dans différents contextes de travail. Dans le cas simple, il s'agira de différents fils de travail. Eh bien, puisque nous vivons dans des processeurs multicœurs, nous aurons plusieurs threads de travail. Certains d'entre eux serviront des requêtes HTTP, d'autres fonctionneront avec des images.
Il s'avère que pour distribuer des images évolutives via HTTP, nous devons réutiliser le code C / C ++ écrit et fonctionnel (dans ce cas, ImageMagic ++), et servir les requêtes HTTP de manière asynchrone, et effectuer le traitement des requêtes par application dans plusieurs workflows. Une excellente tâche pour RESTinio et SObjectizer, comme il nous a semblé.
Et nous avons décidé de nommer notre projet de démonstration de crevettes.
Crevettes telles quelles
Que fait la crevette?
Shrimp s'exécute comme une application console, s'ouvre et écoute sur le port spécifié, reçoit et traite les requêtes HTTP GET du formulaire:
/<image>.<ext> /<image>.<ext>?op=resize&<side>=<value>
Où:
- image est le nom du fichier image à mettre à l'échelle. Par exemple, my_picture ou DSCF0069;
- ext est l'une des extensions prises en charge par shrimp (jpg, jpeg, png ou gif);
- côté indique le côté pour lequel la taille est définie. Il peut avoir une valeur de largeur, dans ce cas, l'image est mise à l'échelle de sorte que la largeur résultante soit égale à la valeur spécifiée, la hauteur de l'image est automatiquement sélectionnée tout en conservant le rapport d'aspect. Ou la valeur de la hauteur, dans ce cas, la mise à l'échelle se produit en hauteur. Soit max, dans ce cas le côté long est limité, et la crevette elle-même détermine si le côté long est en hauteur ou en largeur;
- valeur est la taille à laquelle la mise à l'échelle se produit.
Si seul le nom du fichier est spécifié dans l'URL, sans l'opération de redimensionnement, shrimp renvoie simplement l'image d'origine dans la réponse. Si l'opération de redimensionnement est spécifiée, shrimp modifie la taille de l'image demandée et donne la version à l'échelle.
Dans le même temps, la crevette garde en mémoire un cache d'images à l'échelle. Si une image est demandée à plusieurs reprises avec les mêmes paramètres de redimensionnement, qui sont déjà dans le cache, la valeur du cache est retournée. S'il n'y a pas d'image dans le cache, alors l'image est lue sur le disque, mise à l'échelle, stockée dans le cache et renvoyée en réponse.
Le cache est effacé périodiquement. Les images qui ont vécu dans le cache pendant plus d'une heure depuis le dernier accès à celles-ci sont expulsées. De plus, les images les plus anciennes sont jetées hors du cache si le cache dépasse sa taille maximale (dans un projet de démonstration, il est de 100 Mo).
Nous avons préparé une
page sur laquelle chacun peut expérimenter la crevette:

Sur cette page, vous pouvez définir la taille de l'image et cliquer sur "Redimensionner". Deux demandes seront faites au serveur de crevettes avec les mêmes paramètres. Très probablement, la première demande sera unique (c'est-à-dire qu'il n'y aura pas encore de cache avec de tels paramètres de redimensionnement dans le cache), donc la première demande prendra un certain temps pour redimensionner réellement l'image. Et la deuxième demande, très probablement, trouvera l'image déjà mise à l'échelle dans le cache et la donnera immédiatement.
Il est possible de juger si une image est donnée à partir du cache ou si elle a vraiment été mise à l'échelle par le texte sous l'image. Par exemple, le texte «Transformé (114,0 ms)» indique que l'image a été mise à l'échelle et que l'opération de zoom a pris 114 millisecondes.
Comment les crevettes le font-elles?
Shrimp est une application multithread qui exécute trois groupes de threads de travail:
- Pool de threads de travail exécutant le serveur HTTP. Sur ce pool, de nouvelles connexions sont servies, les demandes entrantes sont reçues et analysées, les réponses sont générées et envoyées. Le serveur HTTP est implémenté via la bibliothèque RESTinio.
- Un thread de travail distinct sur lequel s'exécute l'agent transform_manager SObjectizer. Cet agent traite les demandes reçues du serveur HTTP et gère un cache d'images mises à l'échelle.
- Pool de threads sur lequel les agents SObjectizer travaillent sur les transformateurs. Ils effectuent la mise à l'échelle réelle des images à l'aide d'ImageMagic ++.
Il s'avère que le schéma de travail suivant:

Le serveur HTTP accepte la demande entrante, l'analyse et vérifie l'exactitude. Si cette demande ne nécessite pas d'opération de redimensionnement, le serveur HTTP lui-même traite la demande via l'opération
sendfile . Si la demande nécessite une opération de redimensionnement, la demande est envoyée de manière asynchrone à l'agent transform_manager.
L'agent transform_manager reçoit les demandes du serveur HTTP et vérifie la présence d'images déjà mises à l'échelle dans le cache. S'il y a une image dans le cache, transform_manager génère immédiatement une réponse pour le serveur HTTP. S'il n'y a pas d'image, transform_manager envoie une demande de mise à l'échelle de l'image à l'un des agents du transformateur. Lorsque le résultat de la mise à l'échelle provient du transformateur, le résultat est stocké dans le cache et une réponse est générée pour le serveur HTTP.
L'agent transformateur reçoit les demandes de transform_manager, les traite et renvoie le résultat de la transformation à l'agent transform_manager.
Qu'est-ce que les crevettes ont sous le capot?
Le code source de la version la plus minimale de shrimp décrite dans cet article se trouve dans ce référentiel:
shrimp-demo sur BitBucket ou
sur GitHub .
Il y a beaucoup de code, bien que, dans la plupart des cas, dans cette version de shrimp, le code soit assez trivial. Cependant, il est logique de se concentrer sur certains aspects de la mise en œuvre.
Utilisation de C ++ 17 et des versions de compilateur les plus récentes
Dans l'implémentation de shrimp, nous avons décidé d'utiliser C ++ 17 et les dernières versions des compilateurs, en particulier GCC 7.3 et 8.1. Le projet est fortement de recherche. Par conséquent, la connaissance pratique de C ++ 17 dans le cadre d'un tel projet est naturelle et autorisée. Alors que dans les développements plus banals axés sur les applications industrielles pratiques ici et maintenant, nous sommes obligés de revenir sur des compilateurs plutôt anciens et d'utiliser peut-être C ++ 14, ou même juste un sous-ensemble de C ++ 11.
Je dois dire que C ++ 17 fait bonne impression. Il semble que nous n'ayons pas utilisé autant d'innovations de la dix-septième norme dans le code de la crevette, mais elles ont eu un effet positif: l'attribut [[nodiscard]], std :: optional / std :: variant / std :: filesystem directement " out of the box », et non à partir de dépendances externes, de liaisons structurées, si constexpr, la possibilité d'assembler visiteur pour lambdas pour std :: visit ... Individuellement, ce sont toutes des bagatelles, mais ensemble, elles produisent un puissant effet cumulatif.
Donc, le premier résultat utile que nous avons obtenu lors du développement de crevettes: C ++ 17 vaut la peine d'y passer.
Serveur HTTP utilisant les outils RESTinio
Peut-être que la partie la plus simple de shrimp s'est avérée être le serveur HTTP et le gestionnaire de requêtes HTTP GET (
http_server.hpp et
http_server.cpp ).
Recevoir et envoyer les demandes entrantes
Essentiellement, toute la logique de base du serveur HTTP shrimp est concentrée dans cette fonction:
void add_transform_op_handler( const app_params_t & app_params, http_req_router_t & router, so_5::mbox_t req_handler_mbox ) { router.http_get( R"(/:path(.*)\.:ext(.{3,4}))", restinio::path2regex::options_t{}.strict( true ), [req_handler_mbox, &app_params]( auto req, auto params ) { if( has_illegal_path_components( req->header().path() ) ) { return do_400_response( std::move( req ) ); } const auto opt_image_format = image_format_from_extension( params[ "ext" ] ); if( !opt_image_format ) { return do_400_response( std::move( req ) ); } if( req->header().query().empty() ) { return serve_as_regular_file( app_params.m_storage.m_root_dir, std::move( req ), *opt_image_format ); } const auto qp = restinio::parse_query( req->header().query() ); if( "resize" != restinio::value_or( qp, "op"sv, ""sv ) ) { return do_400_response( std::move( req ) ); } handle_resize_op_request( req_handler_mbox, *opt_image_format, qp, std::move( req ) ); return restinio::request_accepted(); } ); }
Cette fonction prépare le gestionnaire de requêtes HTTP GET à l'aide du
routeur RESTinio
ExpressJS . Lorsque le serveur HTTP reçoit une demande GET, dont l'URL tombe sous l'expression régulière donnée, la fonction lambda spécifiée est appelée.
Cette fonction lambda effectue quelques vérifications simples sur l'exactitude de la demande, mais dans l'ensemble, son travail se résume à un choix simple: si le redimensionnement n'est pas défini, l'image demandée sera retournée dans sa forme d'origine en utilisant un système d'envoi efficace. Si le mode de redimensionnement est défini, un message est généré et envoyé à l'agent transform_manager:
void handle_resize_op_request( const so_5::mbox_t & req_handler_mbox, image_format_t image_format, const restinio::query_string_params_t & qp, restinio::request_handle_t req ) { try_to_handle_request( [&]{ auto op_params = transform::resize_params_t::make( restinio::opt_value< std::uint32_t >( qp, "width" ), restinio::opt_value< std::uint32_t >( qp, "height" ), restinio::opt_value< std::uint32_t >( qp, "max" ) ); transform::resize_params_constraints_t{}.check( op_params ); std::string image_path{ req->header().path() }; so_5::send< so_5::mutable_msg<a_transform_manager_t::resize_request_t>>( req_handler_mbox, std::move(req), std::move(image_path), image_format, op_params ); }, req ); }
Il s'avère que le serveur HTTP, après avoir accepté la demande de redimensionnement, la transmet à l'agent transform_manager via un message asynchrone et continue de servir d'autres demandes.
Partage de fichiers avec sendfile
Si le serveur HTTP détecte une demande pour l'image d'origine, sans l'opération de redimensionnement, le serveur envoie immédiatement cette image via l'opération sendfile. Le code principal associé à ceci est le suivant (le code complet de cette fonction peut être trouvé
dans le référentiel ):
[[nodiscard]] restinio::request_handling_status_t serve_as_regular_file( const std::string & root_dir, restinio::request_handle_t req, image_format_t image_format ) { const auto full_path = make_full_path( root_dir, req->header().path() ); try { auto sf = restinio::sendfile( full_path ); ... return set_common_header_fields_for_image_resp( file_stat.st_mtim.tv_sec, resp ) .append_header( restinio::http_field::content_type, image_content_type_from_img_format( image_format ) ) .append_header( http_header::shrimp_image_src, image_src_to_str( http_header::image_src_t::sendfile ) ) .set_body( std::move( sf ) ) .done(); } catch(...) {} return do_404_response( std::move( req ) ); }
Le point clé ici est d'appeler
restinio :: sendfile () , puis de passer la valeur retournée par cette fonction à set_body ().
La fonction restinio :: sendfile () crée une opération de téléchargement de fichier à l'aide de l'API système. Lorsque cette opération est passée à set_body (), RESTinio comprend que le contenu du fichier spécifié dans restinio :: sendfile () sera utilisé pour le corps de la réponse HTTP. Il utilise ensuite l'API système pour écrire le contenu de ce fichier dans le socket TCP.
Implémentation du cache d'images
L'agent transform_manager stocke le cache des images converties, où les images sont placées après la mise à l'échelle. Ce cache est un simple conteneur self-made qui donne accès à son contenu de deux manières:
- En recherchant un élément par clé (similaire à la façon dont cela se produit dans les conteneurs standard std :: map et std :: unordered_map).
- En accédant à l'élément de cache le plus ancien.
La première méthode d'accès est utilisée lorsque nous devons vérifier la disponibilité de l'image dans le cache. La seconde est lorsque nous supprimons les plus anciennes images du cache.
Nous n'avons pas commencé à chercher quelque chose de prêt à ces fins sur Internet. Probablement Boost.MultiIndex conviendrait parfaitement ici. Mais je ne voulais pas faire glisser Boost juste pour le plaisir de MultiIndex, nous avons donc fait
notre implémentation triviale littéralement à genoux. Cela semble fonctionner;)
File d'attente des demandes en attente dans transform_manager
L'agent transform_manager, malgré sa taille plutôt décente (un
fichier hpp d' environ 250 lignes et un
fichier cpp d' environ 270 lignes), dans notre implémentation la plus simple de shrimp, s'est avéré plutôt trivial, à notre avis.
L'une des choses qui contribue de manière significative à la complexité et à la quantité de code d'agent est la présence non seulement d'un cache d'images transformées dans transform_manager, mais également de files d'attente de demandes en attente.
Nous avons un nombre limité d'agents transformateurs (en principe, leur nombre devrait correspondre approximativement au nombre de cœurs de traitement disponibles). Si plus de demandes arrivent simultanément qu'il n'y a de transformateurs libres, nous pouvons alors immédiatement répondre négativement à la demande ou mettre la demande en file d'attente. Et puis retirez-le de la file d'attente lorsqu'un transformateur libre apparaît.
Dans shrimp, nous utilisons une file d'attente de requêtes en attente, qui est définie comme suit:
struct pending_request_t { transform::resize_request_key_t m_key; sobj_shptr_t<resize_request_t> m_cmd; std::chrono::steady_clock::time_point m_stored_at; pending_request_t( transform::resize_request_key_t key, sobj_shptr_t<resize_request_t> cmd, std::chrono::steady_clock::time_point stored_at ) : m_key{ std::move(key) } , m_cmd{ std::move(cmd) } , m_stored_at{ stored_at } {} }; using pending_request_queue_t = std::queue<pending_request_t>; pending_request_queue_t m_pending_requests; static constexpr std::size_t max_pending_requests{ 64u };
Dès réception de la demande, nous la mettons en file d'attente avec fixation de l'heure de réception de la demande. Ensuite, nous vérifions périodiquement si le délai d'expiration de cette demande a expiré. En effet, en principe, il peut arriver qu'un ensemble de demandes «lourdes» arrive plus tôt, dont le traitement prend trop de temps. Il est faux d'attendre sans fin qu'un transformateur libre apparaisse, il est préférable d'envoyer une réponse négative au client après un certain temps, ce qui signifie que le service est maintenant surchargé.
Il existe également une limite de taille pour la file d'attente des demandes en attente. Si la file d'attente a déjà atteint sa taille maximale, nous refusons immédiatement de traiter la demande et informons le client que nous sommes surchargés.
Il y a un point important lié à la file d'attente des demandes en attente, sur lequel nous nous concentrerons dans la conclusion de l'article.
Tapez sobj_shptr_t et réutilisez les instances de message
En déterminant le type de la file d'attente des requêtes en attente, ainsi que dans les signatures de certaines méthodes de transform_manager, vous pouvez voir l'utilisation du type sobj_shptr_t. Il est logique de s'attarder plus en détail sur son type et pourquoi il est utilisé.
L'essentiel, c'est que transform_manager reçoit une demande du serveur HTTP sous la forme d'un message resize_request_t:
struct resize_request_t final : public so_5::message_t { restinio::request_handle_t m_http_req; std::string m_image; image_format_t m_image_format; transform::resize_params_t m_params; resize_request_t( restinio::request_handle_t http_req, std::string image, image_format_t image_format, transform::resize_params_t params ) : m_http_req{ std::move(http_req) } , m_image{ std::move(image) } , m_image_format{ image_format } , m_params{ params } {} };
et nous devons faire quelque chose pour stocker ces informations dans la file d'attente des demandes en attente. Par exemple, vous pouvez créer une nouvelle instance de resize_request_t et y déplacer les valeurs du message reçu.
Et vous vous souvenez que le message lui-même dans SObjectizer est un objet créé dynamiquement. Et pas un simple objet, mais avec un compteur de liens à l'intérieur. Et que dans SObjectizer il existe un type spécial de pointeur intelligent pour de tels objets - intrusive_ptr_t.
C'est-à-dire nous ne pouvons pas faire une copie de resize_request_t pour la file d'attente des demandes en attente, mais nous pouvons simplement mettre dans cette file d'attente un pointeur intelligent vers une instance existante de resize_request_t. Ce que nous faisons. Et pour ne pas écrire partout le nom plutôt exotique so_5 :: intrusive_ptr_t, on entre dans notre alias:
template<typename T> using sobj_shptr_t = so_5::intrusive_ptr_t<T>;
Réponses asynchrones aux clients
Nous avons dit que les requêtes HTTP sont traitées de manière asynchrone. Et nous avons montré ci-dessus comment le serveur HTTP envoie une requête à l'agent transform_manager avec un message asynchrone. Mais qu'advient-il des réponses aux requêtes HTTP?
Les réponses sont également servies de manière asynchrone. Par exemple, dans le code transform_manager, vous pouvez voir ce qui suit:
void a_transform_manager_t::on_failed_resize( failed_resize_t & , sobj_shptr_t<resize_request_t> cmd ) { do_404_response( std::move(cmd->m_http_req) ); }
Ce code génère une réponse négative à la demande HTTP dans le cas où l'image n'a pas pu être mise à l'échelle pour une raison quelconque. La réponse est générée dans la fonction d'aide do_404_response, dont le code peut être représenté comme suit:
auto do_404_response( restinio::request_handle_t req ) { auto resp = req->create_response( 404, "Not Found" ); resp.append_header( restinio::http_field_t::server, "Shrimp draft server" ); resp.append_header_date_field(); if( req->header().should_keep_alive() ) resp.connection_keep_alive(); else resp.connection_close(); return resp.done(); }
Le premier point clé de do_404_response () est que cette fonction est appelée sur le contexte de travail de l'agent transform_manager, et non sur le contexte de travail du serveur HTTP.
Le deuxième point clé est l'appel à la méthode done () sur l'objet resp entièrement formé. Toute la magie asynchrone avec une réponse HTTP se produit ici. La méthode done () prend toutes les informations préparées dans resp et les envoie de manière asynchrone au serveur HTTP. C'est-à-dire un retour de do_404_response () se produira immédiatement après la mise en file d'attente du contenu de l'objet resp par le serveur HTTP.
Le serveur HTTP dans son contexte de travail détectera la présence d'une nouvelle réponse HTTP et commencera à prendre les mesures nécessaires pour envoyer la réponse au client approprié.
Tapez datasizable_blob_t
Un autre petit point qui a du sens à clarifier, car il est probablement incompréhensible sans comprendre les subtilités de RESTinio. Nous parlons de la présence, à première vue, d'un type étrange de datasizeable_blob_t, défini comme suit:
struct datasizable_blob_t : public std::enable_shared_from_this< datasizable_blob_t > { const void * data() const noexcept { return m_blob.data(); } std::size_t size() const noexcept { return m_blob.length(); } Magick::Blob m_blob;
Afin d'expliquer pourquoi ce type est nécessaire, vous devez montrer comment une réponse HTTP est formée avec une image transformée:
void serve_transformed_image( restinio::request_handle_t req, datasizable_blob_shared_ptr_t blob, image_format_t img_format, http_header::image_src_t image_src, header_fields_list_t header_fields ) { auto resp = req->create_response(); set_common_header_fields_for_image_resp( blob->m_last_modified_at, resp ) .append_header( restinio::http_field::content_type, image_content_type_from_img_format( img_format ) ) .append_header( http_header::shrimp_image_src, image_src_to_str( image_src ) ) .set_body( std::move( blob ) ); for( auto & hf : header_fields ) { resp.append_header( std::move( hf.m_name ), std::move( hf.m_value ) ); } resp.done(); }
Nous prêtons attention à l'appel à set_body (): un pointeur intelligent vers l'instance datasizable_blob_t y est envoyé directement. Pourquoi?
Le fait est que
RESTinio prend en charge plusieurs options pour former le corps d'une réponse HTTP . Le plus simple est de passer une instance de type std :: string à set_body () et RESTinio enregistrera la valeur de cette chaîne à l'intérieur de l'objet resp.
Mais il y a des moments où la valeur de set_body () doit être réutilisée dans plusieurs réponses à la fois. Par exemple, dans crevette, cela se produit lorsque la crevette reçoit plusieurs demandes identiques de transformation de la même image.
Dans ce cas, il n'est pas rentable de copier la même valeur dans chaque réponse. Par conséquent, dans RESTinio, il existe une variante set_body () de la forme: template<typename T> auto set_body(std::shared_ptr<T> body);
Mais dans ce cas, une limitation importante est imposée au type T: il doit contenir les méthodes public data () et size (), qui sont nécessaires pour que RESTinio puisse accéder au contenu de la réponse.L'image mise à l'échelle dans la crevette est stockée en tant qu'objet Magick :: Blob. Il existe une méthode de données dans le type Magic :: Blob, mais il n'y a pas de méthode size (), mais il existe une méthode length (). Par conséquent, nous avions besoin de la classe wrapper datasizable_blob_t, qui fournit à RESTinio l'interface nécessaire pour accéder à la valeur de Magick :: Blob.Messages périodiques dans transform_manager
L'agent transform_manager doit faire plusieurs choses de temps en temps:- Tirez des images qui ont été dans le cache depuis trop longtemps à partir du cache.
- contrôler le temps passé par les requêtes dans la file d'attente des transformateurs libres.
L'agent transform_manager effectue ces actions via des messages périodiques. Il ressemble à ceci.Tout d'abord, les types de signaux qui seront utilisés comme messages périodiques sont déterminés: struct clear_cache_t final : public so_5::signal_t {}; struct check_pending_requests_t final : public so_5::signal_t {};
Ensuite, l'agent est abonné, y compris ces signaux: void a_transform_manager_t::so_define_agent() { so_subscribe_self() .event( &a_transform_manager_t::on_resize_request ) .event( &a_transform_manager_t::on_resize_result ) .event( &a_transform_manager_t::on_clear_cache ) .event( &a_transform_manager_t::on_check_pending_requests ); } void a_transform_manager_t::on_clear_cache( mhood_t<clear_cache_t> ) {...} void a_transform_manager_t::on_check_pending_requests( mhood_t<check_pending_requests_t> ) {...}
Grâce à l'abonnement, SObjectizer appellera le gestionnaire souhaité lorsque l'agent recevra le signal correspondant.Et il ne reste plus qu'à exécuter des messages périodiques au démarrage de l'agent: void a_transform_manager_t::so_evt_start() { m_clear_cache_timer = so_5::send_periodic<clear_cache_t>( *this, clear_cache_period, clear_cache_period ); m_check_pending_timer = so_5::send_periodic<check_pending_requests_t>( *this, check_pending_period, check_pending_period ); }
Le point clé ici est de sauvegarder timer_id, qui sont retournés par les fonctions send_periodic (). Après tout, un signal périodique ne viendra que tant que son timer_id est vivant. Par conséquent, si la valeur de retour de send_periodic () n'est pas enregistrée, l'envoi d'un message périodique sera immédiatement annulé. Par conséquent, la classe a_transform_manager_t a les attributs suivants: so_5::timer_id_t m_clear_cache_timer; so_5::timer_id_t m_check_pending_timer;
Fin de la première partie
Aujourd'hui, nous avons présenté au lecteur la mise en œuvre la plus simple et la plus minimaliste des crevettes. Cette implémentation est suffisante pour montrer comment RESTinio et SObjectizer peuvent être utilisés ensemble pour quelque chose de plus ou moins comme une vraie tâche, plutôt qu'un simple HelloWorld. Mais il présente un certain nombre de défauts graves.Par exemple, dans l'agent transform_manager, il y a une certaine vérification de l'unicité de la demande. Mais cela ne fonctionne que si l'image transformée est déjà dans le cache. S'il n'y a pas encore d'image dans le cache et en même temps deux demandes identiques viennent pour la même image, alors ces deux demandes seront envoyées pour traitement. Ce qui n'est pas bon. Il serait correct de ne traiter qu'un seul d'entre eux et de reporter le second jusqu'à ce que le traitement du premier soit terminé.Un tel contrôle plus avancé sur l'unicité des requêtes conduirait à un code transform_manager beaucoup plus complexe et volumineux. Par conséquent, nous n'avons pas commencé à le mettre en œuvre immédiatement, mais avons décidé d'emprunter la voie de l'évolution - du simple au complexe.De plus, la version la plus simple de la crevette est une «boîte noire» qui ne montre aucun signe de son travail. Ce qui n'est pas très pratique à la fois pendant les tests et pendant le fonctionnement. Par conséquent, dans le bon sens, les crevettes devraient également ajouter une exploitation forestière.Nous essaierons d'éliminer ces défauts et quelques autres de la toute première version de la crevette dans les futures versions et de les décrire dans les prochains articles. Alors restez à l'écoute.Si quelqu'un a des questions sur la logique de la crevette, RESTinio ou SObjectizer, nous serons heureux de répondre dans les commentaires. De plus, la crevette elle-même est un projet de démonstration, mais si quelqu'un s'intéresse à sa fonctionnalité et souhaite voir dans la crevette autre chose que l'opération de redimensionnement, faites-le nous savoir, nous serons heureux d'écouter toutes les idées constructives .À suivre ...