recherche
Maisoninterface Webjs tutorielCréer un serveur proxy inverse évolutif comme Nginx avec Node.js et TypeScript

L'inspiration

Dans l'architecture de microservices actuelle, les proxys inverses jouent un rôle crucial dans la gestion et le routage des demandes entrantes vers divers services backend.

Building a Scalable Reverse Proxy Server like Nginx with Node.js and TypeScript

Un proxy inverse se place devant les serveurs web d'une application et intercepte les requêtes provenant des machines clientes. Cela présente de nombreux avantages tels que l'équilibrage de charge, les adresses IP des serveurs d'origine cachées conduisant à une meilleure sécurité, la mise en cache, la limitation de débit, etc.

Dans une architecture distribuée et microservice, un point d'entrée unique est nécessaire. Les serveurs proxy inverse comme Nginx sont utiles dans de tels scénarios. Si plusieurs instances de notre serveur sont en cours d'exécution, gérer et garantir un routage efficace des requêtes devient délicat. Un proxy inverse comme Nginx est une solution parfaite dans ce cas. Nous pouvons pointer notre domaine vers l'adresse IP du serveur Nginx et le Nginx acheminera la requête entrante selon la configuration vers l'une des instances tout en s'occupant de la charge gérée par chacune.

Comment Nginx fait-il si bien ?

Je recommanderai de lire cet article de Nginx qui explique en détail comment Nginx est capable de prendre en charge une grande échelle de requêtes avec une fiabilité et une rapidité exceptionnelles : Architecture Nginx

En bref, Nginx a un processus maître et un tas de processus de travail. Il dispose également de processus d'assistance tels que Cache Loader et Cache Manager. Le maître et le processus de travail font tout le gros travail.

  • Processus principal : gère la configuration et génère des processus enfants.
  • Cache Loader/Manager : gérez le chargement et l'élagage du cache avec un minimum de ressources.
  • Processus de travail : gérez les connexions, les E/S de disque et la communication en amont, en s'exécutant de manière non bloquante et indépendante.

Les processus de travail gèrent plusieurs connexions de manière non bloquante, réduisant ainsi les changements de contexte. Ils sont monothread, s'exécutent indépendamment et utilisent la mémoire partagée pour les ressources partagées telles que le cache et les données de session. Cette architecture aide Nginx à réduire le nombre de changements de contexte et à augmenter la vitesse plus rapidement qu'une architecture multi-processus bloquante.

En nous inspirant de cela, nous utiliserons le même concept de processus maître et de travail et implémenterons notre propre serveur proxy inverse basé sur les événements qui sera capable de gérer des milliers de connexions par processus de travail.

Architecture du projet

Notre mise en œuvre du proxy inverse suit ces principes de conception clés :

  1. Basé sur la configuration : tout le comportement du proxy est défini dans un fichier de configuration YAML, ce qui facilite la modification des règles de routage.
  2. Sécurité des types : les schémas TypeScript et Zod garantissent la validité de la configuration et la sécurité des types d'exécution.
  3. Évolutivité : le module de cluster Node.js permet d'utiliser plusieurs cœurs de processeur pour de meilleures performances.
  4. Modularité : séparation claire des préoccupations avec des modules distincts pour la configuration, la logique du serveur et la validation du schéma.

Structure du projet

├── config.yaml           # Server configuration
├── src/
│   ├── config-schema.ts  # Configuration validation schemas
│   ├── config.ts         # Configuration parsing logic
│   ├── index.ts         # Application entry point
│   ├── server-schema.ts # Server message schemas
│   └── server.ts        # Core server implementation
└── tsconfig.json        # TypeScript configuration

Composants clés

  1. config.yaml : définit la configuration du serveur, y compris le port, les processus de travail, les serveurs en amont, les en-têtes et les règles de routage.
  2. config-schema.ts : définit les schémas de validation à l'aide de la bibliothèque Zod pour garantir que la structure de configuration est correcte.
  3. server-schema.ts : Spécifie les formats de message échangés entre les processus maître et de travail.
  4. config.ts : fournit des fonctions d'analyse et de validation du fichier de configuration YAML.
  5. server.ts : implémente la logique du serveur proxy inverse, y compris la configuration du cluster, la gestion HTTP et le transfert des demandes.
  6. index.ts : sert de point d'entrée, analyse les options de ligne de commande et lance le serveur.

