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 :
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 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 :
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 |
|---|---|
Location | CloudLocation.FileShare pour un partage UNC/local |
RootPath | Le dossier partagé accessible par chaque nœud |
UsePerTokenSubfolders | Conserver les artefacts de chaque document dans son propre dossier {token} |
ReadReadyPollMs | Duré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 :
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à :
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 :
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 :
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 :
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 :
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 :
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édent | Actuel |
|---|---|
UseDoconutWebFarm(new WebFarmOptions { … }) | PublishAsync(...) du côté écriture plus UseDoconutCloud<FileShareCloudHandler>(...) du côté lecture |
WebFarmOptions.Path | FileShareConfig.RootPath |
PageWaitTimeSeconds / StartWaitFromPage | DistributedDocumentPublishOptions.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 / WebfarmPath | Toujours présent, orthographié IsWebFarm / WebFarmPath — non requis dans le chemin de publication |
Doconut.Configs.Cloud | Doconut.Clouds |
Liste de contrôle
- Construisez le
FileShareConfigdans un helper et appelez‑le depuis les deux moitiés. - Pointez
RootPathvers le partage que vous utilisez déjà. - Publiez avec
PublishAsync(...); supprimez tous les appelsExportToPng/ExportToCloud. - Servez avec
UseDoconutCloud<FileShareCloudHandler>(...)sur la branche appelée par le client. - Activez
FirstPagePriorityetretryOn409ensemble, ou désactivez les deux. - Remplacez la vérification par session par des tickets d’accès signés — ne déployez pas uniquement
UnsafeMode = true. - Vérifiez en ouvrant un document sur un nœud et en demandant une page tardive depuis un autre.
Cette page était-elle utile ?