🔝 Retour au Sommaire
REST (Representational State Transfer) est un style d'architecture pour les services web. Une API REST est comme un menu de restaurant : elle liste toutes les actions disponibles (endpoints) et comment les demander.
Imaginez que vous voulez commander au restaurant :
- GET : "Montrez-moi le menu" (lire des données)
- POST : "Je commande un plat" (créer des données)
- PUT : "Modifiez ma commande" (mettre à jour des données)
- DELETE : "Annulez ma commande" (supprimer des données)
Sans état (Stateless) : Chaque requête contient toutes les informations nécessaires. Le serveur ne garde pas de "mémoire" des requêtes précédentes.
Basé sur HTTP : Utilise les méthodes HTTP standard (GET, POST, PUT, DELETE).
Format de données : Généralement JSON, parfois XML.
URL significatives : Les URLs reflètent la structure des ressources (ex: /api/utilisateurs/123).
Codes de statut HTTP : Utilise les codes standard (200 = OK, 404 = Non trouvé, etc.).
- Universelles : Fonctionnent sur toutes les plateformes et tous les langages
- Simples : Basées sur HTTP, faciles à comprendre et utiliser
- Flexibles : Séparation claire entre client et serveur
- Scalables : Sans état, faciles à mettre à l'échelle
- Standardisées : Conventions communes dans toute l'industrie
La méthode GET est utilisée pour lire des données sans les modifier.
uses
System.Net.HttpClient;
function RecupererUtilisateur(UserID: Integer): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
URL: string;
begin
URL := Format('https://api.example.com/users/%d', [UserID]);
HttpClient := THTTPClient.Create;
try
Response := HttpClient.Get(URL);
if Response.StatusCode = 200 then
Result := Response.ContentAsString
else
raise Exception.CreateFmt('Erreur: %d - %s',
[Response.StatusCode, Response.StatusText]);
finally
HttpClient.Free;
end;
end;
// Utilisation
procedure TForm1.Button1Click(Sender: TObject);
var
JSON: string;
begin
JSON := RecupererUtilisateur(123);
Memo1.Lines.Text := JSON;
end;Les paramètres sont ajoutés à l'URL après un ? :
function RechercherUtilisateurs(const Nom: string; Age: Integer): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
URL: string;
begin
// URL avec paramètres: /users?name=Jean&age=30
URL := Format('https://api.example.com/users?name=%s&age=%d',
[TNetEncoding.URL.Encode(Nom), Age]);
HttpClient := THTTPClient.Create;
try
Response := HttpClient.Get(URL);
Result := Response.ContentAsString;
finally
HttpClient.Free;
end;
end;
// Alternative avec TURI (plus propre, gère l'encodage des paramètres)
function RechercherUtilisateursV2(const Nom: string; Age: Integer): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
URL: TURI;
begin
// TURI vient de l'unité System.Net.URLClient.
URL := TURI.Create('https://api.example.com/users');
// Ajouter les paramètres (TURI URL-encode automatiquement les valeurs
// lors du ToString final, contrairement à la concaténation manuelle).
URL := URL.AddParameter('name', Nom);
URL := URL.AddParameter('age', IntToStr(Age));
HttpClient := THTTPClient.Create;
try
Response := HttpClient.Get(URL.ToString);
Result := Response.ContentAsString;
finally
HttpClient.Free;
end;
end;POST envoie des données au serveur pour créer une nouvelle ressource.
uses
System.JSON;
function CreerUtilisateur(const Nom, Email: string; Age: Integer): Boolean;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
JSONRequest: TJSONObject;
PostData: TStringStream;
Headers: TNetHeaders;
begin
Result := False;
// Créer le JSON
JSONRequest := TJSONObject.Create;
try
JSONRequest.AddPair('name', Nom);
JSONRequest.AddPair('email', Email);
JSONRequest.AddPair('age', TJSONNumber.Create(Age));
// Créer le stream avec le JSON
PostData := TStringStream.Create(JSONRequest.ToString, TEncoding.UTF8);
try
HttpClient := THTTPClient.Create;
try
// Définir les en-têtes
SetLength(Headers, 1);
Headers[0] := TNetHeader.Create('Content-Type', 'application/json');
// Envoyer la requête POST
Response := HttpClient.Post('https://api.example.com/users',
PostData, nil, Headers);
// 201 Created = succès
Result := Response.StatusCode = 201;
if Result then
ShowMessage('Utilisateur créé: ' + Response.ContentAsString)
else
ShowMessage('Erreur: ' + IntToStr(Response.StatusCode));
finally
HttpClient.Free;
end;
finally
PostData.Free;
end;
finally
JSONRequest.Free;
end;
end;
// Utilisation
procedure TForm1.ButtonCreerClick(Sender: TObject);
begin
CreerUtilisateur('Jean Dupont', 'jean@example.com', 30);
end;PUT remplace complètement une ressource existante.
function MettreAJourUtilisateur(UserID: Integer; const Nom, Email: string): Boolean;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
JSONRequest: TJSONObject;
PostData: TStringStream;
Headers: TNetHeaders;
URL: string;
begin
Result := False;
URL := Format('https://api.example.com/users/%d', [UserID]);
JSONRequest := TJSONObject.Create;
try
JSONRequest.AddPair('name', Nom);
JSONRequest.AddPair('email', Email);
PostData := TStringStream.Create(JSONRequest.ToString, TEncoding.UTF8);
try
HttpClient := THTTPClient.Create;
try
SetLength(Headers, 1);
Headers[0] := TNetHeader.Create('Content-Type', 'application/json');
Response := HttpClient.Put(URL, PostData, nil, Headers);
Result := Response.StatusCode = 200;
finally
HttpClient.Free;
end;
finally
PostData.Free;
end;
finally
JSONRequest.Free;
end;
end;PATCH modifie seulement certains champs (pas toute la ressource).
function ModifierEmail(UserID: Integer; const NouvelEmail: string): Boolean;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
JSONRequest: TJSONObject;
PostData: TStringStream;
Headers: TNetHeaders;
URL: string;
begin
Result := False;
URL := Format('https://api.example.com/users/%d', [UserID]);
JSONRequest := TJSONObject.Create;
try
// On ne change que l'email
JSONRequest.AddPair('email', NouvelEmail);
PostData := TStringStream.Create(JSONRequest.ToString, TEncoding.UTF8);
try
HttpClient := THTTPClient.Create;
try
SetLength(Headers, 1);
Headers[0] := TNetHeader.Create('Content-Type', 'application/json');
Response := HttpClient.Patch(URL, PostData, nil, Headers);
Result := Response.StatusCode = 200;
finally
HttpClient.Free;
end;
finally
PostData.Free;
end;
finally
JSONRequest.Free;
end;
end;DELETE supprime une ressource.
function SupprimerUtilisateur(UserID: Integer): Boolean;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
URL: string;
begin
URL := Format('https://api.example.com/users/%d', [UserID]);
HttpClient := THTTPClient.Create;
try
Response := HttpClient.Delete(URL);
// 204 No Content = suppression réussie
Result := (Response.StatusCode = 200) or (Response.StatusCode = 204);
if Result then
ShowMessage('Utilisateur supprimé')
else
ShowMessage('Erreur: ' + IntToStr(Response.StatusCode));
finally
HttpClient.Free;
end;
end;Les en-têtes HTTP contiennent des métadonnées sur la requête ou la réponse.
function RequeteAvecHeaders: string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
Headers: TNetHeaders;
begin
HttpClient := THTTPClient.Create;
try
SetLength(Headers, 5);
// Content-Type : Type de données envoyées
Headers[0] := TNetHeader.Create('Content-Type', 'application/json');
// Accept : Type de données acceptées en réponse
Headers[1] := TNetHeader.Create('Accept', 'application/json');
// Authorization : Authentification
Headers[2] := TNetHeader.Create('Authorization', 'Bearer MON_TOKEN');
// User-Agent : Identification du client
Headers[3] := TNetHeader.Create('User-Agent', 'MonApp/1.0');
// Custom header : En-tête personnalisé
Headers[4] := TNetHeader.Create('X-Custom-Header', 'Valeur');
Response := HttpClient.Get('https://api.example.com/data', nil, Headers);
Result := Response.ContentAsString;
finally
HttpClient.Free;
end;
end;procedure AfficherHeadersReponse;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
Header: TNetHeader;
begin
HttpClient := THTTPClient.Create;
try
Response := HttpClient.Get('https://api.example.com/data');
Memo1.Lines.Add('En-têtes de réponse:');
Memo1.Lines.Add('---');
for Header in Response.Headers do
Memo1.Lines.Add(Header.Name + ': ' + Header.Value);
// Accéder à un en-tête spécifique
Memo1.Lines.Add('---');
Memo1.Lines.Add('Content-Type: ' + Response.HeaderValue['Content-Type']);
finally
HttpClient.Free;
end;
end;Les codes HTTP indiquent le résultat de la requête :
2xx - Succès
- 200 OK : Requête réussie
- 201 Created : Ressource créée avec succès
- 204 No Content : Succès sans contenu à retourner
3xx - Redirection
- 301 Moved Permanently : Ressource déplacée définitivement
- 302 Found : Redirection temporaire
- 304 Not Modified : Contenu non modifié (cache)
4xx - Erreur client
- 400 Bad Request : Requête invalide
- 401 Unauthorized : Authentification requise
- 403 Forbidden : Accès interdit
- 404 Not Found : Ressource introuvable
- 429 Too Many Requests : Trop de requêtes
5xx - Erreur serveur
- 500 Internal Server Error : Erreur serveur
- 502 Bad Gateway : Erreur de passerelle
- 503 Service Unavailable : Service temporairement indisponible
procedure TraiterReponseHTTP(Response: IHTTPResponse);
begin
case Response.StatusCode of
200:
ShowMessage('Succès: ' + Response.ContentAsString);
201:
ShowMessage('Ressource créée avec succès');
204:
ShowMessage('Opération réussie (pas de contenu)');
400:
raise Exception.Create('Requête invalide: ' + Response.ContentAsString);
401:
raise Exception.Create('Authentification requise');
403:
raise Exception.Create('Accès interdit');
404:
raise Exception.Create('Ressource introuvable');
429:
ShowMessage('Trop de requêtes, veuillez patienter');
500..599:
raise Exception.Create('Erreur serveur: ' + Response.StatusText);
else
raise Exception.CreateFmt('Code inattendu: %d', [Response.StatusCode]);
end;
end;function RequeteAvecAPIKey(const APIKey: string): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
URL: string;
begin
URL := 'https://api.example.com/data?api_key=' + APIKey;
HttpClient := THTTPClient.Create;
try
Response := HttpClient.Get(URL);
Result := Response.ContentAsString;
finally
HttpClient.Free;
end;
end;function RequeteAvecAPIKeyHeader(const APIKey: string): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
Headers: TNetHeaders;
begin
HttpClient := THTTPClient.Create;
try
SetLength(Headers, 1);
Headers[0] := TNetHeader.Create('X-API-Key', APIKey);
Response := HttpClient.Get('https://api.example.com/data', nil, Headers);
Result := Response.ContentAsString;
finally
HttpClient.Free;
end;
end;function RequeteAvecBearerToken(const Token: string): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
Headers: TNetHeaders;
begin
HttpClient := THTTPClient.Create;
try
SetLength(Headers, 1);
Headers[0] := TNetHeader.Create('Authorization', 'Bearer ' + Token);
Response := HttpClient.Get('https://api.example.com/data', nil, Headers);
Result := Response.ContentAsString;
finally
HttpClient.Free;
end;
end;uses
System.NetEncoding;
function RequeteAvecBasicAuth(const Username, Password: string): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
Headers: TNetHeaders;
Credentials: string;
begin
// Encoder en Base64 : "username:password"
Credentials := TNetEncoding.Base64.Encode(Username + ':' + Password);
HttpClient := THTTPClient.Create;
try
SetLength(Headers, 1);
Headers[0] := TNetHeader.Create('Authorization', 'Basic ' + Credentials);
Response := HttpClient.Get('https://api.example.com/data', nil, Headers);
Result := Response.ContentAsString;
finally
HttpClient.Free;
end;
end;uses
System.JSON;
procedure ParserUtilisateur(const JSON: string);
var
JSONValue: TJSONValue;
JSONObject: TJSONObject;
Nom, Email: string;
Age: Integer;
begin
JSONValue := TJSONObject.ParseJSONValue(JSON);
try
if JSONValue is TJSONObject then
begin
JSONObject := JSONValue as TJSONObject;
// Extraire les valeurs
Nom := JSONObject.GetValue<string>('name');
Email := JSONObject.GetValue<string>('email');
Age := JSONObject.GetValue<Integer>('age');
ShowMessage(Format('Utilisateur: %s, %d ans, %s', [Nom, Age, Email]));
end;
finally
JSONValue.Free;
end;
end;procedure ParserListeUtilisateurs(const JSON: string);
var
JSONValue: TJSONValue;
JSONArray: TJSONArray;
JSONObject: TJSONObject;
I: Integer;
Nom: string;
begin
JSONValue := TJSONObject.ParseJSONValue(JSON);
try
if JSONValue is TJSONArray then
begin
JSONArray := JSONValue as TJSONArray;
Memo1.Lines.Clear;
Memo1.Lines.Add('Liste des utilisateurs:');
for I := 0 to JSONArray.Count - 1 do
begin
JSONObject := JSONArray.Items[I] as TJSONObject;
Nom := JSONObject.GetValue<string>('name');
Memo1.Lines.Add(Format('%d. %s', [I + 1, Nom]));
end;
end;
finally
JSONValue.Free;
end;
end;// JSON: {"user": {"name": "Jean", "address": {"city": "Paris"}}}
procedure ParserJSONImbrique(const JSON: string);
var
JSONRoot, JSONUser, JSONAddress: TJSONObject;
Nom, Ville: string;
begin
JSONRoot := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
try
JSONUser := JSONRoot.GetValue<TJSONObject>('user');
Nom := JSONUser.GetValue<string>('name');
JSONAddress := JSONUser.GetValue<TJSONObject>('address');
Ville := JSONAddress.GetValue<string>('city');
ShowMessage(Format('%s habite à %s', [Nom, Ville]));
finally
JSONRoot.Free;
end;
end;function ObtenirValeurOuDefaut(JSONObject: TJSONObject;
const Key, Default: string): string;
var
Value: TJSONValue;
begin
Value := JSONObject.GetValue(Key);
// TJSONValue.Value est protégé : on passe par AsType<string>
// pour récupérer la valeur brute, sans guillemets.
if (Value <> nil) and not (Value is TJSONNull) then
Result := Value.AsType<string>
else
Result := Default;
end;
// Utilisation
procedure ParserAvecDefauts(const JSON: string);
var
JSONObject: TJSONObject;
Nom, Email: string;
begin
JSONObject := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
try
Nom := ObtenirValeurOuDefaut(JSONObject, 'name', 'Inconnu');
Email := ObtenirValeurOuDefaut(JSONObject, 'email', 'Non fourni');
ShowMessage(Format('Nom: %s, Email: %s', [Nom, Email]));
finally
JSONObject.Free;
end;
end;function RecupererUtilisateursAvecPagination(Page, ParPage: Integer): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
URL: string;
Offset: Integer;
begin
Offset := (Page - 1) * ParPage;
URL := Format('https://api.example.com/users?limit=%d&offset=%d',
[ParPage, Offset]);
HttpClient := THTTPClient.Create;
try
Response := HttpClient.Get(URL);
Result := Response.ContentAsString;
finally
HttpClient.Free;
end;
end;
// Utilisation : récupérer la page 2 avec 10 éléments par page
procedure TForm1.Button1Click(Sender: TObject);
var
JSON: string;
begin
JSON := RecupererUtilisateursAvecPagination(2, 10);
Memo1.Lines.Text := JSON;
end;Note : les exemples suivants s'appuient sur une petite fonction utilitaire
FaireRequeteGETqui encapsule un appel GET basique. Sa définition (à placer une seule fois dans votre unité) :function FaireRequeteGET(const URL: string): string; var HttpClient: THTTPClient; Response: IHTTPResponse; begin HttpClient := THTTPClient.Create; try Response := HttpClient.Get(URL); Result := Response.ContentAsString; finally HttpClient.Free; end; end;
function RecupererPage(NumeroPage, TaillePage: Integer): string;
var
URL: string;
begin
URL := Format('https://api.example.com/users?page=%d&per_page=%d',
[NumeroPage, TaillePage]);
Result := FaireRequeteGET(URL);
end;type
TResultatPagine = record
Donnees: string;
ProchainCurseur: string;
APlusDePages: Boolean;
end;
function RecupererAvecCurseur(const Curseur: string = ''): TResultatPagine;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
JSONResponse: TJSONObject;
URL: string;
begin
URL := 'https://api.example.com/users';
// Les curseurs renvoyés par les API sont souvent encodés en base64
// (contiennent '+', '/', '=') : sans URL-encoding ils seraient
// mal interprétés par le serveur.
if Curseur <> '' then
URL := URL + '?cursor=' + TNetEncoding.URL.Encode(Curseur);
HttpClient := THTTPClient.Create;
try
Response := HttpClient.Get(URL);
JSONResponse := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject;
try
Result.Donnees := JSONResponse.GetValue<TJSONArray>('data').ToString;
Result.ProchainCurseur := JSONResponse.GetValue<string>('next_cursor');
Result.APlusDePages := Result.ProchainCurseur <> '';
finally
JSONResponse.Free;
end;
finally
HttpClient.Free;
end;
end;
// Récupérer toutes les pages
procedure RecupererToutesLesPages;
var
Resultat: TResultatPagine;
Curseur: string;
begin
Curseur := '';
repeat
Resultat := RecupererAvecCurseur(Curseur);
// Traiter les données
Memo1.Lines.Add('--- Page ---');
Memo1.Lines.Add(Resultat.Donnees);
Curseur := Resultat.ProchainCurseur;
until not Resultat.APlusDePages;
end;⚠ Nota : dans la section « GET avec paramètres de requête » plus haut, on a déjà défini un
RechercherUtilisateurs(const Nom: string; Age: Integer). Pour éviter une collision de signature dans la même unité, on nomme ici la fonctionRechercherParStatut.
function RechercherParStatut(const Statut: string): string;
var
URL: string;
begin
// Filtrer par critère
URL := Format('https://api.example.com/users?status=%s',
[TNetEncoding.URL.Encode(Statut)]);
Result := FaireRequeteGET(URL);
end;
// Utilisation
procedure TForm1.ButtonRechercherClick(Sender: TObject);
var
Resultat: string;
begin
Resultat := RechercherParStatut('active');
Memo1.Lines.Text := Resultat;
end;type
TFiltresUtilisateur = record
Statut: string;
AgeMin: Integer;
AgeMax: Integer;
Ville: string;
end;
function RechercherAvecFiltres(const Filtres: TFiltresUtilisateur): string;
var
URL: TURI;
begin
URL := TURI.Create('https://api.example.com/users');
if Filtres.Statut <> '' then
URL := URL.AddParameter('status', Filtres.Statut);
if Filtres.AgeMin > 0 then
URL := URL.AddParameter('age_min', IntToStr(Filtres.AgeMin));
if Filtres.AgeMax > 0 then
URL := URL.AddParameter('age_max', IntToStr(Filtres.AgeMax));
if Filtres.Ville <> '' then
URL := URL.AddParameter('city', Filtres.Ville);
Result := FaireRequeteGET(URL.ToString);
end;function RecupererTriePar(const Champ: string; Ordre: string = 'asc'): string;
var
URL: string;
begin
URL := Format('https://api.example.com/users?sort=%s&order=%s',
[Champ, Ordre]);
Result := FaireRequeteGET(URL);
end;
// Utilisation
procedure TrierParNom;
begin
// Tri croissant par nom
Memo1.Lines.Text := RecupererTriePar('name', 'asc');
end;
procedure TrierParAge;
begin
// Tri décroissant par âge
Memo1.Lines.Text := RecupererTriePar('age', 'desc');
end;type
TAPIException = class(Exception)
private
FStatusCode: Integer;
FErrorCode: string;
FDetails: string;
public
constructor Create(StatusCode: Integer; const ErrorCode, Message, Details: string);
property StatusCode: Integer read FStatusCode;
property ErrorCode: string read FErrorCode;
property Details: string read FDetails;
end;
constructor TAPIException.Create(StatusCode: Integer;
const ErrorCode, Message, Details: string);
begin
inherited Create(Message);
FStatusCode := StatusCode;
FErrorCode := ErrorCode;
FDetails := Details;
end;procedure TraiterErreurAPI(Response: IHTTPResponse);
var
JSONValue: TJSONValue;
JSONError: TJSONObject;
ErrorCode, Message, Details: string;
begin
if Response.StatusCode < 400 then
Exit;
// On tente de parser la réponse JSON. Si le parsing échoue (corps
// non JSON, champs manquants…), on lève une exception générique.
// ATTENTION : on ne place pas le « raise TAPIException » à l'intérieur
// d'un try..except qui rattrape Exception, sinon notre exception bien
// formée serait remplacée par la version générique du bloc except.
JSONValue := nil;
ErrorCode := '';
Message := '';
Details := '';
try
JSONValue := TJSONObject.ParseJSONValue(Response.ContentAsString);
if JSONValue is TJSONObject then
begin
JSONError := TJSONObject(JSONValue);
ErrorCode := ObtenirValeurOuDefaut(JSONError, 'error_code', '');
Message := ObtenirValeurOuDefaut(JSONError, 'message', '');
Details := ObtenirValeurOuDefaut(JSONError, 'details', '');
end;
finally
JSONValue.Free;
end;
if Message = '' then
Message := 'Erreur API';
if Details = '' then
Details := Response.ContentAsString;
raise TAPIException.Create(Response.StatusCode, ErrorCode, Message, Details);
end;function AppelerAPISafe(const URL: string): string;
var
HttpClient: THTTPClient;
Response: IHTTPResponse;
Tentatives: Integer;
Delai: Integer;
begin
Tentatives := 0;
Delai := 1000;
while Tentatives < 3 do
begin
HttpClient := THTTPClient.Create;
try
try
Response := HttpClient.Get(URL);
case Response.StatusCode of
200..299:
begin
Result := Response.ContentAsString;
Exit;
end;
429: // Too Many Requests
begin
Inc(Tentatives);
Sleep(Delai);
Delai := Delai * 2; // Backoff exponentiel
Continue;
end;
500..599: // Erreur serveur
begin
Inc(Tentatives);
Sleep(Delai);
Continue;
end;
else
TraiterErreurAPI(Response);
end;
except
on E: Exception do
begin
if Tentatives >= 2 then
raise
else
begin
Inc(Tentatives);
Sleep(Delai);
end;
end;
end;
finally
HttpClient.Free;
end;
end;
raise Exception.Create('Échec après 3 tentatives');
end;type
TRateLimitInfo = record
Limite: Integer;
Restant: Integer;
Reset: TDateTime;
end;
function ExtraireRateLimitInfo(Response: IHTTPResponse): TRateLimitInfo;
var
LimiteStr, RestantStr, ResetStr: string;
ResetTimestamp: Int64;
begin
// Lire les en-têtes de rate limit
LimiteStr := Response.HeaderValue['X-RateLimit-Limit'];
RestantStr := Response.HeaderValue['X-RateLimit-Remaining'];
ResetStr := Response.HeaderValue['X-RateLimit-Reset'];
if LimiteStr <> '' then
Result.Limite := StrToIntDef(LimiteStr, 0);
if RestantStr <> '' then
Result.Restant := StrToIntDef(RestantStr, 0);
if ResetStr <> '' then
begin
ResetTimestamp := StrToInt64Def(ResetStr, 0);
Result.Reset := UnixToDateTime(ResetTimestamp);
end;
end;
procedure AfficherRateLimitInfo(const Info: TRateLimitInfo);
begin
ShowMessage(Format(
'Limite: %d requêtes' + sLineBreak +
'Restantes: %d' + sLineBreak +
'Reset: %s',
[Info.Limite, Info.Restant, FormatDateTime('hh:nn:ss', Info.Reset)]
));
end;type
TRateLimitManager = class
private
FDerniereRequete: TDateTime;
FDelaiMinimum: Integer; // millisecondes
FRestant: Integer;
FReset: TDateTime;
public
constructor Create(DelaiMinimumMS: Integer);
procedure AttendreAvantRequete;
procedure MettreAJourLimites(const Info: TRateLimitInfo);
function PeutFaireRequete: Boolean;
end;
constructor TRateLimitManager.Create(DelaiMinimumMS: Integer);
begin
FDelaiMinimum := DelaiMinimumMS;
FDerniereRequete := 0;
FRestant := -1;
end;
procedure TRateLimitManager.AttendreAvantRequete;
var
Ecoule, AAttendre: Integer;
begin
// Attendre si nécessaire entre les requêtes
if FDerniereRequete > 0 then
begin
Ecoule := MilliSecondsBetween(Now, FDerniereRequete);
if Ecoule < FDelaiMinimum then
begin
AAttendre := FDelaiMinimum - Ecoule;
Sleep(AAttendre);
end;
end;
// Attendre jusqu'au reset si limite atteinte
if (FRestant = 0) and (FReset > Now) then
begin
AAttendre := MilliSecondsBetween(FReset, Now);
if AAttendre > 0 then
begin
ShowMessage(Format('Limite atteinte. Attente de %d secondes...',
[AAttendre div 1000]));
Sleep(AAttendre);
end;
end;
FDerniereRequete := Now;
end;
function TRateLimitManager.PeutFaireRequete: Boolean;
begin
Result := (FRestant < 0) or (FRestant > 0) or (Now >= FReset);
end;
procedure TRateLimitManager.MettreAJourLimites(const Info: TRateLimitInfo);
begin
FRestant := Info.Restant;
FReset := Info.Reset;
end;Les WebHooks sont des notifications HTTP que l'API envoie à votre serveur.
uses
IdHTTPServer, IdContext, IdCustomHTTPServer;
type
TWebHookServer = class
private
FServer: TIdHTTPServer;
procedure TraiterWebHook(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
public
constructor Create(Port: Integer);
destructor Destroy; override;
procedure Demarrer;
procedure Arreter;
end;
constructor TWebHookServer.Create(Port: Integer);
begin
FServer := TIdHTTPServer.Create(nil);
FServer.DefaultPort := Port;
FServer.OnCommandGet := TraiterWebHook;
end;
destructor TWebHookServer.Destroy;
begin
FServer.Free;
inherited;
end;
procedure TWebHookServer.TraiterWebHook(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
var
JSONValue: TJSONValue;
JSONData: TJSONObject;
EventType, Data, Payload: string;
Reader: TStreamReader;
begin
// ParseJSONValue n'accepte pas directement un TStream :
// on lit le contenu du flux POST dans une chaîne au préalable.
ARequestInfo.PostStream.Position := 0;
Reader := TStreamReader.Create(ARequestInfo.PostStream, TEncoding.UTF8);
try
Payload := Reader.ReadToEnd;
finally
Reader.Free;
end;
// Garde-fou : si le payload n'est pas un objet JSON valide, on répond
// 400 plutôt que de laisser une exception remonter (ce qui ferait
// crasher le thread du serveur HTTP).
JSONValue := TJSONObject.ParseJSONValue(Payload);
if not (JSONValue is TJSONObject) then
begin
JSONValue.Free;
AResponseInfo.ResponseNo := 400;
AResponseInfo.ContentText := '{"error":"Invalid JSON payload"}';
Exit;
end;
JSONData := TJSONObject(JSONValue);
try
EventType := JSONData.GetValue<string>('event');
Data := JSONData.GetValue<string>('data');
// Traiter selon le type d'événement
if EventType = 'user.created' then
TraiterNouvelUtilisateur(Data)
else if EventType = 'payment.success' then
TraiterPaiementReussi(Data)
else if EventType = 'order.shipped' then
TraiterCommandeExpediee(Data);
// Répondre avec succès
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentText := '{"status":"ok"}';
finally
JSONData.Free;
end;
end;Les WebHooks signés garantissent l'authenticité :
uses
System.Hash, System.NetEncoding;
function ComparaisonConstante(const A, B: string): Boolean;
var
I, Diff: Integer;
begin
// Comparaison à temps constant : on parcourt TOUJOURS la chaîne entière,
// peu importe les différences trouvées. Cela empêche un attaquant de
// deviner la signature caractère par caractère en mesurant le temps
// de réponse (timing attack classique sur SameText / '=').
if Length(A) <> Length(B) then
Exit(False);
Diff := 0;
for I := 1 to Length(A) do
Diff := Diff or (Ord(A[I]) xor Ord(B[I]));
Result := Diff = 0;
end;
function VerifierSignatureWebHook(const Payload, Signature, Secret: string): Boolean;
var
HashCalcule: string;
begin
// Calculer HMAC-SHA256 (retourne directement la chaîne hexadécimale)
HashCalcule := THashSHA2.GetHMAC(Payload, Secret, SHA256);
// ⚠ Important pour la sécurité : on N'utilise PAS SameText / `=` qui
// sortent à la première différence trouvée. Avec une comparaison
// « courte-circuit », un attaquant peut mesurer le temps de réponse
// et reconstruire la signature attendue caractère par caractère.
Result := ComparaisonConstante(LowerCase(HashCalcule), LowerCase(Signature));
end;
procedure TraiterWebHookSecurise(ARequestInfo: TIdHTTPRequestInfo;
AResponseInfo: TIdHTTPResponseInfo);
var
Signature, Payload, Secret: string;
begin
Signature := ARequestInfo.RawHeaders.Values['X-Signature'];
// Lire le contenu du flux POST
ARequestInfo.PostStream.Position := 0;
with TStreamReader.Create(ARequestInfo.PostStream, TEncoding.UTF8) do
try
Payload := ReadToEnd;
finally
Free;
end;
Secret := 'votre_secret_webhook';
if not VerifierSignatureWebHook(Payload, Signature, Secret) then
begin
AResponseInfo.ResponseNo := 401;
AResponseInfo.ContentText := '{"error":"Invalid signature"}';
Exit;
end;
// Traiter le webhook...
AResponseInfo.ResponseNo := 200;
end;type
TAPIClient = class
private
FBaseURL: string;
FAPIKey: string;
FHttpClient: THTTPClient;
FRateLimitManager: TRateLimitManager;
function CreerHeaders: TNetHeaders;
procedure TraiterReponse(Response: IHTTPResponse);
protected
function Get(const Endpoint: string): string;
function Post(const Endpoint: string; const Data: TJSONObject): string;
function Put(const Endpoint: string; const Data: TJSONObject): string;
function Delete(const Endpoint: string): Boolean;
public
constructor Create(const BaseURL, APIKey: string);
destructor Destroy; override;
end;
constructor TAPIClient.Create(const BaseURL, APIKey: string);
begin
FBaseURL := BaseURL;
FAPIKey := APIKey;
FHttpClient := THTTPClient.Create;
FRateLimitManager := TRateLimitManager.Create(1000);
end;
destructor TAPIClient.Destroy;
begin
FRateLimitManager.Free;
FHttpClient.Free;
inherited;
end;
function TAPIClient.CreerHeaders: TNetHeaders;
begin
SetLength(Result, 2);
Result[0] := TNetHeader.Create('Authorization', 'Bearer ' + FAPIKey);
Result[1] := TNetHeader.Create('Content-Type', 'application/json');
end;
function TAPIClient.Get(const Endpoint: string): string;
var
Response: IHTTPResponse;
URL: string;
begin
FRateLimitManager.AttendreAvantRequete;
URL := FBaseURL + Endpoint;
Response := FHttpClient.Get(URL, nil, CreerHeaders);
TraiterReponse(Response);
Result := Response.ContentAsString;
end;
function TAPIClient.Post(const Endpoint: string; const Data: TJSONObject): string;
var
Response: IHTTPResponse;
PostData: TStringStream;
URL: string;
begin
FRateLimitManager.AttendreAvantRequete;
URL := FBaseURL + Endpoint;
PostData := TStringStream.Create(Data.ToString, TEncoding.UTF8);
try
Response := FHttpClient.Post(URL, PostData, nil, CreerHeaders);
TraiterReponse(Response);
Result := Response.ContentAsString;
finally
PostData.Free;
end;
end;
procedure TAPIClient.TraiterReponse(Response: IHTTPResponse);
var
RateLimitInfo: TRateLimitInfo;
begin
// Mettre à jour le rate limiting
RateLimitInfo := ExtraireRateLimitInfo(Response);
FRateLimitManager.MettreAJourLimites(RateLimitInfo);
// Gérer les erreurs
if Response.StatusCode >= 400 then
TraiterErreurAPI(Response);
end;
// Client spécifique héritant de TAPIClient
type
TMonAPIClient = class(TAPIClient)
public
constructor Create(const APIKey: string);
function ObtenirUtilisateurs: TJSONArray;
function CreerUtilisateur(const Nom, Email: string): TJSONObject;
end;
constructor TMonAPIClient.Create(const APIKey: string);
begin
inherited Create('https://api.example.com/v1', APIKey);
end;
function TMonAPIClient.ObtenirUtilisateurs: TJSONArray;
var
JSON: string;
begin
JSON := Get('/users');
Result := TJSONObject.ParseJSONValue(JSON) as TJSONArray;
end;
function TMonAPIClient.CreerUtilisateur(const Nom, Email: string): TJSONObject;
var
Data: TJSONObject;
JSON: string;
begin
Data := TJSONObject.Create;
try
Data.AddPair('name', Nom);
Data.AddPair('email', Email);
JSON := Post('/users', Data);
Result := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
finally
Data.Free;
end;
end;procedure LogRequeteAPI(const Methode, URL, Headers, Body, Response: string;
StatusCode: Integer);
var
Log: TStringList;
Timestamp: string;
begin
Timestamp := FormatDateTime('yyyy-mm-dd hh:nn:ss', Now);
Log := TStringList.Create;
try
if FileExists('api_debug.log') then
Log.LoadFromFile('api_debug.log');
Log.Add('=== ' + Timestamp + ' ===');
Log.Add('Method: ' + Methode);
Log.Add('URL: ' + URL);
Log.Add('Headers: ' + Headers);
if Body <> '' then
Log.Add('Body: ' + Body);
Log.Add('Status: ' + IntToStr(StatusCode));
Log.Add('Response: ' + Response);
Log.Add('');
Log.SaveToFile('api_debug.log');
finally
Log.Free;
end;
end;type
TAPIClientDebug = class(TAPIClient)
private
FDebugMode: Boolean;
public
property DebugMode: Boolean read FDebugMode write FDebugMode;
end;
// Dans les méthodes, ajouter :
if FDebugMode then
LogRequeteAPI('GET', URL, HeadersToString, '', Response.ContentAsString,
Response.StatusCode);Les API REST sont le standard actuel pour l'intégration de services web.
Points clés :
- Méthodes HTTP : GET (lire), POST (créer), PUT/PATCH (modifier), DELETE (supprimer)
- Codes de statut : 2xx succès, 4xx erreur client, 5xx erreur serveur
- Authentification : API Key, Bearer Token, Basic Auth selon l'API
- JSON : Format de données standard, facile à parser
- En-têtes : Content-Type, Authorization, Accept sont essentiels
- Pagination : Gérer les grandes listes (offset, page, curseur)
- Rate Limiting : Respecter les limites pour éviter le blocage
- Gestion d'erreurs : Toujours gérer les erreurs et réessayer si nécessaire
- WebHooks : Notifications push du serveur vers votre application
- Wrapper : Créer une classe réutilisable pour chaque API
Consultez toujours la documentation officielle de l'API que vous utilisez : chaque API a ses spécificités (authentification, format, limites).