Gestion des configurations

Le système de configuration utilise YAML. Voici comment cela fonctionne :

server:
    listen: 8080          # Port the server listens on.
    workers: 2            # Number of worker processes to handle requests.
    upstreams:            # Define upstream servers (backend targets).
        - id: jsonplaceholder
          url: jsonplaceholder.typicode.com
        - id: dummy
          url: dummyjson.com
    headers:              # Custom headers added to proxied requests.
        - key: x-forward-for
          value: $ip      # Adds the client IP to the forwarded request.
        - key: Authorization
          value: Bearer xyz  # Adds an authorization token to requests.
    rules:                # Define routing rules for incoming requests.
        - path: /test
          upstreams:
              - dummy     # Routes requests to "/test" to the "dummy" upstream.
        - path: /
          upstreams:
              - jsonplaceholder  # Routes all other requests to "jsonplaceholder".

Les demandes entrantes sont évaluées par rapport aux règles. En fonction du chemin, le proxy inverse détermine à quel serveur en amont transmettre la demande.

Validation de la configuration (config-schema.ts)

Nous utilisons Zod pour définir des schémas stricts pour la validation de la configuration :

import { z } from "zod";

const upstreamSchema = z.object({
    id: z.string(),
    url: z.string(),
});

const headerSchema = z.object({
    key: z.string(),
    value: z.string(),
});

const ruleSchema = z.object({
    path: z.string(),
    upstreams: z.array(z.string()),
});

const serverSchema = z.object({
    listen: z.number(),
    workers: z.number().optional(),
    upstreams: z.array(upstreamSchema),
    headers: z.array(headerSchema).optional(),
    rules: z.array(ruleSchema),
});

export const rootConfigSchema = z.object({
    server: serverSchema,
});

export type ConfigSchemaType = z.infer<typeof rootconfigschema>;
</typeof>

Analyse et validation des configurations (config.ts)

Le module config.ts fournit des fonctions utilitaires pour analyser et valider le fichier de configuration.

import fs from "node:fs/promises";
import { parse } from "yaml";
import { rootConfigSchema } from "./config-schema";

export async function parseYAMLConfig(filepath: string) {
    const configFileContent = await fs.readFile(filepath, "utf8");
    const configParsed = parse(configFileContent);
    return JSON.stringify(configParsed);
}

export async function validateConfig(config: string) {
    const validatedConfig = await rootConfigSchema.parseAsync(
        JSON.parse(config)
    );
    return validatedConfig;
}

Logique du serveur proxy inverse (server.ts)

Le serveur utilise le module de cluster Node.js pour l'évolutivité et le module http pour gérer les requêtes. Le processus maître distribue les requêtes aux processus de travail, qui les transmettent aux serveurs en amont. Explorons en détail le fichier server.ts, qui contient la logique de base de notre serveur proxy inverse. Nous décomposerons chaque composant et comprendrons comment ils fonctionnent ensemble pour créer un serveur proxy évolutif.

L'implémentation du serveur suit une architecture maître-travailleur utilisant le module cluster de Node.js. Cette conception nous permet de :

  • Utiliser plusieurs cœurs de processeur
  • Gérer les demandes simultanément
  • Maintenir une haute disponibilité
  • Isoler le traitement des demandes
  1. Processus maître :

    • Crée des processus de travail
    • Distribue les demandes entrantes entre les travailleurs
    • Gère le pool de travailleurs
    • Gère les plantages et les redémarrages des travailleurs
  2. Processus de travail :

    • Gérer les requêtes HTTP individuelles
    • Faire correspondre les requêtes aux règles de routage
    • Transférer les requêtes vers les serveurs en amont
    • Traiter les réponses et les renvoyer aux clients

Configuration du processus principal

├── config.yaml           # Server configuration
├── src/
│   ├── config-schema.ts  # Configuration validation schemas
│   ├── config.ts         # Configuration parsing logic
│   ├── index.ts         # Application entry point
│   ├── server-schema.ts # Server message schemas
│   └── server.ts        # Core server implementation
└── tsconfig.json        # TypeScript configuration

Le processus maître crée un pool de travailleurs et transmet la configuration à chaque travailleur via des variables d'environnement. Cela garantit que tous les travailleurs ont accès à la même configuration.

Demander la distribution

