Déploiements distribués

Servir un document depuis plusieurs nœuds

Lorsque plusieurs nœuds servent les mêmes documents, le nœud qui rend un document n'est pas nécessairement celui qui répond aux requêtes de pages du navigateur. Cette version divise cette tâche en deux moitiés explicites — un côté écriture qui publie les artefacts d'un document dans le stockage partagé, et un côté lecture qui les sert depuis n'importe quel nœud.

Si votre application appelle actuellement UseDoconutWebFarm(...), cette page est votre remplacement. Ni UseDoconutWebFarm ni WebFarmOptions n'existent dans cette version.

Votre topologie ne change pas. Pointez la racine partagée vers le même chemin UNC que vous utilisez aujourd'hui et chaque nœud continue de fonctionner comme il le fait maintenant. Ce qui change, c'est que la publication est une opération que vous appelez, au lieu d'un effet secondaire du rendu.

Ce qui a remplacé le middleware web-farm

Auparavant, un seul middleware faisait tout : il servait des images pré‑rendues depuis un dossier partagé et interrogeait pendant qu'un autre nœud les écrivait encore. L'attente était un budget aveugle — « bloquer jusqu'à trois minutes en espérant que la page apparaisse ».

Maintenant, la même tâche se compose de deux appels que vous pouvez voir et raisonner :

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 configuration partagée dont les deux moitiés ont besoin

L'éditeur écrit avec un FileShareConfig et le gestionnaire de lecture le sert avec un. Si RootPath ou UsePerTokenSubfolders diffèrent entre eux, l'autre nœud renvoie 404. Construisez la configuration à un seul endroit et appelez‑la depuis les deux côtés — l'application de référence conserve un seul helper SharedStore.Config(...) pour cette raison :

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 se trouve dans Doconut.Clouds, avec AzureConfig, AmazonConfig, GoogleCloudConfig, DropBoxConfig, RedisConfig, CDNConfig et FTPConfig. Dans la bibliothèque précédente, ceux‑ci étaient dans Doconut.Configs.Cloud ; les noms de classe n'ont pas changé.

PropriétéObjectif
LocationCloudLocation.FileShare pour un partage UNC/local
RootPathLe dossier partagé accessible par chaque nœud
UsePerTokenSubfoldersConserver les artefacts de chaque document dans son propre dossier {token}
ReadReadyPollMsDurée pendant laquelle le côté lecture attend une page encore en cours d'écriture ; 0 désactive le sondage

Côté écriture — publier le document

Injectez DistributedDocumentPublisher et publiez au lieu d'ouvrir une session locale. Un seul appel rend et écrit l'ensemble complet des artefacts — pages, informations du document, index de recherche et métadonnées :

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 contient Token, ArtifactPath, TotalPages et Format. Le token est envoyé au navigateur exactement comme le fait un token de visionneur normal.

Cet appel unique remplace la paire précédente — ouvrir le document dans une session locale puis déverser les images sur le partage avec ExportToPng(sharedPath). ExportToPng et ExportToCloud n'ont aucun équivalent ici.

Côté lecture — servir depuis le partage

Montez le gestionnaire FileShare sur la même branche que le client appelle déjà :

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 est générique dans cette version : le fournisseur est l’argument de type plutôt qu’une valeur Location sélectionnée à l’intérieur du middleware. Les gestionnaires portent le nom <Provider>CloudHandler — FileShareCloudHandler, AzureCloudHandler, AmazonS3CloudHandler, GoogleCloudHandler, DropBoxCloudHandler, RedisCloudHandler, FTPCloudHandler et CDNCloudHandler.

Le gestionnaire CDN est en lecture‑seule — il sert ce qui est déjà présent sur le CDN ; il ne téléverse rien.

Priorité de la première page et HTTP 202

FirstPagePriority = true rend la page 1 disponible immédiatement. Les pages suivantes répondent HTTP 202 Accepted tant qu’elles ne sont pas encore écrites, au lieu de bloquer la requête.

Cela transfère l’attente vers le client, où elle est bornée et visible. Activez la nouvelle tentative sur le widget :

javascript
const objViewer = $('#div_ctlDoc').docViewer({
    BasePath:   '/',
    ResPath:    'doconut-res/images',
    retryOn409: true      // réessaie les réponses de disponibilité 202 (et les anciennes 409)
});

