# 404 में युद्ध: पुरानी चीज़ें बनाने का काम फिर से करना

<!--category-- ASP.NET, C# -->
<datetime class="hidden">2025-11-23T10:35</datetime>

## परिचय

जब आप 2004 के बाद से ब्लॉगिंग कर रहे हैं (हाँ, सच में), आप डिजिटल डीटीटीटीएस के बहुत सारे संग्रह जमा करते हैं. मैंने हाल ही में अपने पुराने पोस्टों को 2004- 2007 से आयात किया ( http://www.wiki.net.net/ राहे ओर से_BAR_BAR_netd) और कि लगभग पता चला गया _BAR_ *सभी* बाहरी लिंक ऐसे साइटों की ओर इशारा करते हैं जो एक दशक पहले गायब हो गए थे, पुराने यूआरएल योजनाों में जो अब मौज़ूदा संरचना, पूरी हद से मेल नहीं खाते.

समस्या तीन भागों में नीचे गिर जाती है:

1. **आंतरिक लिंक** - आयात प्रक्रिया के दौरान स्वयं को मेरे प्रयोग से स्थिर [अभिलेखउसके द्वारा किए गए परिवर्तनों के लिए एक्ज़इफ ओरिएन्टेशन उपकरण.](https://github.com/scottgal/mostlylucid.nugetpackages/tree/main/Mostlylucid.ArchiveOrg). मेरे पुराने पोस्टों ने पुरानी यूआरएल योजना का प्रयोग करते हुए एक दूसरे का उल्लेख किया, तो मैं उत्प्रवासन के भाग के रूप में उन्हें फिर से बदल देता हूँ.

2. **बाहरी कड़ियाँ ( आउट)** - यह बड़ा है _BAR_ बाहरी संसाधनों के लिए कड़ी है कि अब से गायब हो गया है, या पूरी तरह से अलग साइट बन गए हैं _BAR_ 2006 से कुछ दस्तावेजों के लिए एक लिंक चला गया _BAR_ एक ब्लॉग पोस्ट जो लंबे समय से उनके साइट के नीचे ले लिया गया था _BAR_ मृत नियंत्रण की आवश्यकता है _BAR_ (R_)

3. **इनकमिंग आग्रह** - लोग (और खोज इंजिन) अभी भी पुराने यूआरएलों को ऐसे एक्सेस करने की कोशिश करते हैं `/archive/2006/05/15/123.aspx`. manmanmanmanmanmium प्रणाली अकसर पता लगा सकते हैं कि वे क्या कर रहे हैं, यहाँ तक कि एक सटीक मैच के बिना भी।

अब, मैं *कर सकता है* अभिलेख बनाया गया है आयात प्रक्रिया में. लेकिन यहाँ पर बात है, समय पर लिंक ब्रेक. एक साइट है जो आज काम कर रहा है अगले महीने में चला जा सकता है. इस गति से आवधिक पुनः जाँच के साथ तेज, सिस्टम स्वचालित रूप से बाहर हो सकता है. *भविष्य* न सिर्फ़ वे लोग जो आयात के समय अस्तित्व में थे ।

इस लेख में मेरी तरफ बताया गया है:

- **जावक लिंक**: `BrokenLinkArchiveMiddleware` - अभिलेखागार के साथ मरे हुए बाहरी लिंक को प्रतिस्थापित करता है.org स्नैपशॉट
- **आवक लिंक**: पुराने यूआरएल योजना से भी 404 हैंडलर
- **सीखने की प्रणाली**: कैसे साइट उपयोगकर्ता क्लिक से अधिक समय तक स्मार्ट हो जाता है
- **पृष्ठभूमि प्रक्रिया**ब्लॉक निवेदन के बगैर लिंक चेक किया जा रहा है

[TOC]

## पुरानी सामग्री के साथ समस्या

यहां इंटरनेट के बारे में बात है: यह स्थायी नहीं है. यह बढ़िया ब्लॉग पोस्ट जिसे आपने 2006 में जोड़ा था? वो दस्तावेज़ बंद हो गया? यह दस्तावेज़ तीन बार बंद हो गया. आपकी खुद की योजना आप से पहले एक उचित प्रवरित सम्मेलन में बसने से पहले? एमआईएसआईएस.

```mermaid
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
```

लेकिन जब आपके पास हजारों कड़ियों के साथ सैकड़ों पोस्टों को ठीक करने के लिए है, कि पर नहीं है। हमें लिंक की जरूरत है।

## ओवरव्यू

तंत्र में दो मुख्य अवयव हैं जो कि टीक में काम करते हैं:

```mermaid
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
```

## पार्ट १: देखे जा सकने वाले लिंक्स को संभालता है

### टूटीलिंक अभिलेखक

इस बीच में एचटीएमएल प्रतिक्रियाएं तथा तीन बातें करता है:

1. पृष्ठभूमि जाँच के लिए सभी लिंक निकालें
2. अभिलेखों के साथ विकृत बाहरी लिंकों को बदलता है.org संस्करण से.
3. विकृत आंतरिक लिंकों के बदले बदलने के लिए तंत्र खोज का उपयोग करता है

कुंजी अन्तर्दृष्टि यह है कि हम अभिलेख ढूंढना चाहते हैं.org स्नैपशॉट से *समय के चारों ओर पोस्ट लिखा गया*सन्‌ 2006 के लेख के एक स्नेपशॉट के बारे में पूरी तरह से जानकारी हो सकती है. तो हम ब्लॉग पोस्ट पोस्ट को प्रकाशित तिथि देखते हैं और निकटतम स्नेपशॉट के लिए अभिलेख पूछते हैं.org.

यहाँ कोर स्ट्रक्चर है:

```csharp
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);
        }
    }
}
```

### लिंक्स निकाल रहे

हम सब बाहर खींचने के लिए एक उत्पन्न जहन्नम का उपयोग करें `href` गुण. `[GeneratedRegex]` गुण .netT हमें कंपाइल-समय पीढ़ी देता है, जो दोनों तेजी से और आवंटन मुक्त है:

```csharp
[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();
}
```

अब आप काफी हद तक उपयोग कर सकते हैं [Hatatatacks](https://html-agility-pack.net/) /secs.com/ALACAN/AAAAS case cly / एक तेज या समान HTML (और अन्य) विश्लेषण यहाँ अधिक जटिल घटनाओं के लिए, लेकिन यह fhotaks के लिए किया गया था - हम सिर्फ जल्दी से बाहर निकालने की जरूरत है.

### पृष्ठभूमि जांच के लिए लिंक्स का पंजीकृत किया जा रहा है

हम लिंक की जाँच के दौरान प्रतिक्रिया ब्लॉक नहीं करना चाहते. इसके बजाय, हम एक पृष्ठभूमि कार्य से आग:

```csharp
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);
    });
}
```

टिप्पणी `IServiceScopeFactory` - हम एक नया स्कोप की जरूरत है क्योंकि मूल निवेदन सेवा हमारे पृष्ठभूमि कार्य पूरा होने से पहले लागू किया जाएगा।

**महत्वपूर्ण**: यह एक है *अंत में संगतता* मॉडल, जब *कोई भी* लिंक पहले पता चला जाता है, यह वैधीकरण के लिए कतार हो जाता है - हम नहीं जानते कि यह टूट गया है या नहीं. पृष्ठभूमि सेवा यह जाँच करता है (एएसएएएस निवेदन), और अगर यह टूट गया है, एक अभिलेख लाने के लिए लाने.org. *अगला* उस पृष्ठ पर उपस्थित लोगों को निर्धारित लिंक दिखाई देगा. नियमित यातायात के साथ एक ब्लॉग के लिए, इस विशेष रूप से टूटी हुई कड़ियों का उपयोग पहले खोज के घंटों के भीतर तय किया जा सकता है. सुंदर है हमें अनुमान लगाने के लिए नहीं है कि कौन सा लिंक तोड़ दिया जा सकता है - हम सिर्फ उन सभी को स्वचालित रूप से वैध कर सकते हैं.

### टूटी कड़ियां बदली जा रही हैं

एक बार हम जानते हैं कि एक लिंक टूट गया है और एक अभिलेख है.org बदलाव (पिछले पृष्ठभूमि की जाँच से), हम उन्हें एक सहायक विवरण के साथ बदल देते हैं:

```csharp
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);
    }
}
```

आंतरिक लिंक के लिए, हम पहली बार परीक्षण की कोशिश करें:

```csharp
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);
```

## पार्ट 2: पृष्ठभूमि लिंक की जांच कर रहा है

निवेदन - कि अब तक बहुत धीमा हो जाएगा के दौरान आंतरिक लिंक की जाँच नहीं करता. इसके बजाय, यह कतार किसी डाटाबेस तालिका, और एक पृष्ठभूमि प्रक्रिया के लिए कड़ी प्राप्त की गई है `BrokenLinkCheckerBackgroundService` वास्तविक जाँच संभालता है.

सेवा घंटे दौड़ता है और दो चीजें करता है.

1. **कड़ी जाँचें** - सिर यह देखने के लिए अनुरोध करता है कि क्या लिंक अभी भी जीवित हैं
2. **अभिलेख प्राप्त करें.org यूआरएल** - टूटे लिंक के लिए, निकटतम ऐतिहासिक स्नेपशॉट को ढूंढें

हम इस बारे में अच्छे नागरिक हैं - चेक एक मनपसंद उपयोक्ता एजेंट का उपयोग करता है जो खुद की पहचान कराता है और साइट में वापस लिंक करता है:

```csharp
request.Headers.UserAgent.ParseAdd(
    "Mozilla/5.0 (compatible; MostlylucidBot/1.0; +https://www.mostlylucid.net)");
```

यह साइट प्रशासकों को देखने देता है कि उनके सर्वर को क्या मार रहा है, और वे हमें देख सकते हैं अगर वे उत्सुक हैं. हम भी जाँच के बीच 2 सेकण्ड अनुरोध करते हैं, जो कि किसी के सर्वर को हटाने से बचने के लिए.

कॉमीसी, लिंक हैं **समय-जांच**. एक लिंक जो पिछले सप्ताह काम कर रहा था आज मृत हो सकता है. सेवा लिंक लेता है कि पिछले 24 घंटे में चेक नहीं किया गया है और फिर उन्हें फिर से स्थापित नहीं किया गया है. लेकिन, एक बार जब हम एक टूटे हुए लिंक के लिए एक अभिलेख मिल गया है, हम मूल - यह पहले से ही मृत है और हम एक काम करने के लिए नहीं है.

```mermaid
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
```

### अभिलेख.org सीडी API

अभिलेख.org एक सीडीएक्स (कंत्र निर्देशिका)क प्रदान करता है जो कि हमें स्नैपशॉट के लिए क्वैरी करता है. चतुर सा फ़िल्टर तारीख तक फ़िल्टरिंग है:

```csharp
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}";
}
```

इसका मतलब है कि अगर मैं 2008 में कुछ संसाधन के लिए लिंक में एक पोस्ट लिखा, मैं 2008 से अभिलेख मिल जाएगा, एक आधुनिक संस्करण है कि पूरी तरह से अलग हो सकता है.

### लिंक स्थिति खोज

हम एक उचित एंटिटी के साथ ट्रैक करें:

```csharp
[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
}
```

### आवधिक री- जांच

भविष्य में ब्रेकिंग करने के लिए कुंजी फिर से जाँच है. लिंक के लिए सेवा अनुमतिओं को हाल ही में वैध नहीं किया गया है:

```csharp
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);
}
```

इसका मतलब है कि हर लिंक एक दिन कम से कम एक बार फिर शुरू हो जाता है. यदि एक पहले काम लिंक 404s वापस आ जाता है, हम इसे पकड़ लेंगे और एक अभिलेख के लिए देख शुरू करेंगे. `ConsecutiveFailures` क्षेत्र हमें थोड़ा क्षमा करें - हम एक टुकड़े के रूप में एक टुकड़े नहीं है एक अस्थायी त्रुटि के रूप में.

## पार्ट 3: इनकमिंग अनुरोधों को हैंडल करें

अब सिक्के के अन्य पक्ष के लिए: लोग (और खोज इंजन) जो मौजूद नहीं है उन यूआरएल को अनुरोध करते हैं. इसमें यह भी शामिल है:

- **पुराना यूआरएल योजना** - मेरे प्राचीन ब्लॉग ने ऐसे पथ इस्तेमाल किए `/archive/2006/05/15/123.aspx`इनमें से कुछ अभी भी सूचीबद्ध, पसंदीदा, या अन्य साइटों से जुड़े हुए हैं ।
- **टाइपस** - कोई वसा से यूआरएल या इसे गलत तरीके से कॉपी करता है.
- **आंशिक** - खोज इंजन कभी कभी-कभी बदल अजीब टुकड़े.

इटेलिक खोज तंत्र विशेष रूप से यहाँ मदद करता है. यहाँ भी एक सीधे मेल मिलान के बिना, हम अनुरोधित यूआरएल से शब्दों को निकाल सकते हैं और उस सामग्री को पा सकते हैं जो कि अनंतता से मेल खाती है. तंत्र जानता है कि दोनों पोस्ट के लिए डाटा जानता है और प्रत्येक पोस्ट के लिए डाटा जानता है, तो यह "पूरी तरह से मेल खाता है" तो यह भी पूरी तरह से अलग तरह से भिन्न यूआरएल के लिए जोड़ सकता है.

### क्यों मध्य - पूर्व नहीं?

मेरी पहली ख्वाहिश थी कि बीच - बीच में राय कायम करने के लिए बार - बार कुछ फेरबदल करने की गुज़ारिश की जाए, जाने - माने एहसानों की जाँच करें, यहाँ तक कि लात मारने से पहले फिर से कोशिश करें । *कर सकता है* काम, और यहाँ यह कैसा लग रहा है:

```csharp
// 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);
    }
}
```

समस्या यह चल रहा है? *प्रत्येक निवेदन* को `/blog/*`यही कारण है कि हर पृष्ठ के दृश्य पर एक डाटाबेस क्वैरी है, यहाँ तक कि पूरी तरह से वैध यूआरएल के लिए. बढ़िया यातायात के साथ एक ब्लॉग के लिए, आप उस समय के कोई उचित कारण 99% के लिए डाटाबेस का उपयोग कर रहे हैं.

बहुत बेहतर तरीका है: 404 हैंडलर में हुक. यदि कोई मतलब हो तो पहले से ही पृष्ठ मौजूद नहीं है. *तब* हम रूट के लिए जाँच करते हैं. वैध पृष्ठों पर कोई बर्बादता नहीं.

> अजीब बात है: AERTVC के प्रारंभिक दिनों में यह था कि हम कैसे दोस्ताना यूआरएल काम किया. स्कॉट GVC के विमान डेमो जिसने एमVC शुरू किया था, HISVC प्रणाली में हुक, यूआरएल को पढ़ने और सही सामग्री का काम करने में. यह एक सक्रिय था जो मैं एक प्रणाली में इस्तेमाल किया था एक वेब 3 के लिए एक वेब 3 s. मैं कैसे एक टीम पर एक टीम मिल गया!

### सीखने की व्यवस्था

जब किसी को 404 हिट करता है, हम उन्हें सुझाव दिखाते हैं कि लिफाफे से मेल खाते हैं और (वैकल्पिक) खोज करते हैं. यदि वे किसी सुझाव पर क्लिक करते हैं, तो हम रिकॉर्ड करते हैं कि पर्याप्त भरोसे के साथ पर्याप्त क्लिक के बाद, हम ऑटो-चिंग शुरू करते हैं.

```mermaid
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 --> [*]
```

### 404 हैंडलर

सभी पुनर्निदेशित तर्क जीवन में `ErrorController`. एक मतलब है. `UseStatusCodePagesWithReExecute` हमारे त्रुटि हैंडलर के माध्यम से निवेदन को मध्य- भाग दें जब 404 घटित होता है:

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

वह `TryAutoRedirectAsync` विधि दोनों सीखने वाले पुनर्निदेशों तथा पहली बार निर्भरता से मेल खाता है:

```csharp
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
}
```

### एक जैसे स्कोरिंग

सुझाव सेवा लंबी दूरी का उपयोग करती है (दूरी दूरी) और कुछ विशेषज्ञ:

```csharp
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);
}
```

### उपयोक्ता व्यवहार से सीखना

जब कोई उपयोगकर्ता किसी सुझाव को क्लिक करता है, हम इसे रिकॉर्ड करते हैं और विश्‍वास के अंक का अद्यतन करते हैं:

```csharp
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);
}
```

भरोसा स्कोर गणना दोनों क्लिकों और लक्षणों को दर्शाता है:

```csharp
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;
    }
}
```

### दो- टी- टी स्वचालित सेटिंग

हमारे पास स्वचालित पुनर्निदेशित के दो स्तर हैं:

1. **प्रथम- समय उच्च भरोसा (302)**: यदि समानता अंक है > = 0.85 और वहाँ दूसरी सबसे अच्छी मैच के लिए एक महत्वपूर्ण अंतराल है, हम तुरंत एक 302 के साथ पुनर्निदेशित करते हैं. यह स्पष्ट रूप से caseoss.

2. **जवाब (301) सीखा**: 5+सी क्लिक के बाद 70%+ आत्म - विश्‍वास के साथ, हम एक स्थायी 301 दफा का उपयोग करते हैं. यह "मानवों ने कहा है" मामले है.

```csharp
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
}
```

## यह सब एक साथ रखना

### जब मध्य ऐंचर्स सुव्यवस्था करता है (और जब यह नहीं करता)

के लिए **बाहर जाने वाले लिंक**, बीच में चुनाव सही है . `BrokenLinkArchiveMiddleware` एचटीएमएल प्रतिक्रिया दिखाने की जरूरत है *पश्चात* यह अनुवाद किया गया है लेकिन *पहले* यह ग्राहक को भेजा गया है. वहाँ कोई अन्य बुद्धिमान जगह नहीं है यह करने के लिए - हम प्रतिक्रिया स्ट्रीम को संशोधित करने की जरूरत है, और मध्य हॉल को वास्तव में उस के लिए बनाया गया है.

के लिए **आने वाला पुनर्निदेश**- बीच में चुनाव गलत है। हम प्रत्येक पर डाटाबेस जांच हो रही होगी `/blog/*` सिर्फ यह जाँचने के लिए अनुरोध करें कि क्या हो सकता है, संभवतः इस URL को एक पुनर्निदेशित की आवश्यकता हो सकती है. 404 हैंडलर केवल तब चलाता है जब हम पहले से ही पृष्ठ के अस्तित्व में नहीं है.

```csharp
// 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)
```

प्रवाह इस तरह दिखता है:

```mermaid
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
```

## कंटेनमेंट

यह तंत्र अब कुछ समय के लिए चल रहा है और यह संतोषजनक रूप से देखने के लिए चल रहा है. डाटाबेस धीरे से त्वरित फिल्टरों के साथ भरता है, टूटे लिंक अभिलेख मिलता है, और उपयोगकर्ता कम से कम 404 अब देखने के लिए देखते हैं.

मुख्य सिद्धांत:

- **ब्लॉक निवेदन नहीं करें** - महँगी आपरेशनों के लिए पृष्ठभूमि प्रक्रिया इस्तेमाल करें
- **उपयोक्ता से सीखें** - उनके क्लिक मूल्यवान संकेत हैं
- **स्वचालित आकार युक्त हों** - केवल जब आप यकीन कर रहे हैं
- **समय- अ-रूप अभिलेख** - सामग्री लिखी गई थी जब से अभिलेख मिला.
- **अनुग्रहपूर्ण अपमान** -अगर सेवाएँ उपलब्ध नहीं हैं, सिर्फ सामान्य रूप से जारी रखें

यह सिद्ध नहीं है - कुछ कड़ियों को सच में हमेशा के लिए चला गया है, और इमानिक खोज हमेशा सही जगह नहीं पाती। लेकिन यह एक लानत दृष्टि है हजारों टूटे कड़ियों के बारे में झूठ बोलने के लिए।

## जल्द ही आ रहा है: सेलिक खोज

इस सप्ताह (W/CHEHEX नवंबर 2025) मैं अपनी खोज श्रृंखला को प्रकाशित किया जाएगा जो और अधिक विस्तार में चला जाता है कि कैसे सदिश-आधारित खोज अंत के अंतर्गत काम करता है। यह श्रृंखला पीढ़ी, Qavid वेक्टर भंडारण, और कैसे यह सभी संबंधों को नियंत्रित करता है इस 404 सिस्टम में। यदि आप के बारे में सोच रहे हैं, तो आप के बारे में सोच रहे हैं कि कैसे के बारे में सोच रहे हैं। `ISemanticSearchService.SearchAsync` वास्तव में "विष्ट" सामग्री, कि आप देखना चाहते हैं कि जहां है.

पूरा स्रोत भंडार में है यदि आप किसी का उपनाम अपने परियोजना के लिए करना चाहते हैं.