server:
    listen: 8080          # Port the server listens on.
    workers: 2            # Number of worker processes to handle requests.
    upstreams:            # Define upstream servers (backend targets).
        - id: jsonplaceholder
          url: jsonplaceholder.typicode.com
        - id: dummy
          url: dummyjson.com
    headers:              # Custom headers added to proxied requests.
        - key: x-forward-for
          value: $ip      # Adds the client IP to the forwarded request.
        - key: Authorization
          value: Bearer xyz  # Adds an authorization token to requests.
    rules:                # Define routing rules for incoming requests.
        - path: /test
          upstreams:
              - dummy     # Routes requests to "/test" to the "dummy" upstream.
        - path: /
          upstreams:
              - jsonplaceholder  # Routes all other requests to "jsonplaceholder".

Le processus maître utilise une stratégie de distribution aléatoire simple pour attribuer les demandes aux travailleurs. Bien qu'elle ne soit pas aussi sophistiquée que les algorithmes à tour de rôle ou les algorithmes de moindre connexion, cette approche fournit une répartition de charge décente pour la plupart des cas d'utilisation. La logique de distribution des requêtes :

  • Sélectionne au hasard un travailleur dans le pool
  • Crée une charge de travail équilibrée entre les travailleurs
  • Gère les cas extrêmes où les travailleurs peuvent être indisponibles

Logique de demande de processus de travail

Chaque travailleur écoute les messages, compare les demandes aux règles de routage et les transmet au serveur en amont approprié.

import { z } from "zod";

const upstreamSchema = z.object({
    id: z.string(),
    url: z.string(),
});

const headerSchema = z.object({
    key: z.string(),
    value: z.string(),
});

const ruleSchema = z.object({
    path: z.string(),
    upstreams: z.array(z.string()),
});

const serverSchema = z.object({
    listen: z.number(),
    workers: z.number().optional(),
    upstreams: z.array(upstreamSchema),
    headers: z.array(headerSchema).optional(),
    rules: z.array(ruleSchema),
});

export const rootConfigSchema = z.object({
    server: serverSchema,
});

export type ConfigSchemaType = z.infer<typeof rootconfigschema>;
</typeof>

Le processus maître communique avec les travailleurs en construisant une charge utile de message standardisée, comprenant toutes les informations de demande nécessaires, à l'aide de Node.js IPC (Inter-Process Communication) et en validant la structure du message à l'aide des schémas Zod.

Les travailleurs gèrent le traitement réel des demandes et le proxy. Chaque travailleur :

  • Charge sa configuration à partir des variables d'environnement
  • Valide la configuration à l'aide des schémas Zod
  • Maintient sa propre copie de la configuration

Les travailleurs sélectionnent les serveurs en amont par :

  • Trouver l'ID en amont approprié à partir de la règle
  • Localisation de la configuration du serveur en amont
  • La validation du serveur amont existe

Le mécanisme de transfert des requêtes :

  • Crée une nouvelle requête HTTP vers le serveur en amont
  • Diffuse les données de réponse
  • Agrége le corps de la réponse
  • Renvoie la réponse au processus maître

Exécution du serveur

Pour exécuter le serveur, suivez ces étapes :

  1. Construire le projet :
import fs from "node:fs/promises";
import { parse } from "yaml";
import { rootConfigSchema } from "./config-schema";

export async function parseYAMLConfig(filepath: string) {
    const configFileContent = await fs.readFile(filepath, "utf8");
    const configParsed = parse(configFileContent);
    return JSON.stringify(configParsed);
}

export async function validateConfig(config: string) {
    const validatedConfig = await rootConfigSchema.parseAsync(
        JSON.parse(config)
    );
    return validatedConfig;
}
  1. Démarrez le serveur :
if (cluster.isPrimary) {
    console.log("Master Process is up ?");
    for (let i = 0; i  {
        const index = Math.floor(Math.random() * WORKER_POOL.length);
        const worker = WORKER_POOL.at(index);

        if (!worker) throw new Error("Worker not found.");

        const payload: WorkerMessageSchemaType = {
            requestType: "HTTP",
            headers: req.headers,
            body: null,
            url: ${req.url},
        };
        worker.send(JSON.stringify(payload));

        worker.once("message", async (workerReply: string) => {
            const reply = await workerMessageReplySchema.parseAsync(
                JSON.parse(workerReply)
            );

            if (reply.errorCode) {
                res.writeHead(parseInt(reply.errorCode));
                res.end(reply.error);
            } else {
                res.writeHead(200);
                res.end(reply.data);
            }
        });
    });

    server.listen(port, () => {
        console.log(Reverse Proxy listening on port: ${port});
    });
}
  1. Mode développement :
