Azure Function (C# Isolated) में कैप्चा कैसे हल करें

azure functions captcha - How to Solve CAPTCHAs in an Azure Function (C# Isolated)

Azure Functions में कैप्चा हल करने का काम एक ऐसी वजह से टूटता है जो आपका कोड आपको कभी नहीं दिखाता। आप timeout चाहे जो भी सेट करें, HTTP-triggered function के पास किसी request का जवाब देने के लिए 230 सेकंड ही होते हैं, क्योंकि यह सीमा प्लेटफ़ॉर्म के आगे लगे load balancer से आती है। CapSkip client में reCAPTCHA का polling timeout डिफ़ॉल्ट रूप से 300 सेकंड है। यानी धीमे हल को आपका function नहीं, Azure काटता है, और log में बस एक ऐसी request दिखती है जो यूँ ही ख़त्म हो गई। इलाज यह है कि HTTP request पर हल करना बिलकुल बंद कर दें। दूसरी चीज़ जो ठीक करनी है वह है loopback, क्योंकि function app आपकी नहीं, Azure की मशीनों पर चलता है।

आपको क्या चाहिए

  • एक .NET isolated worker function app। in-process मॉडल का सपोर्ट 10 नवंबर 2026 को ख़त्म हो रहा है, इसलिए आगे का काम isolated worker पर ही बनाना चाहिए।
  • किसी Windows मशीन पर Server mode में चलता हुआ CapSkip, ऐसे पते पर जहाँ function app पहुँच सके।
  • एक storage account, क्योंकि नीचे दिया गया तरीक़ा हल को queue-triggered function पर ले जाता है।
  • sitekey और पेज URL hardcode करने के बजाय queue message के साथ आएँ, ताकि एक ही function हर फ़ॉर्म के काम आए।
# dotnet add package CapSkip
dotnet add package CapSkip
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues

चरण 1: function app में loopback वही function app है

यह सबसे पहले तय कर लेना चाहिए, क्योंकि इसी से तय होता है कि बाक़ी सब चलेगा या नहीं। आपका function उस instance पर चलता है जो Azure देता है, इसलिए उसके अंदर 127.0.0.1 वही instance है। वहाँ port 8080 पर कोई नहीं सुन रहा, और गड़बड़ी deploy के बाद पहले ही हल पर NetworkException बनकर आती है, जबकि वही कोड लोकल टूलिंग के साथ बिलकुल ठीक चलता था।

कनेक्शन के दो मोड हैं। Local mode 127.0.0.1 से बँधता है और सिर्फ़ उसी डिवाइस को जवाब देता है, जो तब सही है जब आपका ऑटोमेशन और सॉल्वर एक ही मशीन पर हों। Server mode आपके नेटवर्क पते या पब्लिक IP से बँधता है, ताकि कोई दूसरी मशीन, कोई VPS या कोई होस्टेड प्लेटफ़ॉर्म उसी Windows मशीन तक API के ज़रिए पहुँच सके। Server mode सिर्फ़ यह बदलता है कि सॉल्वर किस पते पर सुनता है। हार्डवेयर अब भी आपका ही है और इस पर अब भी प्रति-हल कोई शुल्क नहीं लगता। ये दोनों मोड यहाँ मिलते हैं: कनेक्शन सेटिंग्स.

आप function को कैसे चला रहे हैंकौन-सा कनेक्शन मोड
लोकल टूलिंग, CapSkip मशीन परLocal mode। 127.0.0.1 सचमुच सही है
डिप्लॉय किया हुआ, सॉल्वर ऐसे नेटवर्क पर जहाँ Azure रूट कर सकेउस प्राइवेट पते के साथ Server mode
डिप्लॉय किया हुआ, सॉल्वर तक पहुँच इंटरनेट के रास्तेस्टैटिक पब्लिक IP और एक फ़ायरवॉल नियम के साथ Server mode

जब सॉल्वर ऐसे नेटवर्क पर हो जहाँ तक Azure रास्ता बना सके, तब Azure की दो सुविधाएँ जानने लायक़ हैं। outbound ट्रैफ़िक के लिए virtual network integration, Flex Consumption, Premium और Dedicated प्लान पर उपलब्ध है, और पुराने Consumption प्लान पर बिलकुल उपलब्ध नहीं है। Hybrid Connections, जो ऐसी सेवा तक पहुँचने के लिए ही बनी हैं जो आपके अपने नेटवर्क पर रहती है, Windows पर चलने वाले ऐप्स के लिए Premium और Dedicated प्लान पर उपलब्ध हैं। दोनों ही सूरतों में सॉल्वर आपके हार्डवेयर पर ही रहता है; सिर्फ़ रास्ता बदलता है।

पते को सोर्स कोड के बजाय किसी application setting में रखें, क्योंकि लोकल टूलिंग और डिप्लॉय किए गए ऐप को अलग-अलग मान चाहिए। client अपने आप कोई भी environment variable नहीं पढ़ता, इसलिए CAPSKIP_HOST को अपने कोड में पढ़ें और client को पास करें, जैसा नीचे वाला function करता है।

// dotnet add package CapSkip
using CapSkip;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);
builder.ConfigureFunctionsWebApplication();

// One client for the app. The host is an application
// setting, so local and deployed can differ.
builder.Services.AddSingleton(new CapSkipClient(
    host: Environment.GetEnvironmentVariable("CAPSKIP_HOST") ?? "127.0.0.1",
    port: 8080));

builder.Build().Run();

चरण 2: 230 सेकंड की दीवार, और यह आपका timeout क्यों नहीं है

यही वह हिस्सा है जो पूरी दोपहर बरबाद करता है, क्योंकि पोर्टल में आपको जो भी संख्या दिखती है वह उस संख्या से बड़ी है जो असल में request को मारती है।

Microsoft इसे साफ़-साफ़ लिखता है: function app की timeout सेटिंग चाहे कुछ भी हो, किसी request का जवाब देने में HTTP-triggered function ज़्यादा से ज़्यादा 230 सेकंड ले सकता है, और यह सीमा Azure Load Balancer के डिफ़ॉल्ट idle timeout की वजह से है। इसे आप न host.json से बढ़ा सकते हैं, न किसी application setting से, और न प्लान बदलकर।

अब इसके बग़ल में client के अपने आँकड़े रखिए। reCAPTCHA, Turnstile और GeeTest का polling timeout डिफ़ॉल्ट रूप से 300 सेकंड है, और इमेज का timeout 120 सेकंड। यानी इमेज वाला हल उस दीवार के अंदर आराम से समा जाता है, और reCAPTCHA वाले हल को उससे 70 सेकंड आगे तक चलने की छूट है। ज़्यादातर हल इन दोनों संख्याओं से बहुत पहले पूरे हो जाते हैं, और ठीक इसीलिए यह प्रोडक्शन तक पहुँच जाता है और फिर धीमे मामलों में फ़ेल होता है।

Limitमानक्या आप इसे बदल सकते हैं?
HTTP response, कोई भी प्लान230 सेकंडनहीं
client का reCAPTCHA polling timeout300 सेकंडहाँ, constructor पर
client का इमेज polling timeout120 सेकंडहाँ, constructor पर

reCAPTCHA का polling timeout 230 सेकंड से नीचे कर देना वैसे भी ठीक रहता है, क्योंकि जो client पहले हार मान लेता है वह एक ऐसा CapSkip.TimeoutException देता है जिसे आप लॉग कर सकते हैं, बजाय ऐसी request के जो चुपचाप ग़ायब हो जाए। हालाँकि यह असली इलाज नहीं है। असली इलाज वही है जो Azure के डॉक्स बताते हैं: Durable Functions वाला async pattern इस्तेमाल करें, या असली काम को टाल दें और तुरंत जवाब लौटा दें। व्यवहार में इसका मतलब है कि HTTP trigger job स्वीकार करता है, एक message लिखता है, और फ़ौरन लौट आता है।

[Function(nameof(EnqueueSolve))]
[QueueOutput("captcha-jobs")]
public SolveRequest EnqueueSolve(
    [HttpTrigger(AuthorizationLevel.Function, "post")] SolveRequest req)
{
    // Returns in milliseconds. The solve happens on the
    // queue-triggered function, off the HTTP request.
    return req;
}

चरण 3: function app का timeout एक अलग सीमा है

जब हल HTTP request से हट जाता है, तो जो timeout मायने रखता है वह host.json वाला है, और वह प्लान के हिसाब से बदलता है। पुराने Consumption प्लान को छोड़कर हर जगह डिफ़ॉल्ट उदार हैं, और यही वह अकेला प्लान है जहाँ धीमा reCAPTCHA हल सचमुच जगह से बाहर जा सकता है।

होस्टिंग प्लानडिफ़ॉल्ट timeoutअधिकतम timeout
Flex Consumption प्लान30 मिनटकोई लागू की गई अधिकतम सीमा नहीं
Premium प्लान30 मिनटकोई लागू की गई अधिकतम सीमा नहीं
Dedicated प्लान30 मिनटकोई लागू की गई अधिकतम सीमा नहीं, Always On के साथ
Consumption प्लान, पुराना5 मिनट10 मिनट

पाँच मिनट यानी ठीक 300 सेकंड, इसलिए पुराना प्लान डिफ़ॉल्ट पर चल रहे reCAPTCHA timeout को पूरा नहीं कर पाता: function ठीक उसी पल दम तोड़ देता है जिस पल client ने हार मान ली होती। अगर आप अब भी उसी प्लान पर हैं तो इसे बढ़ाएँ, और client का अपना timeout उससे नीचे रखें जो भी आप सेट करें।

{
  "version": "2.0",
  "functionTimeout": "00:10:00"
}

चरण 4: एक queue message कितनी बार दोबारा हल होता है

हल को queue पर ले जाने से आपको जगह मिलती है, और साथ में उसका अपना दोबारा चलने वाला बर्ताव भी आता है, जो यूँ ही छोड़ देने पर असली वक़्त बरबाद कराता है।

जब कोई queue-triggered function फ़ेल होता है, तो Azure Functions उस message के लिए function को ज़्यादा से ज़्यादा पाँच बार चलाता है, पहली कोशिश समेत। अगर पाँचों फ़ेल हो जाएँ, तो runtime उस message को ऐसी queue में लिख देता है जिसका नाम मूल queue के नाम पर होता है और आगे poison लगा होता है। यानी अगर गड़बड़ी ऐसी है जो कभी सफल नहीं होगी, जैसे कोई sitekey जो उस पेज की है ही नहीं, तो एक कैप्चा के लिए पाँच हल।

मामला दिखने से ज़्यादा कसा हुआ है। host.json में visibility timeout डिफ़ॉल्ट रूप से शून्य है, यानी फ़ेल हुआ message तुरंत फिर दिखने लगता है, इसलिए वे पाँचों कोशिशें कुछ ही सेकंड में एक के बाद एक हो सकती हैं। इसे ऐसे मान पर सेट करें जो किसी अस्थायी दिक़्क़त को ठीक होने का समय दे, और function में dequeue count पढ़ें ताकि अपनी आख़िरी कोशिश पर पहुँचे message को अलग तरीक़े से सँभाला जा सके।

दूसरा आधा हिस्सा concurrency है। डिफ़ॉल्ट रूप से trigger 16 messages का एक batch लेता है, फिर जैसे ही अब भी चल रहे messages की संख्या घटकर 8 रह जाती है, वह अगले 16 ले आता है। वे 8 नया batch शुरू होते वक़्त भी चलते रहते हैं, इसलिए एक ही instance पर एक function के लिए 24 हल एक साथ चल सकते हैं। जब ऐप scale out होता है, तो यह संख्या instances की गिनती से गुणा हो जाती है। प्रति-हल शुल्क वाली सेवा पर आप बैलेंस बचाने के लिए इस पर सीमा लगाते। यहाँ यह एक Windows मशीन की क्षमता का सवाल है, फिर भी प्रति instance 24 एक साथ चलते हल ऐसा फ़ैसला है जो विरासत में लेने के बजाय जानबूझकर लेना चाहिए।

नीचे दिया गया नमूना जानबूझकर दोनों डिफ़ॉल्ट से नीचे रहता है: पाँच के बजाय तीन attempts, और सोलह के बजाय आठ का batch।

{
  "version": "2.0",
  "extensions": {
    "queues": {
      "batchSize": 8,
      "newBatchThreshold": 4,
      "visibilityTimeout": "00:00:30",
      "maxDequeueCount": 3
    }
  }
}

पूरा चलने वाला उदाहरण

queue-triggered हिस्सा, जिसमें हल और submission एक ही invocation में हैं।

// dotnet add package CapSkip
using CapSkip;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

public class SolveCaptcha(CapSkipClient solver, ILogger<SolveCaptcha> log)
{
    [Function(nameof(SolveCaptcha))]
    public async Task Run([QueueTrigger("captcha-jobs")] SolveRequest job)
    {
        try
        {
            // Solve and submit together. The token is short lived.
            var result = await solver.RecaptchaAsync(job.Sitekey, job.PageUrl);
            await SubmitFormAsync(job.PageUrl, result.Code);
        }
        catch (CapSkip.ValidationException ex)
        {
            // A bad sitekey fails identically on all five tries.
            log.LogError("Not retryable: {Message}", ex.Message);
        }
    }
}

वह कॉल reCAPTCHA v2 का है। बाक़ी प्रकार भी इसी शक्ल के हैं: एक options dictionary पास करें जिसमें invisible या enterprise 1 पर सेट हो, या version किसी action के साथ v3 पर सेट हो, या इसके बजाय TurnstileAsync या GeetestAsync कॉल करें। पूरा ब्योरा यहाँ है: C# कैप्चा सॉल्वर पेज.

चैलेंज-पेज वाला Turnstile इकलौता अपवाद है जिसे जान लेना चाहिए, क्योंकि उसके लिए पेज से दो अतिरिक्त मान चाहिए होते हैं, साथ ही वह user agent भी जो सॉल्वर ने इस्तेमाल किया था। उसके लिए मौजूद है: अपनी एक अलग गाइड.

parameter वाली ग़लती को दोबारा फेंकने के बजाय निगल जाना जानबूझकर किया गया है। फेंका गया exception ही पाँच कोशिशों वाला चक्र शुरू करता है, और जो sitekey पेज से मेल ही नहीं खाती उसके बारे में कोई retry कुछ नहीं कर सकता।

आम errors और उनका मतलब

आप जो देखते हैंकारणफिक्स
HTTP request क़रीब चार मिनट पर बिना किसी error के ख़त्म हो जाती है230 सेकंड वाली load balancer सीमा, आपका timeout नहींतुरंत जवाब लौटाएँ और हल queue trigger पर करें
लोकल टूलिंग के साथ चलता है, डिप्लॉय होते ही NetworkExceptionfunction app में loopback वही Azure instance हैServer mode, और application settings में CAPSKIP_HOST सेट करें
पाँच एक जैसी विफलताएँ, फिर poison queue में एक messagefunction से ऐसी error फेंकी गई जिस पर retry बेकार हैइसके बजाय CapSkip.ValidationException पकड़ें और उसे लॉग करें
पाँच कोशिशें एक मिनट से भी कम में ख़त्मqueue का visibility timeout डिफ़ॉल्ट रूप से शून्य हैvisibility timeout सेट करें ताकि retries के बीच फ़ासला रहे
build किसी अस्पष्ट TimeoutException पर फ़ेल हो जाता हैCapSkip और System, दोनों उस छोटे नाम को परिभाषित करते हैंइसे पूरे नाम से लिखें, या बेस CapSkipError पकड़ें
किसी ApiException के अंदर ERROR_WRONG_USER_KEYडिप्लॉय किए गए ऐप में CAPSKIP_API_KEY सेट नहीं हैइसे application settings में जोड़ें, फिर ऐप restart करें
हाथ से लिखे पोल से CAPCHA_NOT_READYनतीजा पूरा होने से पहले ही पढ़ लिया गयाclient को poll करने दें। वह ख़ुद ही धीमा होता जाता है

यह आख़िरी रिस्पॉन्स वैसे ही लिखा जाता है जैसा दिखता है, और उसमें गायब अक्षर हमारी तरफ़ की गलती नहीं है, क्योंकि API सचमुच इसी वर्तनी में जवाब लौटाता है। इसकी पूरी व्याख्या यहाँ है: CAPCHA_NOT_READY गाइड.

FAQ

क्या Azure में चल रहा function app मेरे अपने नेटवर्क पर मौजूद सॉल्वर तक पहुँच सकता है?

हाँ। कनेक्शन सेटिंग्स में CapSkip को Server mode पर कर दें ताकि वह loopback के बजाय किसी नेटवर्क पते पर सुने, फिर application settings में CAPSKIP_HOST को उसी पते पर लगा दें। प्राइवेट रास्ते के लिए, virtual network integration, Flex Consumption, Premium और Dedicated प्लान पर उपलब्ध है, और Hybrid Connections, Windows पर चलने वाले ऐप्स के लिए Premium और Dedicated पर। अगर आप इसके बजाय इंटरनेट के रास्ते जाते हैं, तो static पब्लिक IP इस्तेमाल करें और साथ में ऐसा firewall नियम रखें जो सिर्फ़ उन्हीं पतों को अनुमति दे जिनकी आप उम्मीद करते हैं। इनमें से किसी भी सूरत में सॉल्वर ख़ुद आपके हार्डवेयर से बाहर नहीं जाता।

मेरा HTTP-triggered हल क़रीब चार मिनट पर क्यों मर जाता है?

क्योंकि किसी HTTP-triggered function को जवाब देने के लिए ज़्यादा से ज़्यादा 230 सेकंड मिलते हैं, और यह सीमा Functions से नहीं बल्कि load balancer से आती है। न कोई प्लान, न host.json का कोई मान, न कोई application setting इसे बढ़ा सकती है। अगर आपको जवाब उसी request पर चाहिए, तो हल को उस खिड़की के अच्छे-ख़ासे अंदर पूरा होना होगा, यानी client का reCAPTCHA polling timeout उसके डिफ़ॉल्ट 300 से घटाना होगा और यह मानकर चलना होगा कि धीमे हल फ़ेल होंगे। बेहतर जवाब यह है कि काम किसी queue-triggered function को सौंप दें और फ़ौरन लौट आएँ।

क्या इसके लिए मुझे Durable Functions चाहिए?

सिर्फ़ तब, जब कॉल करने वाले को नतीजे के लिए poll करना पड़े। Durable Functions आपको async HTTP pattern देता है जिसमें status endpoint पहले से बना होता है, और यह तब क़ीमती है जब कोई ब्राउज़र या कोई पार्टनर सिस्टम नतीजे का इंतज़ार कर रहा हो। अगर हल आपकी अपनी पाइपलाइन का एक क़दम भर है, तो storage queue ज़्यादा आसान है और 230 सेकंड की सीमा से वही छुटकारा देती है। दोनों ही हाल में हल और token इस्तेमाल करने वाले काम को एक ही invocation में रखें, क्योंकि token जल्दी एक्सपायर होता है और orchestration की सीमा वह जगह है जहाँ यह दौड़ आसानी से हारी जा सकती है।

मेरा catch block compile क्यों नहीं होता?

क्योंकि client एक TimeoutException और एक ValidationException परिभाषित करता है जिनके छोटे नाम System में भी मौजूद हैं, और किसी function फ़ाइल में लगभग हमेशा दोनों namespaces दायरे में होते हैं। CapSkip.TimeoutException और CapSkip.ValidationException पूरे नाम से लिखें, या बेस CapSkipError पकड़ें और उसके अंदर शाखाएँ बनाएँ। बाक़ी दो, NetworkException और ApiException, किसी से नहीं टकरातीं और उन्हें छोटे नाम से ही पकड़ा जा सकता है।

संक्षेप में

HTTP trigger पर हल न करें। functionTimeout चाहे जो कहे, load balancer request को 230 सेकंड पर काट देता है, और client का reCAPTCHA timeout डिफ़ॉल्ट रूप से उससे लंबा है। job स्वीकार करें, एक queue message लिखें, तुरंत लौट आएँ, और हल queue-triggered function में करें। queue की retries के बीच फ़ासला रखें और parameter वाली errors पकड़ें, वरना एक ग़लत sitekey ऐसी दिक़्क़त पर पाँच attempts फूँक देगी जिसे कोई retry ठीक नहीं कर सकती। CapSkip को Server mode पर कर दें और उसका पता application settings में डालें, क्योंकि function app के अंदर loopback आपका नहीं, Azure का instance है।

batch size चुनने से पहले एक बात तौलने लायक़ है: कैप्चा बायपास CapSkip के साथ उस मशीन पर चलता है जो पहले से आपकी है, इसलिए एक साथ चलने वाले हल की सीमा वह है जितना वह मशीन झेल सके, न कि जितना महीने का बिल इजाज़त दे।