Un client qui ne réessaie pas affichera des tuiles cassées pour les pages qui sont encore en cours de rendu. Le budget de nouvelles tentatives et ses valeurs par défaut (retryInitialDelayMs, retryBackoffFactor, retryMaxDelayMs, retryMaxAttempts, retryMaxTotalMs) sont documentés dans ConfigurationVisionneuse.

Cette combinaison — FirstPagePriority plus le sondage côté lecture — remplace les anciens paramètres PageWaitTimeSeconds et StartWaitFromPage. Aucun d’eux n’existe plus.

Option : publier en arrière‑plan

Par défaut, PublishAsync rend le document avant de retourner. Pour retourner dès que le jeton est créé et laisser un worker hébergé rendre dans le même dossier, enregistrez la file d’attente d’opt‑in :

csharp
builder.Services.AddDoconut(options => { /* … */ });
builder.Services.AddDoconutDistributedAsyncPublish();

Puis placez les éléments en file d’attente au lieu de publier en ligne, et définissez Async = true sur les options de publication. La file d’attente est bornée : lorsqu’elle est saturée, EnqueueAsync lève DistributedPublishQueueFullException. Répondez avec un 503 et un en‑tête Retry-After afin que les clients ralentissent plutôt que de rester bloqués :

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.");
}

Sans cet enregistrement, rien n’est enregistré et la publication reste synchrone — le chemin par défaut est identique octet à octet.

Lorsque la publication en arrière‑plan est activée, donnez au côté lecture un ReadReadyPollMs non nul afin qu’il attende brièvement une page que le worker n’a pas encore écrite.

Sécurité entre les nœuds

Les jetons liés à la session supposent que le nœud de service a vu la requête d’ouverture. Dans un ferme, ce n’est pas le cas, donc la vérification par session rejetterait les requêtes légitimes. C’est pourquoi l’application de référence distribuée définit UnsafeMode = true.

UnsafeMode = true n’est pas le modèle de sécurité — c’est la suppression d’un modèle qui n’est plus applicable. Remplacez‑le par des tickets d’accès signés, que chaque nœud peut vérifier de façon indépendante :

csharp
builder.Services.AddDoconutDistributedDocumentSecurity(options => { /* … */ });

Ne ramenez jamais UnsafeMode = true dans un déploiement mononœud, et ne le considérez jamais comme un raccourci pour contourner une vérification de session défaillante sur un nœud — dans ce cas, la solution correcte est AddSession() / UseSession(). Voir Sessions et Sécurité.

Fermeture d'un document publié

CloseDocument(token) sur le Viewer injecté libère une session locale. Pour supprimer les artefacts publiés d’un document du partage, l’éditeur expose :

text
bool CloseDistributedDocument(FileShareConfig fileShareConfig, string token)

Passez le même FileShareConfig avec lequel le document a été publié.

Correspondance paramètre par paramètre

PrécédentActuel
UseDoconutWebFarm(new WebFarmOptions { … })PublishAsync(...) du côté écriture plus UseDoconutCloud<FileShareCloudHandler>(...) du côté lecture
WebFarmOptions.PathFileShareConfig.RootPath
PageWaitTimeSeconds / StartWaitFromPageDistributedDocumentPublishOptions.FirstPagePriority plus FileShareConfig.ReadReadyPollMs
viewer.OpenDocument(...) then viewer.ExportToPng(share)un appel PublishAsync(...)
viewer.ExportToCloud(cloudConfig)PublishAsync(...) avec la configuration du fournisseur
UseDoconutCloud(new CloudOptions { Location = … })UseDoconutCloud<THandler>(options => …, pathPrefix)
DocOptions.IsWebfarm / WebfarmPathToujours présent, orthographié IsWebFarm / WebFarmPath — non requis dans le chemin de publication
Doconut.Configs.CloudDoconut.Clouds

Liste de contrôle

  1. Construisez le FileShareConfig dans un helper et appelez‑le depuis les deux moitiés.
  2. Pointez RootPath vers le partage que vous utilisez déjà.
  3. Publiez avec PublishAsync(...) ; supprimez tous les appels ExportToPng / ExportToCloud.
  4. Servez avec UseDoconutCloud<FileShareCloudHandler>(...) sur la branche appelée par le client.
  5. Activez FirstPagePriority et retryOn409 ensemble, ou désactivez les deux.
  6. Remplacez la vérification par session par des tickets d’accès signés — ne déployez pas uniquement UnsafeMode = true.
  7. Vérifiez en ouvrant un document sur un nœud et en demandant une page tardive depuis un autre.

Cette page était-elle utile ?