const server = http.createServer(function (req, res) {
    const index = Math.floor(Math.random() * WORKER_POOL.length);
    const worker = WORKER_POOL.at(index);

    const payload: WorkerMessageSchemaType = {
        requestType: "HTTP",
        headers: req.headers,
        body: null,
        url: ${req.url},
    };
    worker.send(JSON.stringify(payload));
});

Building a Scalable Reverse Proxy Server like Nginx with Node.js and TypeScript

Dans la capture d'écran ci-dessus, nous pouvons voir qu'il y a 1 nœud maître et 2 processus de travail en cours d'exécution. Notre serveur proxy inverse écoute sur le port 8080.
Dans le fichier config.yaml, nous décrivons deux serveurs en amont à savoir : jsonplaceholder et dummy. Si nous voulons que toutes les requêtes arrivant sur notre serveur soient acheminées vers jsonplaceholder, nous mettons la règle comme suit :

├── config.yaml           # Server configuration
├── src/
│   ├── config-schema.ts  # Configuration validation schemas
│   ├── config.ts         # Configuration parsing logic
│   ├── index.ts         # Application entry point
│   ├── server-schema.ts # Server message schemas
│   └── server.ts        # Core server implementation
└── tsconfig.json        # TypeScript configuration

De même, si nous voulons que notre requête au point de terminaison /test soit acheminée vers notre serveur factice en amont, nous mettons la règle comme suit :

server:
    listen: 8080          # Port the server listens on.
    workers: 2            # Number of worker processes to handle requests.
    upstreams:            # Define upstream servers (backend targets).
        - id: jsonplaceholder
          url: jsonplaceholder.typicode.com
        - id: dummy
          url: dummyjson.com
    headers:              # Custom headers added to proxied requests.
        - key: x-forward-for
          value: $ip      # Adds the client IP to the forwarded request.
        - key: Authorization
          value: Bearer xyz  # Adds an authorization token to requests.
    rules:                # Define routing rules for incoming requests.
        - path: /test
          upstreams:
              - dummy     # Routes requests to "/test" to the "dummy" upstream.
        - path: /
          upstreams:
              - jsonplaceholder  # Routes all other requests to "jsonplaceholder".

Testons ça !

Building a Scalable Reverse Proxy Server like Nginx with Node.js and TypeScript

Wow, c'est cool ! Nous naviguons vers localhost:8080 mais en réponse, nous pouvons voir que nous avons reçu la page d'accueil de jsonplaceholder.typicode.com. L'utilisateur final ne sait même pas que nous voyons une réponse provenant d'un serveur distinct. C'est pourquoi les serveurs Reverse Proxy sont importants. Si nous avons plusieurs serveurs exécutant le même code et que nous ne souhaitons pas exposer tous leurs ports aux utilisateurs finaux, utilisez un proxy inverse comme couche d'abstraction. Les utilisateurs accéderont au serveur proxy inverse, un serveur très robuste et rapide, et il déterminera vers quel serveur acheminer la demande.

Appuyons maintenant sur localhost:8080/todos et voyons ce qui se passe.

Building a Scalable Reverse Proxy Server like Nginx with Node.js and TypeScript

Notre demande a de nouveau été transmise par proxy inverse au serveur jsonplaceholder et a reçu une réponse JSON à partir de l'URL résolue : jsonplaceholder.typicode.com/todos.

Flux de communication

Visualisons le flux complet des demandes :

Le client envoie la demande → Processus principal
Processus principal → Travailleur sélectionné
Travailleur → Serveur en amont
Serveur en amont → Travailleur
Travailleur → Processus principal
Processus principal → Client

Considérations relatives aux performances

L'architecture multi-processus offre plusieurs avantages en termes de performances :

  1. Utilisation du processeur : les processus de travail peuvent s'exécuter sur différents cœurs de processeur, en utilisant les ressources matérielles disponibles.
  2. Isolement des processus : un accident chez un travailleur n'affecte pas les autres, ce qui améliore la fiabilité.
  3. Répartition de la charge : la répartition aléatoire des demandes permet d'éviter qu'un seul travailleur ne soit submergé.

Améliorations futures

