


Erstellen eines skalierbaren Reverse-Proxy-Servers wie Nginx mit Node.js und TypeScript
Die Inspiration
In der heutigen Microservices-Architektur spielen Reverse-Proxys eine entscheidende Rolle bei der Verwaltung und Weiterleitung eingehender Anfragen an verschiedene Backend-Dienste.
Ein Reverse-Proxy sitzt vor den Webservern einer Anwendung und fängt die Anfragen ab, die von den Client-Rechnern kommen. Dies hat viele Vorteile wie Lastausgleich, versteckte IP-Adressen der Ursprungsserver, was zu besserer Sicherheit, Caching, Ratenbegrenzung usw. führt.
In einer verteilten Microservice-Architektur ist ein einziger Einstiegspunkt erforderlich. Reverse-Proxy-Server wie Nginx helfen in solchen Szenarien. Wenn mehrere Instanzen unseres Servers ausgeführt werden, wird die Verwaltung und Sicherstellung einer effizienten Anforderungsweiterleitung schwierig. Ein Reverse-Proxy wie Nginx ist in diesem Fall eine perfekte Lösung. Wir können unsere Domain auf die IP-Adresse des Nginx-Servers verweisen und Nginx leitet die eingehende Anfrage entsprechend der Konfiguration an eine der Instanzen weiter und kümmert sich dabei um die Last, die von jeder Instanz verarbeitet wird.
Warum macht Nginx das so gut?
Ich empfehle die Lektüre dieses Artikels von Nginx, der ausführlich erklärt, wie Nginx eine große Anzahl von Anfragen mit höchster Zuverlässigkeit und Geschwindigkeit unterstützen kann: Nginx-Architektur
Kurz gesagt, Nginx verfügt über einen Master-Prozess und eine Reihe von Worker-Prozessen. Es verfügt auch über Hilfsprozesse wie Cache Loader und Cache Manager. Der Master und der Worker-Prozess erledigen die ganze schwere Arbeit.
- Master-Prozess: Verwaltet die Konfiguration und erzeugt untergeordnete Prozesse.
- Cache Loader/Manager: Erledigt das Laden und Bereinigen des Caches mit minimalen Ressourcen.
- Arbeitsprozesse: Verwalten Sie Verbindungen, Festplatten-E/A und Upstream-Kommunikation und laufen Sie nicht blockierend und unabhängig.
Arbeitsprozesse verarbeiten mehrere Verbindungen nicht blockierend und reduzieren so Kontextwechsel. Sie sind Single-Threaded, laufen unabhängig und nutzen gemeinsam genutzten Speicher für gemeinsam genutzte Ressourcen wie Cache und Sitzungsdaten. Diese Architektur hilft Nginx, die Anzahl der Kontextwechsel zu reduzieren und die Geschwindigkeit schneller zu erhöhen als eine blockierende Multiprozessarchitektur.
Inspiriert davon werden wir das gleiche Konzept von Master- und Worker-Prozessen verwenden und unseren eigenen ereignisgesteuerten Reverse-Proxy-Server implementieren, der Tausende von Verbindungen pro Worker-Prozess verarbeiten kann.
Projektarchitektur
Unsere Reverse-Proxy-Implementierung folgt diesen wichtigen Designprinzipien:
- Konfigurationsgesteuert: Das gesamte Proxy-Verhalten ist in einer YAML-Konfigurationsdatei definiert, sodass Routing-Regeln einfach geändert werden können.
- Typsicherheit: TypeScript- und Zod-Schemas gewährleisten Konfigurationsgültigkeit und Laufzeittypsicherheit.
- Skalierbarkeit: Das Node.js-Clustermodul ermöglicht die Nutzung mehrerer CPU-Kerne für eine bessere Leistung.
- Modularität: Klare Trennung der Belange mit unterschiedlichen Modulen für Konfiguration, Serverlogik und Schemavalidierung.
Projektstruktur
├── 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
Schlüsselkomponenten
- config.yaml: Definiert die Konfiguration des Servers, einschließlich Port, Arbeitsprozesse, Upstream-Server, Header und Routing-Regeln.
- config-schema.ts: Definiert Validierungsschemata mithilfe der Zod-Bibliothek, um sicherzustellen, dass die Konfigurationsstruktur korrekt ist.
- server-schema.ts: Gibt Nachrichtenformate an, die zwischen den Master- und Worker-Prozessen ausgetauscht werden.
- config.ts: Bietet Funktionen zum Parsen und Validieren der YAML-Konfigurationsdatei.
- server.ts: Implementiert die Reverse-Proxy-Server-Logik, einschließlich Cluster-Setup, HTTP-Verarbeitung und Anforderungsweiterleitung.
- index.ts: Dient als Einstiegspunkt, analysiert Befehlszeilenoptionen und initiiert den Server.
Konfigurationsmanagement
Das Konfigurationssystem verwendet YAML. So funktioniert es:
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".
Eingehende Anfragen werden anhand der Regeln bewertet. Basierend auf dem Pfad bestimmt der Reverse-Proxy, an welchen Upstream-Server die Anfrage weitergeleitet werden soll.
Konfigurationsvalidierung (config-schema.ts)
Wir verwenden Zod, um strenge Schemata für die Konfigurationsvalidierung zu definieren:
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>
Parsen und Validieren von Konfigurationen (config.ts)
Das config.ts-Modul bietet Hilfsfunktionen zum Parsen und Validieren der Konfigurationsdatei.
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; }
Reverse-Proxy-Server-Logik (server.ts)
Der Server nutzt das Node.js-Clustermodul für Skalierbarkeit und das http-Modul für die Bearbeitung von Anfragen. Der Master-Prozess verteilt Anfragen an Worker-Prozesse, die diese an Upstream-Server weiterleiten. Lassen Sie uns die Datei server.ts im Detail untersuchen, die die Kernlogik unseres Reverse-Proxy-Servers enthält. Wir werden jede Komponente aufschlüsseln und verstehen, wie sie zusammenarbeitet, um einen skalierbaren Proxyserver zu erstellen.
Die Serverimplementierung folgt einer Master-Worker-Architektur unter Verwendung des Node.js-Cluster-Moduls. Dieses Design ermöglicht uns:
- Nutzen Sie mehrere CPU-Kerne
- Anfragen gleichzeitig bearbeiten
- Aufrechterhaltung einer hohen Verfügbarkeit
- Anfrageverarbeitung isolieren
-
Masterprozess:
- Erstellt Arbeitsprozesse
- Verteilt eingehende Anfragen an alle Mitarbeiter
- Verwaltet den Worker-Pool
- Behandelt Worker-Abstürze und Neustarts
-
Arbeiterprozesse:
- Einzelne HTTP-Anfragen bearbeiten
- Anfragen anhand von Routing-Regeln abgleichen
- Anfragen an Upstream-Server weiterleiten
- Verarbeiten Sie Antworten und senden Sie sie an Kunden zurück
Master-Prozess-Setup
├── 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
Der Masterprozess erstellt einen Pool von Workern und übergibt die Konfiguration über Umgebungsvariablen an jeden Worker. Dadurch wird sichergestellt, dass alle Mitarbeiter Zugriff auf dieselbe Konfiguration haben.
Verteilung anfordern
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".
Der Masterprozess verwendet eine einfache Zufallsverteilungsstrategie, um Anfragen den Arbeitern zuzuweisen. Dieser Ansatz ist zwar nicht so ausgefeilt wie Round-Robin- oder Least-Connections-Algorithmen, bietet aber für die meisten Anwendungsfälle eine angemessene Lastverteilung. Die Anforderungsverteilungslogik:
- Wählt zufällig einen Arbeiter aus dem Pool aus
- Sorgt für eine ausgewogene Arbeitsbelastung aller Mitarbeiter
- Behandelt Randfälle, bei denen Mitarbeiter möglicherweise nicht verfügbar sind
Worker-Prozessanforderungslogik
Jeder Worker lauscht auf Nachrichten, gleicht Anfragen mit Routing-Regeln ab und leitet sie an den entsprechenden Upstream-Server weiter.
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>
Der Masterprozess kommuniziert mit den Arbeitern, indem er mithilfe von Node.js IPC (Inter-Process Communication) eine standardisierte Nachrichtennutzlast einschließlich aller erforderlichen Anforderungsinformationen erstellt und die Nachrichtenstruktur mithilfe von Zod-Schemas validiert.
Mitarbeiter kümmern sich um die eigentliche Anforderungsverarbeitung und Weiterleitung. Jeder Arbeiter:
- Lädt seine Konfiguration aus Umgebungsvariablen
- Validiert die Konfiguration mithilfe von Zod-Schemas
- Behält eine eigene Kopie der Konfiguration bei
Mitarbeiter wählen Upstream-Server aus durch:
- Suchen der entsprechenden Upstream-ID aus der Regel
- Auffinden der Upstream-Serverkonfiguration
- Überprüfung, dass der Upstream-Server vorhanden ist
Der Mechanismus zur Anforderungsweiterleitung:
- Erstellt eine neue HTTP-Anfrage an den Upstream-Server
- Streamet die Antwortdaten
- Aggregiert den Antworttext
- Sendet die Antwort zurück an den Masterprozess
Ausführen des Servers
Um den Server auszuführen, befolgen Sie diese Schritte:
- Erstellen Sie das Projekt:
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; }
- Server starten:
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}); }); }
- Entwicklungsmodus:
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)); });
Im obigen Screenshot können wir sehen, dass 1 Master-Knoten und 2 Worker-Prozesse ausgeführt werden. Unser Reverse-Proxy-Server überwacht Port 8080.
In der Datei config.yaml beschreiben wir zwei Upstream-Server, nämlich: jsonplaceholder und dummy. Wenn wir möchten, dass alle an unseren Server eingehenden Anfragen an jsonplaceholder weitergeleitet werden, geben wir die Regel wie folgt ein:
├── 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
Wenn wir möchten, dass unsere Anfrage an den Endpunkt /test an unseren Dummy-Upstream-Server weitergeleitet wird, geben wir die Regel wie folgt ein:
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".
Lass uns das testen!
Wow, das ist cool! Wir navigieren zu „localhost:8080“, sehen aber als Antwort, dass wir die Homepage für „jsonplaceholder.typicode.com“ erhalten haben. Der Endbenutzer weiß nicht einmal, dass wir eine Antwort von einem separaten Server sehen. Deshalb sind Reverse-Proxy-Server wichtig. Wenn auf mehreren Servern derselbe Code ausgeführt wird und nicht alle Ports den Endbenutzern zugänglich gemacht werden sollen, verwenden Sie einen Reverse-Proxy als Abstraktionsschicht. Benutzer greifen auf den Reverse-Proxy-Server zu, einen sehr robusten und schnellen Server, und dieser bestimmt, an welchen Server die Anfrage weitergeleitet werden soll.
Lassen Sie uns jetzt „localhost:8080/todos“ aufrufen und sehen, was passiert.
Unsere Anfrage wurde erneut rückwärts an den JSONPlaceholder-Server weitergeleitet und erhielt eine JSON-Antwort von der aufgelösten URL: jsonplaceholder.typicode.com/todos.
Kommunikationsfluss
Lassen Sie uns den gesamten Anfrageablauf visualisieren:
Kunde sendet Anfrage → Masterprozess
Master-Prozess → Ausgewählter Arbeiter
Arbeiter → Upstream-Server
Upstream-Server → Worker
Arbeiter → Masterprozess
Masterprozess → Kunde
Leistungsüberlegungen
Die Multiprozessarchitektur bietet mehrere Leistungsvorteile:
- CPU-Auslastung: Arbeitsprozesse können auf verschiedenen CPU-Kernen ausgeführt werden und nutzen dabei verfügbare Hardwareressourcen.
- Prozessisolierung: Ein Absturz bei einem Mitarbeiter hat keine Auswirkungen auf andere, was die Zuverlässigkeit erhöht.
- Lastverteilung: Durch die zufällige Verteilung von Anfragen wird verhindert, dass ein einzelner Mitarbeiter überlastet wird.
Zukünftige Verbesserungen
Die aktuelle Implementierung ist zwar funktionsfähig, könnte jedoch um Folgendes erweitert werden:
- Bessere Lastverteilung: Implementieren Sie anspruchsvollere Algorithmen wie Round-Robin oder Least-Connections.
- Gesundheitsprüfungen: Fügen Sie regelmäßige Gesundheitsprüfungen für Upstream-Server hinzu.
- Caching: Implementieren Sie Antwort-Caching, um die Belastung des Upstream-Servers zu reduzieren.
- Metriken: Fügen Sie Metriken im Prometheus-Stil zur Überwachung hinzu.
- WebSocket-Unterstützung: Erweitern Sie den Proxy, um WebSocket-Verbindungen zu verarbeiten.
- HTTPS-Unterstützung: Fügen Sie SSL/TLS-Terminierungsfunktionen hinzu.
Zusammenfassung
Das Erstellen eines Reverse-Proxy-Servers von Grund auf mag auf den ersten Blick einschüchternd wirken, aber wie wir herausgefunden haben, ist es eine lohnende Erfahrung. Durch die Kombination von Node.js-Clustern, TypeScript und YAML-basiertem Konfigurationsmanagement haben wir ein skalierbares und effizientes System geschaffen, das von Nginx inspiriert ist.
Es gibt noch Raum für Verbesserungen dieser Implementierung – besserer Lastausgleich, Caching oder WebSocket-Unterstützung sind nur einige Ideen, die es zu erkunden gilt. Aber das aktuelle Design bildet eine solide Grundlage für Experimente und eine weitere Skalierung. Wenn Sie mitgemacht haben, sind Sie jetzt in der Lage, tiefer in Reverse-Proxys einzutauchen oder sogar mit der Entwicklung maßgeschneiderter Lösungen zu beginnen, die auf Ihre Bedürfnisse zugeschnitten sind.
Wenn Sie sich vernetzen oder mehr von meiner Arbeit sehen möchten, schauen Sie sich meinen GitHub und LinkedIn an.
Das Repository für dieses Projekt finden Sie hier.
Ich würde gerne Ihre Gedanken, Ihr Feedback oder Ihre Verbesserungsvorschläge hören. Vielen Dank fürs Lesen und viel Spaß beim Codieren! ?
Das obige ist der detaillierte Inhalt vonErstellen eines skalierbaren Reverse-Proxy-Servers wie Nginx mit Node.js und TypeScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

