La guerre contre 404: Faire fonctionner de nouveau les vieilles choses
Introduction
Lorsque vous ' avez blogé depuis 2004 (oui,en fait), vous accumulez beaucoup de débris numériquesM SK5 J’ai récemment importé mes anciens articles de SSK6 ( httpsMSC8wwwMRK9mostlylucidMNK10netMGK11blogMKK12categoryMZK13ImportedMMK14 et j’ai découvert qu’environ tout a été rompu. Liens externes pointant vers des sites disparus il y a une décennie
REMARQUE: Il arrive bientôt, ainsi que la série de recherche sémantique et les fonctionnalités sur le blog . Just a BIG change and I had a few smaller articles to get out firstMSC3 Enjoy M SK4but stuff is still broken right now 🤓)
Le problème se divise en trois parties:
Liens internes - Fixé durant le processus d'importation lui-même en utilisant mon Outil d'importateur ArchiveOrg. mes anciens articles se sont référés l’un à l’autre en utilisant le vieux schéma de URL, donc je les ai réécrits dans le cadre de la migration
Liens externes (outgoing) - Il s'agit de la plus grande . Liens à des ressources externes qui ont disparu depuis lors, sont déplacésM SK3 ou se sont transformées en sites complètement différentsMSC4 Un lien vers une documentation provenant de 2006? DépasséMST6 Une référence à un post sur le blog par quelqu'un qui MST7 a pris leur site depuis longtemps downMst8 MortMSt9 Ces éléments nécessitent une gestion du temps d'exécutionM st10
Demandes incombant - Les gens (et les moteurs de recherche ) tentent toujours d’accéder aux URL anciens comme
/archive/2006/05/15/123.aspx. Le système de recherche sémantique peut souvent déterminer ce qu’ils étaient après.
Maintenant, I de pouvoir ont fait cuire l’archive.org lookups dans le processus d’importationM SK1 Mais ici'c’est ce qui se passe : les liens brochent au fil du temps avenir les interruptions, non seulement celles qui existaient au moment de l’importation.
Cet article traite de mon approche:
- Liens sortants:
BrokenLinkArchiveMiddleware- remplace les liens externes morts par des archives - Liens incombant: L'interprète 404 avec la recherche sémantique - trouve le contenu approprié même des anciennes schémas URL
- Le système d'apprentissage: Comment le site devient plus intelligent au fil du temps grâce aux clics des utilisateurs
- Traitement du contexte: Vérifier les liens sans bloquer des demandes
Le problème des contenus anciens
Voici' c'est la chose à propos de l'InternetM SK1 il ' n'est pas permanent. Ce excellent blog post que vous avez lié dans MST4 disparuMSP5 Ce site de documentationMSV6 Restructuré trois foisMSS7 Votre propre schéma d'URL avant que vous ne vous établisez sur une convention appropriée en matière d'affaissementMSR8 gênantMSSS9
graph TD
A[User requests old post] --> B{Link valid?}
B -->|Yes| C[Happy user]
B -->|No| D[404 Error]
D --> E[Frustrated user]
E --> F[User leaves]
style D stroke:#ef4444,stroke-width:3px
style F stroke:#ef4444,stroke-width:3px
style C stroke:#10b981,stroke-width:3px
L’approche naïve consiste à fixer les liens manuellement.
Aperçu de l'architecture
Le système comporte deux composants principaux qui fonctionnent en tandem:
flowchart TB
subgraph Incoming["Incoming Requests"]
A[User Request] --> B{Page exists?}
B -->|Yes| C[Render Page]
B -->|No| D[404 Handler]
D --> E{Learned redirect?}
E -->|Yes| F[301 Permanent Redirect]
E -->|No| G{High-confidence match?}
G -->|Yes| H[302 Temporary Redirect]
G -->|No| I[Show suggestions]
I --> J[User clicks suggestion]
J --> K[Learn redirect]
end
subgraph Outgoing["Outgoing Links"]
C --> L[BrokenLinkArchiveMiddleware]
L --> M[Extract all links]
M --> N[Register for checking]
L --> O[Replace broken links]
O --> P[Archive.org URLs]
O --> Q[Semantic search results]
O --> R[Remove dead links]
end
style F stroke:#10b981,stroke-width:3px
style H stroke:#f59e0b,stroke-width:3px
style P stroke:#3b82f6,stroke-width:3px
style Q stroke:#8b5cf6,stroke-width:3px
Partie 1: Traitement des liens sortants
BrokenLinkArchiveMiddleware
Ce middleware intercepte les réponses HTML et fait trois choses:
- Extrait tous les liens pour vérifier le contexte
- Remplace les liens externes défectueux connus par des versions archive.org
- Utilise la recherche sémantique pour trouver des remplacements de liens internes défectueux
Nous voulons trouver ici des snapshots d’archive.org de à peu près au moment où le poste a été écrit. Un aperçu d’un article de 2024 peut se référer à un contenu complètement différent.
Voici la structure de base :
public partial class BrokenLinkArchiveMiddleware(
RequestDelegate next,
ILogger<BrokenLinkArchiveMiddleware> logger,
IServiceScopeFactory serviceScopeFactory)
{
public async Task InvokeAsync(
HttpContext context,
IBrokenLinkService? brokenLinkService,
ISemanticSearchService? semanticSearchService)
{
// Only process HTML responses for blog pages
if (!ShouldProcessRequest(context))
{
await next(context);
return;
}
// Capture the response so we can modify it
var originalBodyStream = context.Response.Body;
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
await next(context);
// Process the HTML response
if (IsSuccessfulHtmlResponse(context, responseBody))
{
var html = await ReadResponseAsync(responseBody);
html = await ProcessLinksAsync(html, context, brokenLinkService, semanticSearchService);
await WriteModifiedResponseAsync(originalBodyStream, html, context);
}
else
{
await CopyOriginalResponseAsync(responseBody, originalBodyStream);
}
}
}
Extraire des liens
Nous utilisons un regex généré pour retirer tous les href attributs. Le [GeneratedRegex] l'attribut dans .NET nous permet de compiler-génération du regex tempsM SK2 qui est à la fois plus rapide et plus répartie -freeMSC4
[GeneratedRegex(@"<a[^>]*\shref\s*=\s*[""']([^""']+)[""'][^>]*>",
RegexOptions.IgnoreCase | RegexOptions.Compiled)]
private static partial Regex HrefRegex();
private List<string> ExtractAllLinks(string html, HttpRequest request)
{
var links = new List<string>();
var matches = HrefRegex().Matches(html);
foreach (Match match in matches)
{
var href = match.Groups[1].Value;
// Skip special links (anchors, mailto, etc.)
if (SkipPatterns.Any(p => href.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
continue;
if (Uri.TryCreate(href, UriKind.Absolute, out var uri))
{
if (uri.Scheme == "http" || uri.Scheme == "https")
links.Add(href);
}
else if (href.StartsWith("/"))
{
// Convert relative URLs to absolute for tracking
var baseUri = new UriBuilder(request.Scheme, request.Host.Host,
request.Host.Port ?? (request.Scheme == "https" ? 443 : 80));
links.Add(new Uri(baseUri.Uri, href).ToString());
}
}
return links.Distinct().ToList();
}
Maintenant, vous pouvez raisonnablement utiliser HtmlAgilityPack / httpsM SK1github.comMSC3AngleSharpMST4AnglesSharp ou HTML similaire (et d’autres parseurs MST6 ici pour des scénarios plus complexes mais il s’agissait de surkill à cette fin.
Liens d'inscription pour la vérification du contexte
Nous ne voulons pas bloquer la réponse en vérifiant les liens.
var allLinks = ExtractAllLinks(html, context.Request);
var sourcePageUrl = context.Request.Path.Value;
if (allLinks.Count > 0)
{
// Fire and forget - don't block the response
_ = Task.Run(async () =>
{
using var scope = serviceScopeFactory.CreateScope();
var scopedService = scope.ServiceProvider.GetRequiredService<IBrokenLinkService>();
await scopedService.RegisterUrlsAsync(allLinks, sourcePageUrl);
});
}
Notez le IServiceScopeFactory - nous avons besoin d’un nouveau champ de compétence parce que la demande initiale ' les services couverts seront éliminés avant que notre tâche de fond ne soit achevée.
Importance: C'est un la cohérence éventuelle modèle. Quand Toute le lien est découvert pour la première fois, il fait l'objet d'une queue de validation M SK1 nous ne savons pas'il n'est pas encore brouillé ' Le service de base le vérifie M SK5Requête du chefMSC6 et s'il est brouéMNK7le ,le reçoit un archiveMRK9remplacement par une source de données Suivant Le visiteur de cette page verra le lien fixe. Pour un blog avec un trafic régulier, cela signifie habituellement que les liens défectueux sont réglés dans des heures après la première découverteM SK2 La beauté est que nous n’avons pas à croire quels liens risquent d’être défectus.
Remplacement des liens bloqués
Une fois que nous savons qu’un lien est rompu et qu’il y a un archive.org de remplacement (de la vérification précédente de l’arrière-planM SK2, nous les échangons avec une mise à jour utile
foreach (var (originalUrl, archiveUrl) in archiveMappings)
{
if (html.Contains(originalUrl))
{
var tooltipText = $"Original link ({originalUrl}) is dead - archive.org version used";
var originalPattern = $"href=\"{originalUrl}\"";
var archivePattern = $"href=\"{archiveUrl}\" class=\"tooltip tooltip-warning\" " +
$"data-tip=\"{tooltipText}\" data-original-url=\"{originalUrl}\"";
html = html.Replace(originalPattern, archivePattern);
}
}
Pour les liens internes défectueux, nous essayons la recherche sémantique d’abord:
if (isInternal && semanticSearchService != null)
{
var replacement = await TryFindSemanticReplacementAsync(
brokenUrl, semanticSearchService, request, cancellationToken);
if (replacement != null)
{
html = ReplaceHref(html, brokenUrl, replacement);
continue;
}
}
// No replacement found - convert to plain text
html = RemoveHref(html, brokenUrl);
Partie 2: Examen des liens de fond
Le middleware ne vérifie pas les liens pendant la demande. BrokenLinkCheckerBackgroundService gère le contrôle effectif.
Le service fonctionne en heures et fait deux choses:
- Vérifier la validité du lien - HEAD demande de voir si les liens sont encore vivants
- Trouver les adresses URL de l'archive.org - Pour les liens brisés, trouvez la photo historique la plus proche
Nous sommes des bons citoyens à cet égard.
request.Headers.UserAgent.ParseAdd(
"Mozilla/5.0 (compatible; MostlylucidBot/1.0; +https://www.mostlylucid.net)");
Cela permet aux administrateurs du site de voir ce qui touche leur serveur, 's et ils peuvent nous consulter s’ils sont curieux.
Les liens suivants sont essentiels : Renouveler périodiquement -appuyé. Un lien qui fonctionnait la semaine dernière pourrait être mort aujourd’hui. Le service prend en charge des liens qui n’ont pas été vérifiés dans les dernières heures ' et les vérifie de nouveauM SK4 CependantMSC5 une fois que nousMNK6 avons trouvé un archiveMMK7 un remplacement d’org pour un lien défectueuxMRK8 nous ne l’avons pas encore fait,' nous n’en sommes pas encore arrivés à la version originale MNK11 c’est-à-dire ' il est déjà mort et nous disposons d’un remplaçant en cours de travailMBK13
sequenceDiagram
participant BG as Background Service
participant DB as Database
participant Web as External Sites
participant Archive as Archive.org CDX API
loop Every Hour
BG->>DB: Get links not checked in 24h (batch of 20)
loop For each link
BG->>Web: HEAD request
Web-->>BG: Status code
BG->>DB: Update link status + LastCheckedAt
end
BG->>DB: Get broken links needing archive lookup
loop For each broken link
BG->>DB: Look up source post publish date
BG->>Archive: CDX API query (filtered by date)
Archive-->>BG: Closest snapshot
BG->>DB: Store archive URL (permanent)
end
end
The Archive.org CDX API
Archive.org fournit une API CDX M SK1Capture Index) qui nous permet de faire des recherches sur les photos prises à l'aide d'un fichier ci-dessous.
private async Task<string?> GetArchiveUrlAsync(
string originalUrl,
DateTime? beforeDate,
CancellationToken cancellationToken)
{
var queryParams = new List<string>
{
$"url={Uri.EscapeDataString(originalUrl)}",
"output=json",
"fl=timestamp,original,statuscode",
"filter=statuscode:200", // Only successful responses
"limit=1"
};
// Find snapshot closest to the blog post's publish date
if (beforeDate.HasValue)
{
queryParams.Add($"to={beforeDate.Value:yyyyMMdd}");
queryParams.Add("sort=closest");
queryParams.Add($"closest={beforeDate.Value:yyyyMMdd}");
}
var apiUrl = $"https://web.archive.org/cdx/search/cdx?{string.Join("&", queryParams)}";
// ... fetch and parse response
return $"https://web.archive.org/web/{timestamp}/{original}";
}
Cela signifie que si j’ai écrit un post dans 2008 liant à une ressource , je' obtiendrai l’archive .org snapshot de la région \2008, pas une version moderne qui pourrait être complètement différente
Surveillance de l'état des liens
Nous suivons les liens avec une entité appropriée:
[Table("broken_links", Schema = "mostlylucid")]
public class BrokenLinkEntity
{
public int Id { get; set; }
public string OriginalUrl { get; set; } = string.Empty;
public string? ArchiveUrl { get; set; }
public bool IsBroken { get; set; } = false;
public int? LastStatusCode { get; set; }
public DateTimeOffset? LastCheckedAt { get; set; }
public int ConsecutiveFailures { get; set; } = 0;
public string? SourcePageUrl { get; set; } // For publish date lookup
}
Examen périodique Re-checking
La clé pour attirer les ruptures futures est de re-checking. Les demandes de service concernant des liens qui n’ont pas été validés récemmentM SK2
public async Task<List<BrokenLinkEntity>> GetLinksToCheckAsync(int batchSize, CancellationToken cancellationToken)
{
var cutoff = DateTimeOffset.UtcNow.AddHours(-24);
return await _dbContext.BrokenLinks
.Where(x => x.LastCheckedAt == null || x.LastCheckedAt < cutoff)
.OrderBy(x => x.LastCheckedAt ?? DateTimeOffset.MinValue) // Oldest first
.Take(batchSize)
.ToListAsync(cancellationToken);
}
Cela signifie que chaque lien est validé au moins une fois par jour. ConsecutiveFailures le champ nous permet d’être un peu pardonnants - nous ne marquons pas une liaison comme défectueuse après une erreur transitoire
Partie 3: Traitement des demandes reçues
Maintenant, pour l’autre côté de la monnaie, les personnes qui demandent des URLs qui ne sont pas existantes dans le ' et les moteurs de recherche du . incluent ce qui suit :
- Schemes URL anciens - Mon antique blog a utilisé des chemins comme
/archive/2006/05/15/123.aspx. Certains d’entre eux sont encore indexés , affichés à l’index de la pageM SK2 ou liés d’autres sites. - Types - Someone fat-fingers the URL or copie it incorrectly .
- Enlèvements partiels - Les moteurs de recherche indexent parfois des fragments étranges
Le système de recherche sémantique est particulièrement utile à cet égard. Même sans une correspondance directe entre les libellées d’un libellés de l’URL requise, nous pouvons extraire des termes significatifs de la URL demandée et trouver un contenu qui corresponde sémantiellement aux libelles d’une URL requise. Le système connaît aussi bien le libellement que les données embedding pour chaque postM SK3 de sorte qu’il peut trouver " clore suffisammentMSC5 des mots qui correspondent même à des structures URL complètement différentesMNK6
Pourquoi pas Middleware?
Mon premier instinct était de traiter les redirections dans le middleware - demandes d'intercepte précoces , vérifier pour les redirections connues, et rediriger avant que la routage ne soit même lancée en de pouvoir Travailler , et ici ' c'est ce qu'il ressemblerait
// DON'T DO THIS - runs on EVERY request
public class SlugRedirectMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context, ISlugSuggestionService? service)
{
if (context.Request.Path.StartsWithSegments("/blog"))
{
var targetSlug = await service.GetAutoRedirectSlugAsync(slug, language);
if (!string.IsNullOrWhiteSpace(targetSlug))
{
context.Response.Redirect($"/blog/{targetSlug}", permanent: true);
return;
}
}
await next(context);
}
}
Le problème? Ce système fonctionne chaque demande unique à /blog/*. C'est ce que ' est une demande de base de données sur chaque page d'affichage
L'approche beaucoup meilleure: hook dans l'interface 404 handler . Si ASPM SK3NET a déjà déterminé que la page n'existe pas puis Nous vérifions les redirections. Aucune demande inutile sur des pages valides .
Fait intéressant: Au début de l’ASP.NET MVC c’est ainsi que nous avons fait fonctionner des URLs convivialesMSC2 Scott GuthrieM SK3 la démonstration aérienne qui a démarré MVC a utilisé cette approche ; couper dans le système IIS MST5 le système de manutention MST6 lire les URL et servir le contenu approprié Mst7 C’était ALSO ce que j’ai utilisé dans un système que je me suis construit dans WebForms
Le système d'apprentissage
Lorsque quelqu’un touche un 404,, nous leur montrons des suggestions en utilisant le parallèle de chaînes floues et une recherche sémantique.
stateDiagram-v2
[*] --> RequestReceived
RequestReceived --> RoutingCheck
RoutingCheck --> PageFound: Exists
RoutingCheck --> NotFound: 404
PageFound --> [*]
NotFound --> ErrorController
ErrorController --> CheckLearnedRedirect
CheckLearnedRedirect --> Redirect301: Has learned redirect
CheckLearnedRedirect --> CheckHighConfidence: No learned redirect
CheckHighConfidence --> Redirect302: Score >= 0.85 & gap >= 0.15
CheckHighConfidence --> ShowSuggestions: Lower confidence
ShowSuggestions --> UserClicks
UserClicks --> RecordClick
RecordClick --> UpdateWeight
UpdateWeight --> CheckThreshold
CheckThreshold --> EnableAutoRedirect: Weight >= 5 & confidence >= 70%
CheckThreshold --> [*]: Below threshold
EnableAutoRedirect --> [*]
Le manutentionnaire 404
Toute la logique de redirection vit dans le ErrorController. ASPM SK1NET's UseStatusCodePagesWithReExecute middleware re-execute la demande par l'entremise de notre gestionnaire d'erreurs lorsque se produit un 404
public class ErrorController(
BaseControllerService baseControllerService,
ILogger<ErrorController> logger,
ISlugSuggestionService? slugSuggestionService = null) : BaseController(baseControllerService, logger)
{
[Route("/error/{statusCode}")]
[HttpGet]
public async Task<IActionResult> HandleError(int statusCode, CancellationToken cancellationToken = default)
{
var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
switch (statusCode)
{
case 404:
// Check for auto-redirects before showing 404 page
var autoRedirectResult = await TryAutoRedirectAsync(statusCodeReExecuteFeature, cancellationToken);
if (autoRedirectResult != null)
return autoRedirectResult;
var model = await CreateNotFoundModel(statusCodeReExecuteFeature, cancellationToken);
return View("NotFound", model);
case 500:
return View("ServerError");
default:
return View("Error");
}
}
}
L'article 5 de la Loi sur l'immigration et le statut des réfugiés TryAutoRedirectAsync la méthode gère à la fois les redirections apprises et les matches de temps élevés-confidence premièreM SK1:
private async Task<IActionResult?> TryAutoRedirectAsync(
IStatusCodeReExecuteFeature? statusCodeReExecuteFeature,
CancellationToken cancellationToken)
{
if (slugSuggestionService == null || statusCodeReExecuteFeature == null)
return null;
var originalPath = statusCodeReExecuteFeature.OriginalPath ?? string.Empty;
var (slug, language) = ExtractSlugAndLanguage(originalPath);
// First: check for learned redirects (user previously clicked a suggestion)
// These get 301 Permanent Redirect - confirmed patterns
var learnedTargetSlug = await slugSuggestionService.GetAutoRedirectSlugAsync(
slug, language, cancellationToken);
if (!string.IsNullOrWhiteSpace(learnedTargetSlug))
{
var redirectUrl = BuildRedirectUrl(learnedTargetSlug, language);
logger.LogInformation("Learned auto-redirect (301): {Original} -> {Target}", originalPath, redirectUrl);
return RedirectPermanent(redirectUrl);
}
// Second: check for high-confidence first-time matches
// These get 302 Temporary Redirect until confirmed by user clicks
var firstTimeTargetSlug = await slugSuggestionService.GetFirstTimeAutoRedirectSlugAsync(
slug, language, cancellationToken);
if (!string.IsNullOrWhiteSpace(firstTimeTargetSlug))
{
var redirectUrl = BuildRedirectUrl(firstTimeTargetSlug, language);
logger.LogInformation("First-time auto-redirect (302): {Original} -> {Target}", originalPath, redirectUrl);
return Redirect(redirectUrl);
}
return null; // No redirect - show suggestions
}
Évaluation de la similitude
Le service de suggestion utilise la distance de Levenshtein (distance d'éditionM SK1 plus quelques héuristiques:
private double CalculateSimilarity(string source, string target)
{
if (string.Equals(source, target, StringComparison.OrdinalIgnoreCase))
return 1.0;
source = source.ToLowerInvariant();
target = target.ToLowerInvariant();
// Levenshtein distance converted to similarity (0-1)
var distance = CalculateLevenshteinDistance(source, target);
var maxLength = Math.Max(source.Length, target.Length);
var levenshteinSimilarity = 1.0 - (double)distance / maxLength;
// Bonus if one string contains the other
var substringBonus = (source.Contains(target) || target.Contains(source)) ? 0.2 : 0.0;
// Bonus for common prefix (catches typos at the end)
var prefixLength = GetCommonPrefixLength(source, target);
var prefixBonus = (double)prefixLength / Math.Min(source.Length, target.Length) * 0.1;
return Math.Min(1.0, levenshteinSimilarity + substringBonus + prefixBonus);
}
Apprentissage à partir du comportement de l'utilisateur
Lorsque l’utilisateur clique sur une suggestion, nous la consignons et actualisons les cotes de confiance.
public async Task RecordSuggestionClickAsync(
string requestedSlug,
string clickedSlug,
string language,
int suggestionPosition,
double originalScore,
CancellationToken cancellationToken = default)
{
var redirect = await _context.SlugRedirects
.FirstOrDefaultAsync(r =>
r.FromSlug == normalizedRequestedSlug &&
r.ToSlug == normalizedClickedSlug &&
r.Language == language,
cancellationToken);
if (redirect == null)
{
redirect = new SlugRedirectEntity
{
FromSlug = normalizedRequestedSlug,
ToSlug = normalizedClickedSlug,
Language = language,
Weight = 1
};
_context.SlugRedirects.Add(redirect);
}
else
{
redirect.Weight++;
redirect.LastClickedAt = DateTimeOffset.UtcNow;
}
redirect.UpdateConfidenceScore();
await _context.SaveChangesAsync(cancellationToken);
}
Le calcul du score de confiance tient compte à la fois des clics et des impressions:
public void UpdateConfidenceScore()
{
var total = Weight + ShownCount;
ConfidenceScore = total > 0 ? (double)Weight / total : 0.0;
// Enable auto-redirect after 5+ clicks with 70%+ confidence
if (Weight >= AutoRedirectWeightThreshold &&
ConfidenceScore >= AutoRedirectConfidenceThreshold)
{
AutoRedirect = true;
}
}
Deux-Tier Auto-Redirection
Nous avons deux niveaux de redirection automatique:
Premier-temps élevéM SK1confidence (302): Si la note de similitude est >= 0.85 ET qu’il y a une différence significative entre les deux matchs, nous redirectionnons immédiatement avec un
Redirections apprises (301): Après les clics de 5+ avec la confiance de |70%+, nous utilisons un redirectionnement permanent de \301.
public async Task<string?> GetFirstTimeAutoRedirectSlugAsync(
string requestedSlug,
string language,
CancellationToken cancellationToken = default)
{
var suggestions = await GetSuggestionsWithScoreAsync(requestedSlug, language, 2, cancellationToken);
if (suggestions.Count == 0) return null;
var topMatch = suggestions[0];
// Need high confidence
if (topMatch.Score < 0.85) return null;
// If there's only one suggestion with high score, redirect
if (suggestions.Count == 1) return topMatch.Post.Slug;
// Multiple suggestions - only redirect if there's a clear winner
var scoreGap = topMatch.Score - suggestions[1].Score;
if (scoreGap >= 0.15)
return topMatch.Post.Slug;
return null; // Too close to call - show suggestions instead
}
Mettre tout ensemble
Quand Middleware fait son sens (et quand il ne le fait pasM SK1t)
Pour Liens sortants, le logiciel de middleware est la bonne option BrokenLinkArchiveMiddleware doit intercepter la réponse HTML après il' a été rendu mais précédemment Il n’y a pas d’autre endroit raisonnable pour le faire - nous devons modifier le flux de réponse , et la middleware est conçue exactement pour ce qui suit :
Pour redirections incombant, le middleware est la mauvaise option . Nous'd’effectuerons des recherches sur chaque base de données /blog/* demande simplement de vérifier si peut-être , possible, cette URL pourrait avoir besoin d'un redirectionnement . L'approche de l'interprète 404 ne fonctionne que lorsque nous avons déjà déterminé que la page n'existe pasM SK5 non plus.
// In Program.cs
app.UseStatusCodePagesWithReExecute("/error/{0}"); // Handles 404s through ErrorController
app.UseStaticFiles();
app.UseRouting();
// ... other middleware
app.UseBrokenLinkArchive(); // Process outgoing links in responses (ONLY for middleware)
Le flux ressemble à ce qui suit:
flowchart LR
subgraph Request["Request Processing"]
direction TB
A[Request] --> B[Static Files]
B --> C[Routing]
C --> D{Page exists?}
D -->|Yes| E[MVC/Endpoints]
D -->|No| F[ErrorController 404]
F --> G{Auto-redirect?}
G -->|Yes| H[Redirect]
G -->|No| I[Show suggestions]
end
subgraph Response["Response Processing"]
direction TB
E --> J[BrokenLinkArchiveMiddleware]
J --> K[Link Extraction]
K --> L[Link Replacement]
L --> M[Response]
end
subgraph Background["Background Processing"]
direction TB
N[BrokenLinkCheckerService] --> O[Check URLs]
O --> P[Fetch Archive.org]
P --> Q[Update Database]
end
K -.->|Register links| N
style F stroke:#ef4444,stroke-width:3px
style J stroke:#8b5cf6,stroke-width:3px
style N stroke:#f59e0b,stroke-width:3px
Conclusion
Ce système fonctionne depuis un certain temps et il est plutôt satisfaisant de le voir apprendre.
Principes clés:
- Don't demandes de blocage - utiliser le traitement de fond pour des opérations coûteuses
- Étudier auprès des utilisateurs - leurs clics sont des signaux valables
- Soyez prudent avec les redirections auto- - ne redirige que lorsque vous êtes confiant
- Le temps-aware archiving - recherche d'archives. snapshots de l'org du moment où le contenu a été écrit
- Dégradation gracieuse - si les services ne sont pas disponibles ' non disponible M SK2 se poursuivent normalement
Il n’est pas parfait. Certains liens sont vraiment perdus pour toujours et la recherche sémantique ne permet pas de trouver le remplacement approprié.
Coming Soon: Recherche sémantique Deep Dive
Cette semaine, je publierai une série de recherches sémantiques sur la façon dont fonctionne le vecteur basé sur - sous le capuchon. ISemanticSearchService.SearchAsync en fait trouve " similaire" contenuM SK2 queMSC3 est là où vous voudrez regarder
La source complète se trouve dans le répertoire si vous voulez en faire un extrait pour vos propres projets.