Bien que fonctionnelle, la mise en œuvre actuelle pourrait être améliorée avec :

  1. Meilleur équilibrage de charge : implémentez des algorithmes plus sophistiqués comme le round-robin ou les moindres connexions.
  2. Vérifications de santé : ajoutez des vérifications de santé périodiques pour les serveurs en amont.
  3. Mise en cache : implémentez la mise en cache des réponses pour réduire la charge du serveur en amont.
  4. Métriques : ajoutez des métriques de style Prometheus pour la surveillance.
  5. Support WebSocket : étendez le proxy pour gérer les connexions WebSocket.
  6. Prise en charge HTTPS : ajoutez des fonctionnalités de terminaison SSL/TLS.

Conclusion

Construire un serveur proxy inverse à partir de zéro peut sembler intimidant au début, mais comme nous l'avons exploré, c'est une expérience enrichissante. En combinant les clusters Node.js, TypeScript et la gestion de configuration basée sur YAML, nous avons créé un système évolutif et efficace inspiré de Nginx.

Il est encore possible d'améliorer cette implémentation : un meilleur équilibrage de charge, une meilleure mise en cache ou une meilleure prise en charge de WebSocket ne sont que quelques idées à explorer. Mais la conception actuelle constitue une base solide pour expérimenter et évoluer davantage. Si vous avez suivi, vous êtes désormais équipé pour approfondir vos connaissances sur les proxys inverses ou même commencer à créer des solutions personnalisées adaptées à vos besoins.

Si vous souhaitez vous connecter ou voir plus de mon travail, consultez mon GitHub, LinkedIn.
Le référentiel de ce projet peut être trouvé ici.

J'aimerais entendre vos réflexions, commentaires ou idées d'amélioration. Merci d'avoir lu et bon codage ! ?

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
JavaScript en action: Exemples et projets du monde réelJavaScript en action: Exemples et projets du monde réelApr 19, 2025 am 12:13 AM

L'application de JavaScript dans le monde réel comprend un développement frontal et back-end. 1) Afficher les applications frontales en créant une application de liste TODO, impliquant les opérations DOM et le traitement des événements. 2) Construisez RestulAPI via Node.js et Express pour démontrer les applications back-end.

Javascript et le web: fonctionnalité de base et cas d'utilisationJavascript et le web: fonctionnalité de base et cas d'utilisationApr 18, 2025 am 12:19 AM

Les principales utilisations de JavaScript dans le développement Web incluent l'interaction client, la vérification du formulaire et la communication asynchrone. 1) Mise à jour du contenu dynamique et interaction utilisateur via les opérations DOM; 2) La vérification du client est effectuée avant que l'utilisateur ne soumette les données pour améliorer l'expérience utilisateur; 3) La communication de rafraîchissement avec le serveur est réalisée via la technologie AJAX.

Comprendre le moteur JavaScript: détails de l'implémentationComprendre le moteur JavaScript: détails de l'implémentationApr 17, 2025 am 12:05 AM

Comprendre le fonctionnement du moteur JavaScript en interne est important pour les développeurs car il aide à écrire du code plus efficace et à comprendre les goulots d'étranglement des performances et les stratégies d'optimisation. 1) Le flux de travail du moteur comprend trois étapes: analyse, compilation et exécution; 2) Pendant le processus d'exécution, le moteur effectuera une optimisation dynamique, comme le cache en ligne et les classes cachées; 3) Les meilleures pratiques comprennent l'évitement des variables globales, l'optimisation des boucles, l'utilisation de const et de locations et d'éviter une utilisation excessive des fermetures.

Python vs JavaScript: la courbe d'apprentissage et la facilité d'utilisationPython vs JavaScript: la courbe d'apprentissage et la facilité d'utilisationApr 16, 2025 am 12:12 AM

Python convient plus aux débutants, avec une courbe d'apprentissage en douceur et une syntaxe concise; JavaScript convient au développement frontal, avec une courbe d'apprentissage abrupte et une syntaxe flexible. 1. La syntaxe Python est intuitive et adaptée à la science des données et au développement back-end. 2. JavaScript est flexible et largement utilisé dans la programmation frontale et côté serveur.

Python vs JavaScript: communauté, bibliothèques et ressourcesPython vs JavaScript: communauté, bibliothèques et ressourcesApr 15, 2025 am 12:16 AM