JavaScript ist das Herzstück moderner Websites, da es die Interaktivität und Dynamik von Webseiten verbessert. 1) Es ermöglicht die Änderung von Inhalten, ohne die Seite zu aktualisieren, 2) Webseiten durch DOMAPI zu manipulieren, 3) Komplexe interaktive Effekte wie Animation und Drag & Drop, 4) die Leistung und Best Practices optimieren, um die Benutzererfahrung zu verbessern.

C und JavaScript erreichen die Interoperabilität durch WebAssembly. 1) C -Code wird in das WebAssembly -Modul zusammengestellt und in die JavaScript -Umgebung eingeführt, um die Rechenleistung zu verbessern. 2) In der Spieleentwicklung kümmert sich C über Physik -Engines und Grafikwiedergabe, und JavaScript ist für die Spiellogik und die Benutzeroberfläche verantwortlich.

JavaScript wird in Websites, mobilen Anwendungen, Desktop-Anwendungen und serverseitigen Programmierungen häufig verwendet. 1) In der Website -Entwicklung betreibt JavaScript DOM zusammen mit HTML und CSS, um dynamische Effekte zu erzielen und Frameworks wie JQuery und React zu unterstützen. 2) Durch reaktnatives und ionisches JavaScript wird ein plattformübergreifendes mobile Anwendungen entwickelt. 3) Mit dem Elektronenframework können JavaScript Desktop -Anwendungen erstellen. 4) Node.js ermöglicht es JavaScript, auf der Serverseite auszuführen und unterstützt hohe gleichzeitige Anforderungen.

