Azure Function’da CAPTCHA Nasıl Çözülür (C# Isolated)

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

Azure Functions üzerinde bir captcha çözümü, kodunuzun size hiç göstermediği bir nedenle bozulur. HTTP ile tetiklenen bir function, hangi zaman aşımını ayarlarsanız ayarlayın bir isteğe yanıt vermek için 230 saniyeye sahiptir; çünkü bu sınır, platformun önündeki yük dengeleyiciden gelir. CapSkip istemcisindeki reCAPTCHA yoklama zaman aşımı ise varsayılan olarak 300 saniyedir. Yani yavaş bir çözümü function’ınız değil Azure keser ve logda yalnızca öylece sonlanmış bir istek görünür. Doğru yaklaşım, CAPTCHA’yı HTTP isteğinin üzerinde çözmeyi tamamen bırakmaktır. Doğru kurmanız gereken diğer şey loopback, çünkü bir function app sizin makinelerinizde değil Azure’un makinelerinde çalışır.

Neye ihtiyacınız var

  • Bir .NET isolated worker function app. In-process model için destek 10 Kasım 2026’da sona eriyor, dolayısıyla üzerine inşa edilecek model isolated worker’dır.
  • Bir Windows makinesinde, Server modunda ve function app’in ulaşabileceği bir adreste çalışan CapSkip.
  • Bir depolama hesabı, çünkü aşağıdaki desen çözümü kuyrukla tetiklenen bir function’a taşır.
  • Sabit kodlanmak yerine kuyruk mesajıyla gelen sitekey ve sayfa URL’si; böylece tek function her formu karşılar.
# dotnet add package CapSkip
dotnet add package CapSkip
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Storage.Queues

1. Adım: bir function app içinde loopback, o function app’in kendisidir

Her şeyden önce bunu netleştirmekte fayda var, çünkü geri kalanın çalışıp çalışmayacağını bu belirler. Function’ınız Azure’un sağladığı bir instance üzerinde çalışır, dolayısıyla içerideki 127.0.0.1 o instance’ın kendisidir. Orada 8080 portunu dinleyen hiçbir şey yoktur ve hata, dağıtımdan sonraki ilk çözümde bir NetworkException olarak gelir. Oysa aynı kod yerel araçlarla kusursuz çalışıyordu.

İki bağlantı modu vardır. Local, 127.0.0.1 adresine bağlanır ve yalnızca o cihaza yanıt verir; otomasyonunuz ile çözücü aynı makineyi paylaştığında doğru seçim budur. Server ise ağ adresinize veya genel IP adresinize bağlanır; böylece başka bir makine, bir VPS ya da barındırılan bir platform aynı Windows makinesine API üzerinden ulaşabilir. Server modu yalnızca çözücünün hangi adresi dinlediğini değiştirir. Donanım yine sizin donanımınızdır ve çözüm başına ücretlendirme yine yoktur. Her iki mod da şurada yer alır: bağlantı ayarları.

Function’ı nasıl çalıştırdığınızHangi bağlantı modu
CapSkip makinesinde, yerel araçlarlaLocal modu. 127.0.0.1 gerçekten doğru
Dağıtılmış, çözücü Azure’un yönlendirebileceği bir ağdaO özel adresle Server modu
Dağıtılmış, çözücüye internet üzerinden erişiliyorStatik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu

Çözücü, Azure’un yönlendirebileceği bir ağda bulunduğunda bilmeye değer iki Azure özelliği var. Giden trafik için sanal ağ entegrasyonu Flex Consumption, Premium ve Dedicated planlarında mevcuttur; eski Consumption planında ise hiç yoktur. Kendi ağınızda kalan bir servise ulaşmak için tasarlanan Hybrid Connections ise Windows üzerinde çalışan uygulamalar için Premium ve Dedicated planlarında mevcuttur. Her iki durumda da çözücü sizin donanımınızda kalır; değişen yalnızca rotadır.

Adresi kaynak kodunda değil bir uygulama ayarında tutun, çünkü yerel araçlar ile dağıtılmış uygulama farklı değerler ister. İstemci hiçbir ortam değişkenini kendi başına okumaz; bu yüzden, aşağıdaki fonksiyonun yaptığı gibi, CAPSKIP_HOST değerini kendi kodunuzda okuyup istemciye iletin.

// 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. Adım: 230 saniyelik duvar ve bunun neden sizin zaman aşımınız olmadığı

Bir öğleden sonrayı yiyen kısım budur, çünkü portalda görebildiğiniz her sayı, isteği asıl öldüren sayıdan büyüktür.

Microsoft bunu açıkça belgeliyor: function app zaman aşımı ayarından bağımsız olarak, HTTP ile tetiklenen bir function’ın bir isteğe yanıt vermek için harcayabileceği en fazla süre 230 saniyedir ve bu sınır, Azure Load Balancer’daki varsayılan boşta kalma zaman aşımından kaynaklanır. Bu süreyi host.json’dan, bir uygulama ayarından ya da plan değiştirerek yükseltemezsiniz.

Şimdi istemcinin kendi sayılarını bunun yanına koyun. reCAPTCHA, Turnstile ve GeeTest yoklama zaman aşımı varsayılan olarak 300 saniye, görüntü zaman aşımı ise 120 saniyedir. Yani bir görüntü çözümü duvarın içine rahatça sığar, bir reCAPTCHA çözümünün ise duvarı 70 saniye aşmasına izin verilmiştir. Çözümlerin çoğu bu sayıların ikisinden de çok önce biter; kodun üretime çıkıp ancak yavaş uçtaki çözümlerde patlamasının sebebi tam olarak budur.

LimitDeğerDeğiştirebilir misiniz?
HTTP yanıtı, her plan230 saniyeHayır
İstemci reCAPTCHA yoklama zaman aşımı300 saniyeEvet, yapıcı metotta
İstemci görüntü yoklama zaman aşımı120 saniyeEvet, yapıcı metotta

reCAPTCHA yoklama zaman aşımını 230 saniyenin altına çekmek yine de işe yarar, çünkü önce pes eden bir istemci, ortadan kaybolan bir istek yerine loglayabileceğiniz bir CapSkip.TimeoutException üretir. Yine de gerçek çözüm bu değil. Gerçek çözüm, Azure dokümantasyonunun verdiği çözümdür: Durable Functions asenkron desenini kullanın ya da asıl işi erteleyip hemen bir yanıt döndürün. Pratikte bu, HTTP tetikleyicisinin işi kabul etmesi, bir mesaj yazması ve anında dönmesi demektir.

[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. Adım: function app zaman aşımı ayrı bir sınırdır

Çözüm HTTP isteğinden çıktıktan sonra önem taşıyan zaman aşımı, host.json’daki olandır ve plana göre değişir. Varsayılanlar, eski Consumption planı dışında her yerde cömerttir; yavaş bir reCAPTCHA çözümünün gerçekten yer sıkıntısına düşebileceği tek plan odur.

Barındırma planıVarsayılan zaman aşımıMaksimum zaman aşımı
Flex Consumption planı30 dakikaZorunlu bir üst sınır yok
Premium planı30 dakikaZorunlu bir üst sınır yok
Dedicated planı30 dakikaAlways On ile birlikte zorunlu bir üst sınır yok
Consumption planı, eski5 dakika10 dakika

Beş dakika tam olarak 300 saniyedir; yani eski plan, varsayılan hâliyle bir reCAPTCHA zaman aşımını karşılamaz: function, istemcinin tam da pes edeceği anda ölür. Hâlâ o plandaysanız bu değeri yükseltin ve istemcinin kendi zaman aşımını, belirlediğiniz değerin altında tutun.

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

4. Adım: bir kuyruk mesajı kaç kez yeniden çözülür

Çözümü bir kuyruğa taşımak size alan kazandırır ama kendi yeniden çalıştırma davranışını da getirir; dokunmadan bırakırsanız bu davranış size gerçek anlamda zaman kaybettirir.

Kuyrukla tetiklenen bir function başarısız olduğunda, Azure Functions o mesaj için function’ı, ilk deneme dahil en fazla beş kez çalıştırır. Beşi de başarısız olursa çalışma zamanı mesajı, orijinalin adına poison eki gelmiş bir kuyruğa yazar. Hata asla başarıya ulaşmayacak türdense, örneğin sayfaya ait olmayan bir sitekey ise, bu tek bir CAPTCHA için beş çözüm demektir.

Durum göründüğünden de sıkışıktır. host.json’daki visibilityTimeout varsayılan olarak sıfırdır; yani başarısız olan mesaj anında yeniden görünür ve o beş deneme saniyeler içinde art arda tükenebilir. Bu değeri, geçici bir sorunun düzelmesine zaman tanıyacak bir süreye ayarlayın ve son denemesindeki bir mesajı farklı ele alabilmek için function içinde dequeue sayısını okuyun.

İşin diğer yarısı eşzamanlılıktır. Tetikleyici varsayılan olarak 16 mesajlık bir batch alır, ardından işlemde kalan mesaj sayısı 8’e düşer düşmez bir 16 tane daha çeker. Yeni batch başlarken o 8 mesaj hâlâ çalışmaktadır; yani tek bir instance, tek bir function için aynı anda 24 çözüm yürütebilir. Uygulama yatay ölçeklendiğinde bu sayı instance sayısıyla çarpılır. Çözüm başına ücretlendirilen bir serviste bunu, bir bakiyeyi korumak için sınırlardınız. Burada soru tek bir Windows makinesinin kapasitesiyle ilgili; yine de instance başına 24 eşzamanlı çözüm, miras alınacak değil bilerek verilecek bir karardır.

Aşağıdaki örnek, bilerek her iki varsayılanın da altında kalır: beş yerine üç deneme ve on altı yerine sekiz mesajlık bir batch.

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

Tam çalışan örnek

Kuyrukla tetiklenen yarısı: çözüm ve gönderim aynı çağrının içinde.

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

Bu çağrı reCAPTCHA v2 içindir. Diğer türler de aynı şekle sahiptir: invisible ya da enterprise değeri 1 olan bir options sözlüğü geçirin, bir action ile birlikte version değerini v3 yapın ya da bunun yerine TurnstileAsync veya GeetestAsync çağırın. Tüm yüzeyi şurada bulabilirsiniz: C# CAPTCHA çözücü sayfası.

Bilinmeye değer tek istisna, challenge sayfasındaki Turnstile’dır; çünkü sayfadan iki ek değere, ayrıca çözücünün kullandığı user agent bilgisine ihtiyaç duyar. Bu tür şurada anlatılıyor: kendine ait bir rehber.

Parametre hatasını yeniden fırlatmak yerine yutmak bilinçli bir tercihtir. Beş denemelik döngüyü başlatan şey fırlatılan bir istisnadır ve sayfayla eşleşmeyen bir sitekey konusunda yeniden denemenin yapabileceği hiçbir şey yoktur.

Sık görülen hatalar ve anlamları

GördüğünüzNedenDüzeltme
HTTP isteği yaklaşık dört dakikada hatasız sonlanıyorSizin zaman aşımınız değil, 230 saniyelik yük dengeleyici sınırıHemen dönün ve çözümü bir kuyruk tetikleyicisinde yapın
Yerel araçlarla çalışıyor, dağıtımdan sonra NetworkExceptionBir function app içinde loopback, Azure instance’ının kendisidirServer modu ve uygulama ayarlarında CAPSKIP_HOST’u ayarlayın
Birbirinin aynı beş başarısızlık, ardından poison kuyruğunda bir mesajFunction’dan yeniden denenemez bir hata fırlatıldıBunun yerine CapSkip.ValidationException’ı yakalayıp loglayın
Beş deneme bir dakikadan kısa sürede tükendiKuyruk visibilityTimeout değeri varsayılan olarak sıfırdırYeniden denemelerin arası açılsın diye bir visibilityTimeout ayarlayın
Derleme, belirsiz bir TimeoutException yüzünden başarısız oluyorO kısa adı hem CapSkip hem System tanımlıyorAdı tam nitelendirin ya da temel CapSkipError’ı yakalayın
Bir ApiException içinde ERROR_WRONG_USER_KEYDağıtılmış uygulamada CAPSKIP_API_KEY tanımlı değilUygulama ayarlarına ekleyin, sonra uygulamayı yeniden başlatın
Elle yazılmış bir yoklamadan CAPCHA_NOT_READYSonuç, tamamlanmadan önce okunduYoklamayı istemciye bırakın. Kendiliğinden geri çekilir

Son yanıt tam da göründüğü gibi yazılır; eksik harf bizim tarafımızdaki bir yazım hatası değildir, çünkü API gerçekten de onu bu şekilde döndürür. Tamamı şurada açıklanıyor: CAPCHA_NOT_READY rehberi.

FAQ

Azure’daki bir function app kendi ağımdaki bir çözücüye ulaşabilir mi?

Evet. CapSkip’i bağlantı ayarlarından Server moduna alın ki loopback yerine bir ağ adresini dinlesin, sonra uygulama ayarlarında CAPSKIP_HOST’u o adrese yöneltin. Özel bir rota için sanal ağ entegrasyonu Flex Consumption, Premium ve Dedicated planlarında, Hybrid Connections ise Windows üzerinde çalışan uygulamalar için Premium ve Dedicated planlarında mevcuttur. Bunun yerine internet üzerinden gidecekseniz sabit bir genel IP kullanın ve yalnızca beklediğiniz adreslere izin veren bir güvenlik duvarı kuralı tanımlayın. Bunların hiçbirinde çözücünün kendisi sizin donanımınızdan ayrılmaz.

HTTP ile tetiklenen çözümüm neden yaklaşık dört dakikada ölüyor?

Çünkü HTTP ile tetiklenen bir function’ın yanıt verme tavanı 230 saniyedir ve bu tavan Functions’tan değil yük dengeleyiciden gelir. Hiçbir plan, host.json değeri ya da uygulama ayarı bu sınırı yükseltmez. Yanıtı aynı istekte almak istiyorsanız çözümün bu pencerenin epey içinde bitmesi gerekir; yani istemcinin reCAPTCHA yoklama zaman aşımını varsayılan 300’den düşürmeniz ve yavaş çözümlerin başarısız olacağını kabul etmeniz gerekir. Daha iyi yanıt, işi kuyrukla tetiklenen bir function’a devredip hemen dönmektir.

Bunun için Durable Functions gerekli mi?

Yalnızca çağıran tarafın sonucu yoklaması gerekiyorsa. Durable Functions size, hazır bir durum uç noktasıyla birlikte asenkron HTTP desenini verir; sonucu bir tarayıcı ya da iş ortağı sistemi bekliyorsa buna değer. Çözüm kendi hattınızın tek bir adımıysa bir depolama kuyruğu daha basittir ve 230 saniyelik sınırdan aynı şekilde kurtarır. Her iki durumda da çözümü ve tokeni tüketen şeyi aynı çağrının içinde tutun, çünkü token hızla sona erer ve bir orkestrasyon sınırı, bu yarışı kaybetmek için ideal bir yerdir.

catch bloğum neden derlenmiyor?

Çünkü istemci, kısa adları System içinde de bulunan bir TimeoutException ve bir ValidationException tanımlar ve bir function dosyasında neredeyse her zaman iki ad alanı da kapsam içindedir. CapSkip.TimeoutException ve CapSkip.ValidationException yazın ya da temel CapSkipError’ı yakalayıp içeride dallanın. Diğer ikisinde, NetworkException ve ApiException’da çakışma yoktur; onları kısa adlarıyla yakalayabilirsiniz.

Kısa özet

Bir HTTP tetikleyicisinde çözüm yapmayın. functionTimeout ne derse desin isteği 230 saniyede yük dengeleyici keser ve istemcinin reCAPTCHA zaman aşımı varsayılan olarak bundan uzundur. İşi kabul edin, bir kuyruk mesajı yazın, hemen dönün ve çözümü kuyrukla tetiklenen function içinde yapın. Kuyruk yeniden denemelerinin arasını açın ve parametre hatalarını yakalayın; yoksa hatalı tek bir sitekey, hiçbir yeniden denemenin düzeltemeyeceği bir sorun uğruna beş denemeyi harcar. CapSkip’i Server moduna alın ve adresini uygulama ayarlarına koyun, çünkü bir function app içindeki loopback sizin değil Azure’un instance’ıdır.

Bir batch boyutu seçmeden önce tartılacak tek bir şey var: captcha atlatma CapSkip ile zaten sahip olduğunuz bir makinede çalışır, dolayısıyla eşzamanlı çözüm tavanını ayın faturasının izin verdiği değil, o makinenin taşıyabildiği yük belirler.