Python et JavaScript ont leurs propres avantages et inconvénients en termes de communauté, de bibliothèques et de ressources. 1) La communauté Python est amicale et adaptée aux débutants, mais les ressources de développement frontal ne sont pas aussi riches que JavaScript. 2) Python est puissant dans les bibliothèques de science des données et d'apprentissage automatique, tandis que JavaScript est meilleur dans les bibliothèques et les cadres de développement frontaux. 3) Les deux ont des ressources d'apprentissage riches, mais Python convient pour commencer par des documents officiels, tandis que JavaScript est meilleur avec MDNWEBDOCS. Le choix doit être basé sur les besoins du projet et les intérêts personnels.

De C / C à JavaScript: comment tout cela fonctionneDe C / C à JavaScript: comment tout cela fonctionneApr 14, 2025 am 12:05 AM

Le passage de C / C à JavaScript nécessite de s'adapter à la frappe dynamique, à la collecte des ordures et à la programmation asynchrone. 1) C / C est un langage dactylographié statiquement qui nécessite une gestion manuelle de la mémoire, tandis que JavaScript est dynamiquement typé et que la collecte des déchets est automatiquement traitée. 2) C / C doit être compilé en code machine, tandis que JavaScript est une langue interprétée. 3) JavaScript introduit des concepts tels que les fermetures, les chaînes de prototypes et la promesse, ce qui améliore la flexibilité et les capacités de programmation asynchrones.

Moteurs JavaScript: comparaison des implémentationsMoteurs JavaScript: comparaison des implémentationsApr 13, 2025 am 12:05 AM

Différents moteurs JavaScript ont des effets différents lors de l'analyse et de l'exécution du code JavaScript, car les principes d'implémentation et les stratégies d'optimisation de chaque moteur diffèrent. 1. Analyse lexicale: convertir le code source en unité lexicale. 2. Analyse de la grammaire: générer un arbre de syntaxe abstrait. 3. Optimisation et compilation: générer du code machine via le compilateur JIT. 4. Exécuter: Exécutez le code machine. Le moteur V8 optimise grâce à une compilation instantanée et à une classe cachée, SpiderMonkey utilise un système d'inférence de type, résultant en différentes performances de performances sur le même code.

Au-delà du navigateur: Javascript dans le monde réelAu-delà du navigateur: Javascript dans le monde réelApr 12, 2025 am 12:06 AM

Les applications de JavaScript dans le monde réel incluent la programmation côté serveur, le développement des applications mobiles et le contrôle de l'Internet des objets: 1. La programmation côté serveur est réalisée via Node.js, adaptée au traitement de demande élevé simultané. 2. Le développement d'applications mobiles est effectué par le reactnatif et prend en charge le déploiement multiplateforme. 3. Utilisé pour le contrôle des périphériques IoT via la bibliothèque Johnny-Five, adapté à l'interaction matérielle.

See all articles

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

AI Hentai Generator

AI Hentai Generator

Générez AI Hentai gratuitement.

Outils chauds

Dreamweaver Mac

Dreamweaver Mac

Outils de développement Web visuel

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

mPDF

mPDF

mPDF est une bibliothèque PHP qui peut générer des fichiers PDF à partir de HTML encodé en UTF-8. L'auteur original, Ian Back, a écrit mPDF pour générer des fichiers PDF « à la volée » depuis son site Web et gérer différentes langues. Il est plus lent et produit des fichiers plus volumineux lors de l'utilisation de polices Unicode que les scripts originaux comme HTML2FPDF, mais prend en charge les styles CSS, etc. et présente de nombreuses améliorations. Prend en charge presque toutes les langues, y compris RTL (arabe et hébreu) ​​et CJK (chinois, japonais et coréen). Prend en charge les éléments imbriqués au niveau du bloc (tels que P, DIV),

Navigateur d'examen sécurisé

Navigateur d'examen sécurisé

Safe Exam Browser est un environnement de navigation sécurisé permettant de passer des examens en ligne en toute sécurité. Ce logiciel transforme n'importe quel ordinateur en poste de travail sécurisé. Il contrôle l'accès à n'importe quel utilitaire et empêche les étudiants d'utiliser des ressources non autorisées.

Adaptateur de serveur SAP NetWeaver pour Eclipse

Adaptateur de serveur SAP NetWeaver pour Eclipse

Intégrez Eclipse au serveur d'applications SAP NetWeaver.