Python eignet sich besser für Datenwissenschaft und Automatisierung, während JavaScript besser für die Entwicklung von Front-End- und Vollstapel geeignet ist. 1. Python funktioniert in Datenwissenschaft und maschinellem Lernen gut und unter Verwendung von Bibliotheken wie Numpy und Pandas für die Datenverarbeitung und -modellierung. 2. Python ist prägnant und effizient in der Automatisierung und Skripten. 3. JavaScript ist in der Front-End-Entwicklung unverzichtbar und wird verwendet, um dynamische Webseiten und einseitige Anwendungen zu erstellen. 4. JavaScript spielt eine Rolle bei der Back-End-Entwicklung durch Node.js und unterstützt die Entwicklung der Vollstapel.

C und C spielen eine wichtige Rolle in der JavaScript -Engine, die hauptsächlich zur Implementierung von Dolmetschern und JIT -Compilern verwendet wird. 1) C wird verwendet, um JavaScript -Quellcode zu analysieren und einen abstrakten Syntaxbaum zu generieren. 2) C ist für die Generierung und Ausführung von Bytecode verantwortlich. 3) C implementiert den JIT-Compiler, optimiert und kompiliert Hot-Spot-Code zur Laufzeit und verbessert die Ausführungseffizienz von JavaScript erheblich.

