💡 Plně funkční příklad je k dispozici na GitHubu:
nodejs-docker-signing-with-fonts
Úvod
Rozlišení fontů je částí podepisování kontejneru, která rozhoduje, zda vaše Node služba vytváří dokumenty nebo výjimky. GroupDocs.Signature nenahrazuje chybějící rodinu: pokud obrázek nemá požadovaný název, volání vyvolá výjimku a nic nevyprodukuje. Vymazání fontu také není řešení, protože knihovna pak požaduje svůj výchozí font a selže stejným způsobem.
Existují tři způsoby, jak určit, kterou rodinu předat, a jen jeden z nich přežije v kontejneru. Tento článek je porovnává, poté popisuje provisioning a chování vazby, které formují kód kolem nich, protože Node.js přes Java má na této platformě více obojího než jakákoli jiná platforma, na kterou je tato knihovna distribuována.
Proč je to důležitější v Node.js
Balíček je most: node-java načítá JVM v procesu. Proto obraz Node pro podepisování potřebuje JDK, toolchain node-gyp k sestavení mostu a LD_LIBRARY_PATH ukazující na libjvm.so, a to vše ještě před tím, než jsou fonty relevantní. node:18-bookworm pak přidává 6 souborů fontů DejaVu pro AWT – stačí pro latinku, nic pro CJK.
Tato kombinace způsobuje selhání, která vypadají jako chyby aplikace. Chybějící cesta k JVM, chybějící font a nesoulad v maršálování se všechny projeví jako Error running instance method, protože tak node-java hlásí jakoukoli výjimku vyhozenou na straně Javy.
Požadavky
Node 18 – most se sestavuje proti NAN, který se nedaří zkompilovat proti V8 v Node 20 nebo 22 ('AccessorSignature' is not a member of 'v8'). JDK 8 až 17: na JDK 25 vrstva obrazu selže s Cannot open an image. The image size can not be 0!.
Instalace
npm install @groupdocs/groupdocs.signature
V obrazu je potřeba, aby byl nainstalován build-essential a python3, plus openjdk-17-jdk-headless a cesta k načítači:
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ENV PATH="${JAVA_HOME}/bin:${PATH}"
# node-java dlopens libjvm.so at run time; it is not on the default loader path.
ENV LD_LIBRARY_PATH="${JAVA_HOME}/lib/server:${LD_LIBRARY_PATH}"
Metoda 1 – Pevně zakódovaný název rodiny
Verze, kterou všichni píší jako první: vyberete Arial, nasadíte ji a pokračujete. Na vývojovém stroji funguje a při prvním spuštění kontejneru selže, protože Debian obrazy neinstalují Arial – instalují Liberation Sans, který je metricky kompatibilní, ale pod jiným názvem rodiny.
Neexistuje žádný kód, který by zde stálo za ukázku, a to je podstata. Celý obsah metody je řetězcový literál, který je pravdivý jen v jednom prostředí.
Metoda 2 – Detekce fontů ze souborového systému
Přirozené řešení: prohledat adresáře s fonty, zjistit, co je k dispozici, a vybrat něco. Polovina je skutečně užitečná – inventář vám řekne, zda obraz má 0 fontů nebo 6:
const roots = [
'/usr/share/fonts',
'/usr/local/share/fonts',
path.join(home, '.fonts'),
path.join(home, '.local', 'share', 'fonts'),
'/System/Library/Fonts',
'/Library/Fonts',
];
Druhá polovina nefunguje. Fontové soubory zřídka obsahují řetězec rodiny, který volající musí předat: Debianův fonts-noto-cjk instaluje NotoSansCJK-Regular.ttc, jehož rodina je Noto Sans CJK JP. Odvození rodiny z názvu souboru vám dá NotoSansCJK-Regular, což se nepřeloží na nic. Detekce názvu souboru zároveň nepostihne fonty, které jsou přítomny, a sebejistě hlásí rodiny, které selžou.
Uchovávejte inventář jako diagnostiku. Nepoužívejte jej k výběru. Počet odpovídá na otázku, zda byl obraz vůbec provisionován, což je jiná a stejně užitečná otázka.
Metoda 3 – Dotázat se knihovny
Zkuste jednorázový podpis pro každou kandidátní rodinu a zachovejte první, která nevyvolá výjimku. Stojí to jeden zápis PDF na kandidáta a je to jediná metoda, jejíž odpověď je autoritativní, protože jde o stejné volání, které provede skutečný podpis.
for (const candidate of candidates) {
if (tryFamily(sourcePath, candidate) === null) {
return candidate;
}
}
return null;
V Node je potřeba jeden doplněk. node-java převádí každou Java výjimku na Error running instance method, takže skutečná zpráva musí být získána z zabaleného stack trace:
const stack = err.stack || '';
const match = stack.match(/com\.groupdocs\.signature\.exception\.[^\n]*/);
return match ? match[0].trim() : (err.message || String(err));
Bez těchto dvou řádků produkují kontejner bez fontů a poškozená cesta k JVM identické logy. Strávil jsem více času, než bych chtěl přiznat, porovnáváním dvou kontejnerů, které tiskly stejnou chybu z naprosto odlišných důvodů, než jsem přidal regulární výraz.
Náklady na sondování
Námitka proti sondování je, že zapisuje soubory, a skutečně zapisuje: jeden malý PDF na kandidáta, který je okamžitě smazán. Seznam latinky ve vzorku má čtyři položky a seznam CJK osm, takže při studeném startu se zapíše nejvýše dvanáct jednopage dokumentů do dočasného adresáře, než je služba připravena.
Jedná se o náklad při startu, ne o náklad na jednotlivý požadavek, a poskytuje řádek logu pojmenovávající obě vyřešené rodiny. Ve srovnání s kontejnerem, který startuje čistě a pak selže při prvním zákaznickém dokumentu s chybou mostu, dvanáct dočasných souborů není obtížná výměna.
Porovnání metod: Kdy použít kterou
| Metoda | Nejlepší pro | Klíčové výhody | Omezení |
|---|---|---|---|
| Pevně zakódovaný název | jediné kontrolované prostředí | triviální, žádné náklady na start | selže v jakémkoli obrazu, který tuto konkrétní rodinu nemá |
| Detekce názvu souboru | diagnostika obsahu obrazu | rychlé, žádné volání podpisu | názvy souborů nejsou názvy rodin, takže odvozené volby selžou |
| Sondování knihovny | jakýkoli kontejnerizovaný nebo přenosný scénář | autoritativní, funguje na laptopu i v obrazu | jeden zápis PDF na kandidáta, proto provést při startu a cacheovat |
Dvě zvláštnosti vazby, které stojí za to znát
Jakmile se rodina vyřeší, samotné volání podpisu má specifický tvar pro Node. Java API přijímá seznam možností, ale JavaScriptové pole se nepromaršáluje na java.util.List, takže předání jednoho pole vyvolá Could not find method "sign(java.lang.String, [Ljava.lang.Object;)". Řešením je řetězit přetížený overload s jednou možností a provést mezikrok přes dočasný soubor:
new signatureLib.Signature(sourcePath)
.sign(firstOutput, buildTextOptions(LATIN_TEXT, latinFamily, 50));
if (stageTwo) {
new signatureLib.Signature(firstOutput)
.sign(outputPath, buildTextOptions(CJK_TEXT, cjkFamily, 120));
}
Druhá zvláštnost je čtení zpět. TextVerifyOptions neprojde touto vazbou: verify vyvolá stejnou obecnou chybu mostu, takže vzorek vrací sentinel a vypisuje unavailable místo toho, aby předstíral, že podpis selhal. npm balíček má verzi 24.12.0, publikovanou v prosinci 2024, a obsahuje engine 23.6.1, zatímco .NET je na 26.6 a Java na 26.5. Podepisování není ovlivněno; chybí jen cesta ověření.
Mám i nadále používat Node.js vazbu v produkci?
Pro podepisování pouze latinky, ano: podepisuje správně a chybějící font vyvolá výjimku místo tichého degradování, takže režim selhání je hlasitý. Pro smíšené skripty zvažte chybějící čtení zpět, protože pak nic v procesu nemůže potvrdit, že CJK glyfy jsou vloženy místo toho, aby se vykreslovaly jako krabice. Malý ověřovač na .NET nebo Java ve stejném pipeline tuto mezeru zaplní.
Nejlepší postupy a tipy
- Provisionujte v pořadí: JDK a toolchain, cesta k načítači, fonty, pak aplikaci. Každá vrstva selže jinak a jejich míchání zpomaluje diagnostiku.
- Vyřešte rodiny jednou při startu a zalogujte je vedle počtu fontů.
- Upněte Node 18 a JDK mezi 8 a 17 a považujte je za pevnou infrastrukturu, ne za rutinní upgrade.
- Uchovávejte Dockerfile bez fontů v repozitáři, aby selhání zůstalo jen jeden build daleko.
Závěr
Tři způsoby, jak vybrat font, jeden, který přežije nasazení. Sondujte knihovnu, cacheujte odpověď a nechte inventář sloužit jako diagnostiku, ne jako rozhodnutí. Pak pracujte s vazbou tak, jak je: podepisujte jednu možnost najednou, vytáhněte Java výjimku ze stack trace a upřímně hlaste chybějící ověření místo jeho skrývání. Vzorek repozitáře sestavuje oba obrazy, takže každý zde uvedený nárok lze ověřit dvěma příkazy.