Verteilte Bereitstellungen
Ein Dokument von mehreren Knoten aus bereitstellen
Wenn mehrere Knoten dieselben Dokumente bereitstellen, ist der Knoten, der ein Dokument rendert, nicht unbedingt der Knoten, der die Seitenanfragen des Browsers beantwortet. Diese Version teilt diese Aufgabe in zwei explizite Hälften – eine Schreibseite, die die Artefakte eines Dokuments in den gemeinsamen Speicher veröffentlicht, und eine Leseseite, die sie von jedem Knoten aus bereitstellt.
Falls Ihre Anwendung derzeit UseDoconutWebFarm(...) aufruft, ist diese Seite Ihr Ersatz. Weder UseDoconutWebFarm noch WebFarmOptions existieren in dieser Version.
Ihre Topologie ändert sich nicht. Zeigen Sie das gemeinsame Root-Verzeichnis auf denselben UNC-Pfad, den Sie heute verwenden, und jeder Knoten funktioniert weiterhin wie bisher. Was sich ändert, ist, dass das Veröffentlichen ein Aufruf ist, den Sie tätigen, anstatt einer Nebeneffekt des Renderns.
Was die Web-Farm-Middleware ersetzt hat
Früher erledigte eine Middleware alles: Sie diente vorgerenderte Bilder aus einem gemeinsamen Ordner aus und pollte, während ein anderer Knoten sie noch schrieb. Das Warten war ein blinder Puffer – „bis zu drei Minuten blockieren, in der Hoffnung, dass die Seite erscheint“.
Jetzt ist dieselbe Aufgabe in zwei Aufrufe aufgeteilt, die Sie sehen und nachvollziehen können:
node A OpenDocument request
-> PublishAsync(...) writes pages, document info,
search index and metadata
into RootPath/{token}/
-> returns the token
node B ?token=...&page=7
-> UseDoconutCloud<FileShareCloudHandler>
-> reads RootPath/{token}/ and serves the pageDie gemeinsam genutzte Konfiguration, die beide Hälften benötigen
Der Publisher schreibt mit einer FileShareConfig und der Lese‑Handler dient mit einer. Wenn RootPath oder UsePerTokenSubfolders zwischen ihnen unterschiedlich sind, liefert der andere Knoten 404. Erstellen Sie die Konfiguration an genau einer Stelle und rufen Sie sie von beiden Seiten aus auf – die Referenzanwendung hält aus diesem Grund einen einzigen SharedStore.Config(...)‑Hilfsmechanismus bereit:
internal static class SharedStore
{
public static FileShareConfig Config(string rootPath, bool asyncPublish) => new()
{
Location = CloudLocation.FileShare,
RootPath = rootPath, // e.g. \\fileserver\doconut
UsePerTokenSubfolders = true,
ReadReadyPollMs = asyncPublish ? 250 : 0,
};
}FileShareConfig befindet sich in Doconut.Clouds, zusammen mit AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig und FTPConfig. In der vorherigen Bibliothek waren diese in Doconut.Configs.Cloud; die Klassennamen haben sich nicht geändert.
| Eigenschaft | Zweck |
|---|---|
Location | CloudLocation.FileShare für einen UNC-/lokalen Share |
RootPath | Der gemeinsam genutzte Ordner, den jeder Knoten erreichen kann |
UsePerTokenSubfolders | Bewahrt die Artefakte jedes Dokuments in einem eigenen {token}‑Ordner |
ReadReadyPollMs | Wie lange die Leseseite auf eine Seite wartet, die noch geschrieben wird; 0 deaktiviert das Polling |
Schreibseite — Dokument veröffentlichen
Injizieren Sie DistributedDocumentPublisher und veröffentlichen Sie, anstatt eine lokale Sitzung zu öffnen. Ein Aufruf rendert und schreibt das komplette Artefakt‑Set – Seiten, Dokumentinformationen, Suchindex und Metadaten:
var shareConfig = SharedStore.Config(rootPath, asyncPublish: false);
var documentOptions = new DocOptions { TimeOut = 60 };
var config = new PdfConfig { AllowSearch = true };
var publishOptions = new DistributedDocumentPublishOptions
{
CleanUpStagingDirectory = true,
FirstPagePriority = true,
};
DistributedDocumentPublishResult result = await _publisher.PublishAsync(
pathToFile, shareConfig, config, documentOptions, publishOptions, ct);
return Content(result.Token);DistributedDocumentPublishResult enthält Token, ArtifactPath, TotalPages und Format. Das Token wird an den Browser gesendet, genau wie ein normales Viewer‑Token.
Dieser einzelne Aufruf ersetzt das vorherige Paar – das Öffnen des Dokuments in einer lokalen Sitzung und anschließend das Ablegen von Bildern im Share mit ExportToPng(sharedPath). ExportToPng und ExportToCloud haben hier keine Entsprechung.
Lese‑Seite — vom Share aus bedienen
Mounten Sie den FileShare‑Handler auf dem gleichen Branch, den der Client bereits aufruft:
app.UseDoconutResources();
app.MapWhen(
ctx => ctx.Request.Path.Value?.EndsWith("DocImage.axd", StringComparison.OrdinalIgnoreCase) == true,
branch => branch.UseDoconutCloud<FileShareCloudHandler>(options =>
{
options.Location = CloudLocation.FileShare;
options.CloudUploadConfig = SharedStore.Config(rootPath, asyncPublish: false);
}, "/"));UseDoconutCloud ist in diesem Release generisch: Der Provider ist das Typargument statt eines Location‑Werts, der innerhalb der Middleware ausgewertet wird. Handler heißen <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler und CDNCloudHandler.
Der CDN‑Handler ist schreibgeschützt — er liefert nur das, was bereits im CDN liegt; er lädt nichts hoch.
Priorität der ersten Seite und HTTP 202
FirstPagePriority = true macht Seite 1 sofort servierbar. Spätere Seiten geben HTTP 202 Accepted zurück, bis sie geschrieben wurden, anstatt die Anfrage zu blockieren.
Damit wird das Warten zum Client verlagert, wo es begrenzt und sichtbar ist. Aktivieren Sie den Retry‑Mechanismus im Widget:
const objViewer = $('#div_ctlDoc').docViewer({
BasePath: '/',
ResPath: 'doconut-res/images',
retryOn409: true // wiederholt 202 (und Legacy‑409) Readiness‑Antworten
});Ein Client, der nicht erneut versucht, zeigt kaputte Kacheln für Seiten, die noch gerendert werden. Das Retry‑Budget und seine Vorgaben (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) sind in der Viewer‑Konfiguration dokumentiert (ViewerConfig).
Dieses Paar — FirstPagePriority plus das Lese‑Seiten‑Polling — ersetzt die früheren Einstellungen PageWaitTimeSeconds und StartWaitFromPage. Keine dieser Optionen existiert mehr.
Optional: im Hintergrund veröffentlichen
Standardmäßig rendert PublishAsync, bevor es zurückkehrt. Um sofort zurückzugeben, sobald das Token erzeugt ist, und einen gehosteten Worker in denselben Ordner rendern zu lassen, registrieren Sie die Opt‑In‑Warteschlange:
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();Enqueue‑statt Inline‑Publish und setzen Sie Async = true in den Publish‑Optionen. Die Warteschlange ist begrenzt: Wenn sie gesättigt ist, wirft EnqueueAsync eine DistributedPublishQueueFullException. Antworten Sie mit einem 503 und einem Retry-After‑Header, damit Clients zurücktreten statt zu blockieren:
try
{
var token = await _publishQueue.EnqueueAsync(
pathToFile, shareConfig, config, documentOptions, publishOptions, ct);
return Content(token);
}
catch (DistributedPublishQueueFullException)
{
Response.Headers.RetryAfter = "5";
Response.StatusCode = 503;
return Content("The publish queue is full. Please retry.");
}Ohne diese Registrierung ist nichts registriert und das Veröffentlichen bleibt synchron — der Standardpfad ist byte‑für‑byte identisch.
Wenn das Hintergrund‑Publishing aktiviert ist, geben Sie der Lese‑Seite ein von Null verschiedenes ReadReadyPollMs, damit sie kurz wartet, bis ein Worker eine Seite geschrieben hat.
Sicherheit über Knoten hinweg
Sitzungsgebundene Tokens gehen davon aus, dass der bedienende Knoten die Eröffnungsanfrage gesehen hat. In einem Farm‑Setup ist das nicht der Fall, sodass die Sitzungsprüfung legitime Anfragen ablehnen würde. Deshalb setzt die verteilte Referenz‑Anwendung UnsafeMode = true.
UnsafeMode = true ist nicht das Sicherheitsmodell — es ist das Entfernen eines Modells, das nicht mehr gilt. Ersetzen Sie es durch signierte Zugriffstickets, die jeder Knoten eigenständig verifizieren kann:
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });Tragen Sie UnsafeMode = true niemals in eine Single‑Node‑Bereitstellung zurück und behandeln Sie es nicht als Abkürzung für eine fehlgeschlagene Sitzungsprüfung auf einem Knoten — dort ist die korrekte Lösung AddSession() / UseSession(). Siehe Sitzungen & Sicherheit (Sessions & Security).
Schließen eines veröffentlichten Dokuments
CloseDocument(token) on dem injizierten Viewer beendet eine lokale Sitzung. Um die veröffentlichten Artefakte eines Dokuments aus dem Share zu entfernen, stellt der Publisher bereit:
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)Verwenden Sie dieselbe FileShareConfig, mit der das Dokument veröffentlicht wurde.
Einstellung-für-Einstellung Zuordnung
| Vorherige | Jetzt |
|---|---|
UseDoconutWebFarm(new WebFarmOptions { … }) | PublishAsync(...) auf der Schreibseite plus UseDoconutCloud<FileShareCloudHandler>(...) auf der Leseseite |
WebFarmOptions.Path | FileShareConfig.RootPath |
PageWaitTimeSeconds / StartWaitFromPage | DistributedDocumentPublishOptions.FirstPagePriority plus FileShareConfig.ReadReadyPollMs |
viewer.OpenDocument(...) then viewer.ExportToPng(share) | ein PublishAsync(...) Aufruf |
viewer.ExportToCloud(cloudConfig) | PublishAsync(...) mit der Konfiguration des Anbieters |
UseDoconutCloud(new CloudOptions { Location = … }) | UseDoconutCloud<THandler>(options => …, pathPrefix) |
DocOptions.IsWebfarm / WebfarmPath | Noch vorhanden, geschrieben IsWebFarm / WebFarmPath — nicht im Veröffentlichungs‑Pfad benötigt |
Doconut.Configs.Cloud | Doconut.Clouds |
Checkliste
- Erstellen Sie die
FileShareConfigin einem Hilfsprogramm und rufen Sie sie von beiden Seiten auf. - Setzen Sie
RootPathauf das Share, das Sie bereits verwenden. - Veröffentlichen Sie mit
PublishAsync(...); entfernen Sie alle Aufrufe vonExportToPng/ExportToCloud. - Bedienen Sie mit
UseDoconutCloud<FileShareCloudHandler>(...)auf dem Zweig, den der Client aufruft. - Aktivieren Sie
FirstPagePriorityundretryOn409gemeinsam, oder lassen Sie beide deaktiviert. - Ersetzen Sie die Sitzungs‑Prüfung durch signierte Zugriffstickets — versenden Sie nicht nur
UnsafeMode = true. - Verifizieren Sie, indem Sie ein Dokument auf einem Knoten öffnen und von einem anderen Knoten eine späte Seite anfordern.
War diese Seite hilfreich?