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:

text
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 page

La 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:

csharp
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
LocationCloudLocation.FileShare per una condivisione UNC/local
RootPathLa cartella condivisa a cui ogni nodo può accedere
UsePerTokenSubfoldersMantieni gli artefatti di ogni documento nella propria cartella {token}
ReadReadyPollMsQuanto 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:

csharp
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à:

csharp
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:

javascript
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:

csharp
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:

csharp
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:

csharp
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:

text
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)

Pass the same FileShareConfig the document was published with.

Mapping impostazione per impostazione

PrecedenteOra
UseDoconutWebFarm(new WebFarmOptions { … })PublishAsync(...) sul lato scrittura e UseDoconutCloud<FileShareCloudHandler>(...) sul lato lettura
WebFarmOptions.PathFileShareConfig.RootPath
PageWaitTimeSeconds / StartWaitFromPageDistributedDocumentPublishOptions.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 / WebfarmPathAncora presente, scritto IsWebFarm / WebFarmPath — non necessario nel percorso di pubblicazione
Doconut.Configs.CloudDoconut.Clouds

Lista di controllo

  1. Crea il FileShareConfig in un helper e richiamalo da entrambe le parti.
  2. Imposta RootPath sullo share che usi già.
  3. Pubblica con PublishAsync(...); elimina tutte le chiamate a ExportToPng / ExportToCloud.
  4. Servi con UseDoconutCloud<FileShareCloudHandler>(...) sul ramo che il client chiama.
  5. Attiva FirstPagePriority e retryOn409 insieme, oppure disattivali entrambi.
  6. Sostituisci il controllo per sessione con ticket di accesso firmati — non distribuire UnsafeMode = true da solo.
  7. Verifica aprendo un documento su un nodo e richiedendo una pagina tardiva da un altro.

Questa pagina è stata utile?