Die Anwendung von JavaScript in der realen Welt umfasst Front-End- und Back-End-Entwicklung. 1) Zeigen Sie Front-End-Anwendungen an, indem Sie eine TODO-Listanwendung erstellen, die DOM-Operationen und Ereignisverarbeitung umfasst. 2) Erstellen Sie RESTFUFFUPI über Node.js und express, um Back-End-Anwendungen zu demonstrieren.

Zu den Hauptanwendungen von JavaScript in der Webentwicklung gehören die Interaktion der Clients, die Formüberprüfung und die asynchrone Kommunikation. 1) Dynamisches Inhaltsaktualisierung und Benutzerinteraktion durch DOM -Operationen; 2) Die Kundenüberprüfung erfolgt vor dem Einreichung von Daten, um die Benutzererfahrung zu verbessern. 3) Die Aktualisierung der Kommunikation mit dem Server wird durch AJAX -Technologie erreicht.

Es ist für Entwickler wichtig, zu verstehen, wie die JavaScript -Engine intern funktioniert, da sie effizientere Code schreibt und Leistungs Engpässe und Optimierungsstrategien verstehen kann. 1) Der Workflow der Engine umfasst drei Phasen: Parsen, Kompilieren und Ausführung; 2) Während des Ausführungsprozesses führt die Engine dynamische Optimierung durch, wie z. B. Inline -Cache und versteckte Klassen. 3) Zu Best Practices gehören die Vermeidung globaler Variablen, die Optimierung von Schleifen, die Verwendung von const und lass und die Vermeidung übermäßiger Verwendung von Schließungen.


Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

Video Face Swap
Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heißer Artikel

Heiße Werkzeuge

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

SublimeText3 Englische Version
Empfohlen: Win-Version, unterstützt Code-Eingabeaufforderungen!

SublimeText3 Linux neue Version
SublimeText3 Linux neueste Version

WebStorm-Mac-Version
Nützliche JavaScript-Entwicklungstools

mPDF
mPDF ist eine PHP-Bibliothek, die PDF-Dateien aus UTF-8-codiertem HTML generieren kann. Der ursprüngliche Autor, Ian Back, hat mPDF geschrieben, um PDF-Dateien „on the fly“ von seiner Website auszugeben und verschiedene Sprachen zu verarbeiten. Es ist langsamer und erzeugt bei der Verwendung von Unicode-Schriftarten größere Dateien als Originalskripte wie HTML2FPDF, unterstützt aber CSS-Stile usw. und verfügt über viele Verbesserungen. Unterstützt fast alle Sprachen, einschließlich RTL (Arabisch und Hebräisch) und CJK (Chinesisch, Japanisch und Koreanisch). Unterstützt verschachtelte Elemente auf Blockebene (wie P, DIV),