Distribuzioni Distribuite
Fornisci un documento da diversi nodi
Quando diversi nodi forniscono gli stessi documenti, il nodo che rende un documento non è necessariamente quello che risponde alle richieste di pagina del browser. Questa versione suddivide quel compito in due metà esplicite — un lato di scrittura che pubblica gli artefatti di un documento nello storage condiviso, e un lato di lettura che li serve da qualsiasi nodo.
Se la tua applicazione chiama attualmente UseDoconutWebFarm(...), questa pagina è la tua sostituzione. Né UseDoconutWebFarm né WebFarmOptions esistono in questa versione.
La tua topologia non cambia. Punta la radice condivisa allo stesso percorso UNC che usi oggi e ogni nodo continuerà a funzionare come ora. Ciò che cambia è che la pubblicazione è un'operazione che chiami, invece di un effetto collaterale del rendering.
Cosa ha sostituito il middleware web-farm
In precedenza un unico middleware faceva tutto: serviva immagini pre‑renderizzate da una cartella condivisa e eseguiva il polling mentre un altro nodo le stava ancora scrivendo. L'attesa era un budget cieco — "blocca fino a tre minuti sperando che la pagina appaia".
Ora lo stesso compito è costituito da due chiamate che puoi vedere e ragionare:
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 pageLa configurazione condivisa di cui entrambe le metà hanno bisogno
Il publisher scrive con un FileShareConfig e il gestore di lettura serve con uno. Se RootPath o UsePerTokenSubfolders differiscono tra loro, l'altro nodo restituisce 404. Costruisci la configurazione in un unico posto e richiamala da entrambe le parti — l'applicazione di riferimento mantiene un unico helper SharedStore.Config(...) per questo motivo:
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 si trova in Doconut.Clouds, insieme a AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig e FTPConfig. Nella libreria precedente questi erano in Doconut.Configs.Cloud; i nomi delle classi non sono cambiati.
| Proprietà | Scopo |
|---|---|
Location | CloudLocation.FileShare per una condivisione UNC/local |
RootPath | La cartella condivisa a cui ogni nodo può accedere |
UsePerTokenSubfolders | Mantieni gli artefatti di ogni documento nella propria cartella {token} |
ReadReadyPollMs | Quanto tempo il lato di lettura attende una pagina ancora in scrittura; 0 disabilita il polling |
Lato di scrittura — pubblica il documento
Inietta DistributedDocumentPublisher e pubblica invece di aprire una sessione locale. Una chiamata rende e scrive l'intero set di artefatti — pagine, informazioni del documento, indice di ricerca e metadati:
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 contiene Token, ArtifactPath, TotalPages e Format. Il token viene inviato al browser esattamente come fa un normale token del visualizzatore.
Questa singola chiamata sostituisce la coppia precedente — aprire il documento in una sessione locale e poi scaricare le immagini sulla condivisione con ExportToPng(sharedPath). ExportToPng e ExportToCloud non hanno un equivalente qui.
Lato di lettura — servire dalla condivisione
Montare il gestore FileShare sullo stesso ramo che il client chiama già:
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 è generico in questa versione: il provider è l'argomento di tipo anziché un valore Location selezionato all'interno del middleware. I gestori sono denominati <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler e CDNCloudHandler.
Il gestore CDN è sola lettura — serve ciò che è già sul CDN; non effettua upload.
Priorità della prima pagina e HTTP 202
FirstPagePriority = true rende la pagina 1 servibile immediatamente. Le pagine successive rispondono con HTTP 202 Accepted finché non sono scritte, invece di bloccare la richiesta.
Ciò sposta l'attesa sul client, dove è limitata e visibile. Abilita il retry sul widget:
const objViewer = $('#div_ctlDoc').docViewer({
BasePath: '/',
ResPath: 'doconut-res/images',
retryOn409: true // retries 202 (and legacy 409) readiness responses
});Un client che non effettua il retry mostra tasselli rotti per le pagine che sono ancora in fase di rendering. Il budget di retry e i suoi valori predefiniti (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) sono documentati in ConfigurazioneViewer.
Questa coppia — FirstPagePriority più il polling lato lettura — è ciò che sostituisce le precedenti impostazioni PageWaitTimeSeconds e StartWaitFromPage. Nessuna delle due esiste più.
Opzionale: pubblicare in background
Per impostazione predefinita PublishAsync esegue il rendering prima di restituire. Per restituire non appena il token è generato e consentire a un worker ospitato di renderizzare nella stessa cartella, registra la coda opt‑in:
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();Quindi accoda invece di pubblicare inline e imposta Async = true nelle opzioni di pubblicazione. La coda è limitata: quando è saturata, EnqueueAsync lancia DistributedPublishQueueFullException. Rispondi con un 503 e un'intestazione Retry-After in modo che i client ritardino anziché bloccarsi:
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.");
}Senza questa registrazione nulla viene registrato e la pubblicazione rimane sincrona — il percorso predefinito è identico byte per byte.
Quando la pubblicazione in background è attiva, assegna al lato lettura un valore non zero per ReadReadyPollMs in modo che attenda brevemente una pagina che il worker non ha ancora scritto.
Sicurezza tra nodi
I token legati alla sessione presumono che il nodo di servizio abbia visto la richiesta iniziale. In un cluster ciò non avviene, quindi il controllo per sessione rifiuterebbe richieste legittime. Ecco perché l'applicazione di riferimento distribuita imposta UnsafeMode = true.
UnsafeMode = true non è il modello di sicurezza — è la rimozione di uno che non è più applicabile. Sostituiscilo con ticket di accesso firmati, che ogni nodo può verificare in modo indipendente:
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });Non utilizzare mai UnsafeMode = true in un'installazione a nodo singolo, e non considerarlo mai una scorciatoia per un controllo di sessione fallito su un nodo — lì la correzione corretta è AddSession() / UseSession(). Vedi Sessioni e Sicurezza.
Chiusura di un documento pubblicato
CloseDocument(token) on the injected Viewer disposes a local session. To remove a document's published artifacts from the share, the publisher exposes:
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)Pass the same FileShareConfig the document was published with.
Mapping impostazione per impostazione
| Precedente | Ora |
|---|---|
UseDoconutWebFarm(new WebFarmOptions { … }) | PublishAsync(...) sul lato scrittura e UseDoconutCloud<FileShareCloudHandler>(...) sul lato lettura |
WebFarmOptions.Path | FileShareConfig.RootPath |
PageWaitTimeSeconds / StartWaitFromPage | DistributedDocumentPublishOptions.FirstPagePriority e FileShareConfig.ReadReadyPollMs |
viewer.OpenDocument(...) then viewer.ExportToPng(share) | una chiamata PublishAsync(...) |
viewer.ExportToCloud(cloudConfig) | PublishAsync(...) con la configurazione del provider |
UseDoconutCloud(new CloudOptions { Location = … }) | UseDoconutCloud<THandler>(options => …, pathPrefix) |
DocOptions.IsWebfarm / WebfarmPath | Ancora presente, scritto IsWebFarm / WebFarmPath — non necessario nel percorso di pubblicazione |
Doconut.Configs.Cloud | Doconut.Clouds |
Lista di controllo
- Crea il
FileShareConfigin un helper e richiamalo da entrambe le parti. - Imposta
RootPathsullo share che usi già. - Pubblica con
PublishAsync(...); elimina tutte le chiamate aExportToPng/ExportToCloud. - Servi con
UseDoconutCloud<FileShareCloudHandler>(...)sul ramo che il client chiama. - Attiva
FirstPagePriorityeretryOn409insieme, oppure disattivali entrambi. - Sostituisci il controllo per sessione con ticket di accesso firmati — non distribuire
UnsafeMode = trueda solo. - Verifica aprendo un documento su un nodo e richiedendo una pagina tardiva da un altro.
Questa pagina è stata utile?