Localhost'u İnternete Açmak ve Mock Server'lar: Dev Tunnels, Cloudflare Tunnel, ngrok, Prism ve WireMock.Net
Ödeme sağlayıcısının webhook'unu localhost'taki Bookstore.Api'ye almak için bir tünel gerekir, ama API'yi koruyan şey tünel değil imzadır. HMAC imzası ve zaman toleransıyla çalışan webhook endpoint'ini yerelde gerçek çıktıyla deniyor, Dev Tunnels, Cloudflare quick tunnel ve ngrok'u erişim ayarlarıyla karşılaştırıyor, Prism'le OpenAPI'den 401 ve 422 dönen bir mock, WireMock.Net'le senaryo ve gecikmeli kargo mock'u kuruyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 ile derlenip denenmiştir; mock'lar Prism 5.16 (@stoplight/prism-cli) ve WireMock.Net 2.31 ile çalıştırılmıştır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore'da (bir online kitapçı) ödeme sağlayıcısı, ödeme tamamlanınca Bookstore.Api'ye imzalı bir webhook gönderiyor. Sağlayıcılar teslimattan emin olamayınca aynı webhook'u tekrar gönderir. Şu üç isteğe API ne döner?
- Aynı
evt-0001olayının arka arkaya gelen iki kopyası. - İmzası doğru ama tutarı yolda
1.00'e çevrilmiş bir istek. - 15 dakika önce imzalanmış, hiç değiştirilmemiş bir istek; örneğin tünel aracının arayüzünden "Replay" ile yeniden gönderilen eski bir webhook.
Cevaplar webhook bölümündeki çıktıda. Üçüncüsü tünelle çalışırken en çok şaşırtan.
Ne Zaman Localhost'u İnternete Açarız
Bir önceki yazıda Bookstore.Api'den dışarı giden trafiği izledik. Bu yazıda yön ters. Üç durumda dışarıdaki bir şeyin senin makinendeki API'ye ulaşması gerekir: ödeme ya da kargo sağlayıcısının webhook'unu geliştirirken almak, bookstore-web'i telefondan denemek ve yarım kalmış bir işi müşteriye ya da Selin'e göstermek. Router'da port açmak yerine bir tünel servisi kullanılır.

Üç aracın modeli aynı. Makinendeki agent tünel servisine giden bir bağlantı açar; servis sana public bir HTTPS adresi verir ve o adrese gelen istekleri bu bağlantıdan agent'a, agent da localhost'taki porta iletir. Makinede dışarıya açık bir port yok, ama adresi bilen herkes API'ye ulaşabilir. Bu yüzden önce API'nin kendisini koruyan şeyi, webhook imzasını kuruyorum.
Webhook Endpoint'i ve İmza Doğrulama
Bookstore.Api'ye Payments module'ünü ekledim. POST /api/payments/webhooks kullanıcı token'ı istemiyor, kimliği imza kanıtlıyor. Sağlayıcı X-Payment-Timestamp header'ına Unix zamanını, X-Payment-Signature'a da v1= ile başlayan hex bir HMAC (paylaşılan secret ile hesaplanan imza; burada SHA-256 hash algoritmasıyla) yazıyor. İmzalanan veri "{timestamp}.{ham body}":
private static string? Verify(IHeaderDictionary headers, byte[] body, PaymentsOptions options, DateTimeOffset now)
{
if (!long.TryParse(headers[TimestampHeader], NumberStyles.None, CultureInfo.InvariantCulture, out long unixSeconds))
{
return "missing timestamp";
}
// Eski bir isteğin yeniden oynatılmasını engeller (varsayılan tolerans 5 dakika).
TimeSpan age = now - DateTimeOffset.FromUnixTimeSeconds(unixSeconds);
if (age.Duration() > options.SignatureTolerance)
{
return $"timestamp outside tolerance ({age.TotalSeconds:F0}s)";
}
string? header = headers[SignatureHeader];
if (header is null || !header.StartsWith("v1=", StringComparison.Ordinal))
{
return "missing signature";
}
byte[] expected = Sign(options.WebhookSecret, unixSeconds, body);
byte[] actual;
try
{
actual = Convert.FromHexString(header.AsSpan(3));
}
catch (FormatException)
{
return "malformed signature";
}
return CryptographicOperations.FixedTimeEquals(expected, actual) ? null : "signature mismatch";
}
İmza JSON'a çevrilmiş nesne üzerinden değil, gelen byte'lar üzerinden hesaplanıyor; bir boşluk farkı bile imzayı bozar. Karşılaştırma FixedTimeEquals ile, böylece süreye bakarak imzayı tahmin etmek mümkün değil. Geçerli bir olayın eventId'si 24 saat IMemoryCache'te tutuluyor ve ikinci kopya işlenmeden 200 alıyor. Gerçek handler'ın siparişi aynı adımda Paid yapması, kalıcı idempotency ve hızlı 2xx dönmek External Service Integration ve Resilience serisinin "Webhook Almak: İmza Doğrulama, Replay Koruması, Hızlı 2xx ve Idempotent İşleme" yazısında anlatılıyor. Burada amaç endpoint'i tünelden önce yerelde denemek.
Sağlayıcıyı taklit etmek için scripts/send-payment-webhook.cs adında tek dosyalık bir .NET 10 uygulaması yazdım (dotnet run ile proje dosyası olmadan çalışıyor). --tamper imzaladıktan sonra tutarı değiştiriyor, --age zaman damgasını geriye alıyor. Development'taki secret appsettings.Development.json'da açık duruyor ve bilerek gerçek bir formata benzemiyor:
$ export PAYMENT_WEBHOOK_SECRET=test-webhook-secret-not-real
$ dotnet run scripts/send-payment-webhook.cs -- $B/api/payments/webhooks 1 --event evt-0001
evt-0001 -> 200 OK
$ dotnet run scripts/send-payment-webhook.cs -- $B/api/payments/webhooks 1 --event evt-0001
evt-0001 -> 200 OK
$ dotnet run scripts/send-payment-webhook.cs -- $B/api/payments/webhooks 1 --event evt-0002 --tamper
evt-0002 -> 401 Unauthorized
$ dotnet run scripts/send-payment-webhook.cs -- $B/api/payments/webhooks 1 --event evt-0003 --age 900
evt-0003 -> 401 Unauthorized
# Bookstore.Api
info: Bookstore.Payments.PaymentEvent[0]
Payment event evt-0001 payment.succeeded for order 1: 659.80 TRY
info: Bookstore.Payments.PaymentEvent[0]
Payment event evt-0001 already processed
warn: Bookstore.Payments.PaymentEvent[0]
Rejected payment webhook: signature mismatch
warn: Bookstore.Payments.PaymentEvent[0]
Rejected payment webhook: timestamp outside tolerance (901s)
Tahminin cevabı: tekrar gelen kopya 200 alıyor ama bir kez işleniyor, değiştirilmiş tutar 401 alıyor, 15 dakikalık istek de imzası doğru olduğu halde 401 alıyor. Tünel araçlarının "Replay" düğmesi bu yüzden birkaç dakikadan sonra işe yaramaz; eski isteği denemek için sağlayıcının panelinden yeni bir gönderim istemek gerekir. Webhook güvenliğinin geri kalanı API Güvenliği (OWASP API Top 10) serisinin "API7 ve API10: Dış Dünyayla Konuşan API'ler" yazısında.
Dev Tunnels
Bu bölümde resmî belgelerden yararlanılmıştır.
Microsoft dev tunnels hâlâ public preview'da; CLI komutlarının adları değişebiliyor. Tünel açmak için Microsoft, Microsoft Entra ID ya da GitHub hesabıyla giriş şart, anonim host desteklenmiyor. Windows'ta kurulum ve geçici bir tünel:
winget install Microsoft.devtunnel
devtunnel user login
# varsayılan: yalnız tüneli açan hesap erişebilir
devtunnel host -p 5080
# webhook gibi giriş yapamayan istemciler için
devtunnel host -p 5080 --allow-anonymous
Geçici tünel process kapanınca siliniyor. Sağlayıcının panelinde webhook adresini her seferinde değiştirmek istemiyorsan devtunnel create ile kalıcı tünel açılıyor ve port ile erişim kuralları ayrı komutlarla ekleniyor. Erişim süreleri en az 1 saat, en fazla 30 gün. Anonim olmayan bir tünele API istemcisi, devtunnel token ile alınan token'ı X-Tunnel-Authorization: tunnel <token> header'ında göndererek girebiliyor; ödeme sağlayıcın özel header eklemene izin veriyorsa anonim erişime gerek kalmaz. Dokümanın uyarısı açık: anonim erişimde tünel ID'sini tahmin eden herkes local sunucuna bağlanabilir.
Visual Studio'da (dokümana göre 2022 17.6 ve sonrası) aynı özellik debug menüsünde: Dev Tunnels → Create A Tunnel, tür olarak Temporary ya da Persistent, erişim olarak Private, Organization ya da Public. Tünel aktifken Visual Studio uygulamaya VS_TUNNEL_URL environment variable'ını veriyor. Tarayıcıdan ilk girişte bir uyarı sayfası çıkıyor; GET dışındaki ya da Accept header'ında text/html olmayan istekler, yani webhook'lar, bu sayfaya takılmıyor.
Cloudflare Tunnel ve ngrok
Bu bölümde resmî belgelerden yararlanılmıştır.
Cloudflare'ın quick tunnel'ı hesap ve domain istemiyor:
cloudflared tunnel --url http://localhost:5080
Komut her açılışta rastgele bir trycloudflare.com adresi veriyor ve cloudflared kapanınca adres ölüyor. Dokümana göre quick tunnel yalnız test ve geliştirme için: aynı anda en fazla 200 istek (fazlası 429), Server-Sent Events desteği yok, uptime garantisi yok. Sabit adres ve Cloudflare Access ile kimlik doğrulama için hesaba bağlı, isimli bir Cloudflare Tunnel gerekiyor.
ngrok hesap ve authtoken istiyor. Ücretsiz planda her hesaba sabit bir dev domain (ngrok-free.app ya da ngrok-free.dev uzantılı) veriliyor ve ngrok http onu kullanıyor; webhook adresi agent yeniden başlasa da değişmiyor. Ücretsiz planda HTML isteyen tarayıcılara bir uyarı sayfası gösteriliyor, API istemcileri etkilenmiyor. Kimlik doğrulama artık komut satırı flag'leriyle değil traffic policy dosyasıyla yapılıyor; --oauth ve --basic-auth deprecated:
ngrok config add-authtoken <token>
ngrok http 5080
ngrok http 5080 --traffic-policy-file policy.yml
# policy.yml: ngrok dokümanındaki Google OAuth örneği
on_http_request:
- actions:
- type: oauth
config:
provider: google
OAuth, telefondan ya da müşteriye demo için uygun; webhook gönderen bir sunucu OAuth akışından geçemez. ngrok agent'ının http://localhost:4040'taki inspection arayüzü istekleri body'leriyle gösteriyor ve "Replay" ile yeniden gönderebiliyor; yukarıda gördüğümüz gibi imza zaman damgası taşıyorsa bu replay birkaç dakika sonra 401 alır.
| Konu | Dev Tunnels | Cloudflare quick tunnel | ngrok (ücretsiz) |
|---|---|---|---|
| Hesap | Microsoft, Entra ID ya da GitHub | Gerekmez | Gerekir (authtoken) |
| Adres | Geçici tünelde her seferinde yeni, kalıcıda sabit | Her seferinde rastgele | Hesabın sabit dev domain'i |
| Varsayılan erişim | Yalnız tüneli açan hesap | Adresi bilen herkes | Adresi bilen herkes |
| Erişimi daraltmak | Tenant, GitHub org, tünel token'ı | Quick tunnel'da --allowed-mail, isimli tünelde Cloudflare Access | Traffic policy (OAuth vb.) |
| Not | Public preview | 200 eşzamanlı istek, Server-Sent Events yok | Aylık 20.000 HTTP isteği |
Webhook için Bookstore'da Dev Tunnels'ın kalıcı tünelini tercih ediyorum: varsayılanı kapalı, Visual Studio'yla birlikte geliyor ve adres sağlayıcının panelinde bir kez yazılıyor. Hesap açmadan beş dakikalık bir deneme için quick tunnel yeterli.
Prism: OpenAPI Dokümanından Mock
Dördüncü yazıda Postman CLI'ın yerel mock'u token'sız isteğe 201 dönmüş, negatif fiyatlar üretmişti. Prism aynı OpenAPI dokümanını okuyup daha katı bir mock açıyor. Frontend'deki Ayşe, kargo ücreti endpoint'i hazır olmadan checkout sayfasını yazmak istesin:
$ curl -s -o openapi.json $B/openapi/v1.json
$ npx --yes @stoplight/prism-cli@5.16.0 mock openapi.json -h 127.0.0.1 -p 52690
[2:14:14 PM] › [CLI] ℹ info POST http://127.0.0.1:52690/api/shipping/quotes
[2:14:14 PM] › [CLI] ℹ info POST http://127.0.0.1:52690/api/payments/webhooks
[2:14:14 PM] › [CLI] ℹ info POST http://127.0.0.1:52690/api/auth/login
...
[2:14:14 PM] › [CLI] ▶ start Prism is listening on http://127.0.0.1:52690
$ curl -s -w ' %{http_code}\n' $P/api/shipping/quotes --json '{"city":"Ankara","books":2}'
{"type":"https://stoplight.io/prism/errors#UNAUTHORIZED","title":"Invalid security scheme used","status":401, ...} 401
$ curl -s -w ' %{http_code}\n' $P/api/shipping/quotes -H "Authorization: Bearer test" --json '{"books":200}'
{"type":"https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY","title":"Invalid request","status":422, ... "validation":[{"location":["body"],"severity":"Error","code":"required","message":"Request body must have required property 'city'"},{"location":["body","books"],"severity":"Error","code":"maximum","message":"Request body property books must be <= 99"}]} 422
$ curl -s -w ' %{http_code}\n' $P/api/shipping/quotes -H "Authorization: Bearer test" --json '{"city":"Ankara","books":2}'
{"quoteId":"string","city":"string","books":-2147483648,"price":-1.7976931348623157e+308,"currency":"string","estimatedDays":-2147483648} 200
$ curl -s -w ' %{http_code}\n' $P/api/shipping/quotes -H "Authorization: Bearer test" -H "Prefer: dynamic=true" --json '{"city":"Ankara","books":2}'
{"adipisicing8":"quis magna cupidatat aliqua labore","books":"187","cillum7cf":41635620.867682666,"city":"sunt non magna","currency":"fugiat","estimatedDays":"0", ... ,"price":"42485421229.934061","quoteId":"dolor pariatur nisi eiusmod", ...} 200
($P Prism'in adresi.) İyi taraf: Prism security tanımına bakıp token'sız isteğe 401, şemaya uymayan body'ye 422 dönüyor; Angular'daki hata akışları mock'la denenebiliyor. Kötü taraf cevaplarda. Statik modda her alan için tipin en uç değeri geliyor. Prefer: dynamic=true ile Faker'dan değer geliyor ama iki sürpriz var: şema ek property'lere kapalı olmadığı için cevapta rastgele alanlar var, books ve price da string olarak geliyor. Sebep ASP.NET Core 10'un dokümanı: web varsayılanlarında sayılar string olarak da okunabildiği için şemada "type": ["integer", "string"] yazıyor ve Prism ikisinden birini seçiyor. price.toFixed(2) çağıran bir Angular kodu bu mock'ta patlar. Prefer: code=404 ise "Requested status code 404 is not defined in the document." diyor: Prism yalnız dokümanda tanımlı cevapları üretebiliyor.
Çözüm, dokümana gerçekçi bir örnek koymak. Bir schema transformer ShippingQuoteView'a örnek ekliyor:
// /openapi/v1.json'a örnek cevap: Prism gibi mock'lar "string" ve int.MinValue yerine bunu döner.
public static class ShippingOpenApi
{
public static OpenApiOptions AddShippingExamples(this OpenApiOptions options) =>
options.AddSchemaTransformer((schema, context, _) =>
{
if (context.JsonTypeInfo.Type == typeof(ShippingQuoteView))
{
schema.Examples =
[
new JsonObject
{
["quoteId"] = "q-0001",
["city"] = "Ankara",
["books"] = 2,
["price"] = 49.90m,
["currency"] = "TRY",
["estimatedDays"] = 2
}
];
}
return Task.CompletedTask;
});
}
İlk denemede schema.Example'ı kullandım; Microsoft.OpenApi 2.x'te obsolete olduğu için TreatWarningsAsErrors açık projede derleme CS0618 ile durdu. Examples listesiyle dokümanı yeniden alıp Prism'i yeniden başlatınca:
$ curl -s -w ' %{http_code}\n' $P/api/shipping/quotes -H "Authorization: Bearer test" --json '{"city":"Ankara","books":2}'
{"quoteId":"q-0001","city":"Ankara","books":2,"price":49.9,"currency":"TRY","estimatedDays":2} 200
Angular dev server'ı Prism'e yönlendirmek, önceki yazıdaki proxy.mitm.conf.json gibi ayrı bir proxy dosyasıyla target'ı 52690 yapmaktan ibaret. Prism stateless: sipariş oluşturup sonra okumak gibi birbirine bağlı istekler için doğru araç değil.
WireMock.Net: Senaryo ve Gecikme
Prism sözleşmeyi taklit ediyor; WireMock.Net ise dış servisin davranışını. Bookstore'un ihtiyacı kargonun kötü günleri: bir şehirde ilk istek 503, ikincisi başarılı; başka bir şehirde cevap 3 saniye gecikiyor. Mapping'ler mocks/wiremock/__admin/mappings/ altında JSON dosyaları. Kars için iki adımlı senaryonun ilk dosyası:
{
"Guid": "6d1f0c52-7d0a-4a43-9a9e-0c2a3f6c1004",
"Title": "Carrier outage for Kars: first call fails",
"Priority": 1,
"Scenario": "carrier-outage",
"SetStateTo": "recovered",
"Request": {
"Path": { "Matchers": [ { "Name": "ExactMatcher", "Pattern": "/v1/quotes" } ] },
"Methods": [ "POST" ],
"Body": { "Matcher": { "Name": "JsonPartialMatcher", "Pattern": { "destinationCity": "Kars" } } }
},
"Response": {
"StatusCode": 503,
"Headers": { "Content-Type": "application/json" },
"BodyAsJson": { "error": "temporarily_unavailable" }
}
}
İkinci dosya aynı isteği "WhenStateIs": "recovered" ile 200'e bağlıyor. Van mapping'i cevaba "Delay": 3000 ekliyor, doğru API key'li diğer istekler normal teklif alıyor, key'siz istek 401 alıyor. WireMock.Net'i repo'nun tool manifest'ine local tool olarak ekleyip mapping klasöründen çalıştırdım:
$ dotnet tool install dotnet-wiremock --version 2.31.0
$ cd mocks/wiremock
$ dotnet tool run dotnet-wiremock --Urls http://127.0.0.1:52691 --ReadStaticMappings true --WireMockLogger WireMockConsoleLogger
11/15/2026 11:12:03 AM [Info] : Version [2.31.0.0]
11/15/2026 11:12:03 AM [Info] : Reading Static MappingFile : '.../__admin/mappings/carrier-outage-kars-1.json'.
...
11/15/2026 11:12:03 AM [Info] : Server listening at http://localhost:52691
--Urls'i boş bırakma: dokümana göre varsayılan http://*:9091/, yani bütün arayüzler, ve admin arayüzü varsayılan olarak açık. Aynı ağdaki biri mapping'lerini değiştirebilir. appsettings.Development.json'da kargo adresi artık WireMock (http://127.0.0.1:52691). Önceki yazıdaki fake_carrier.py HTTPS ve proxy denemeleri için duruyor. API'yi başlatıp üç şehir sordum:
$ for c in Ankara Kars Van; do curl -s -w ' %{http_code} %{time_total}s\n' $B/api/shipping/quotes \
-H "Authorization: Bearer $TOKEN" --json "{\"city\":\"$c\",\"books\":2}"; done
{"quoteId":"wm-0001","city":"Ankara","books":2,"price":49.9,"currency":"TRY","estimatedDays":2} 200 0.056216s
{"quoteId":"wm-kars","city":"Kars","books":2,"price":79.9,"currency":"TRY","estimatedDays":3} 200 0.142304s
{"type":"https://tools.ietf.org/html/rfc9110#section-15.6.4","title":"Service Unavailable","status":503, ...} 503 6.522767s
$ curl -s http://127.0.0.1:52691/__admin/requests | jq -c '.[] | {city: (.Request.BodyAsJson.destinationCity), status: .Response.StatusCode, mapping: .MappingTitle}'
{"city":"Ankara","status":200,"mapping":"Carrier quote: normal answer"}
{"city":"Kars","status":503,"mapping":"Carrier outage for Kars: first call fails"}
{"city":"Kars","status":200,"mapping":"Carrier outage for Kars: recovered"}
{"city":"Van","status":200,"mapping":"Carrier quote: Van answers after 3 seconds"}
{"city":"Van","status":200,"mapping":"Carrier quote: Van answers after 3 seconds"}
{"city":"Van","status":200,"mapping":"Carrier quote: Van answers after 3 seconds"}
Kars'ta retry 503'ü kullanıcıdan saklıyor. Van'da önceki yazıda mitmproxy addon'uyla gördüğümüz tablo, bu kez proxy olmadan: üç deneme, üçü de kargoda 200, kullanıcıda 503. Admin API'nin request journal'ı kimin hangi mapping'e düştüğünü gösteriyor; mapping'in neden eşleşmediğini anlamanın en kısa yolu bu. Senaryo bir kez "recovered" olunca öyle kalıyor; yeniden denemek için:
$ curl -s -X POST http://127.0.0.1:52691/__admin/scenarios/reset
{"Status":"Scenarios reset"}
Aynı mapping dosyalarını testte de kullanıyorum. tests/Bookstore.Shipping.Tests WireMock'u test içinde rastgele bir portta başlatıyor, mapping klasörünü okuyor ve CarrierClient'ı AddShippingModule'ün gerçek resilience ayarlarıyla kuruyor:
[Fact]
public async Task Retries_after_a_503_and_returns_the_second_answer()
{
CarrierClient client = services.GetRequiredService<CarrierClient>();
CarrierQuote quote = await client.GetQuoteAsync(new QuoteRequest("Kars", 1, 900), TestContext.Current.CancellationToken);
Assert.Equal("wm-kars", quote.QuoteId);
Assert.Equal([503, 200], carrier.LogEntries.Select(e => Convert.ToInt32(e.ResponseMessage!.StatusCode)));
}
$ dotnet test --project tests/Bookstore.Shipping.Tests
Test run summary: Passed!
total: 3
failed: 0
succeeded: 3
skipped: 0
duration: 5s 665ms
Testlerde deneme süresini 0,5 saniyeye indirdim; Van testi yine üç denemenin hepsinin timeout olduğunu doğruluyor. WireMock.Net'in test içindeki kullanımı, fault injection ve FakeTimeProvider Integration ve E2E (uçtan uca) Test serisinin "Dış Bağımlılıkları Sınırda Taklit Etmek: WireMock.Net, Sahte SMTP, FakeTimeProvider ve Hata Senaryoları" yazısında.
Prism mi, WireMock.Net mi, Postman Mock mu
| Konu | Prism | WireMock.Net | Postman mock |
|---|---|---|---|
| Kaynak | OpenAPI dokümanı | Elle yazılan mapping'ler (JSON ya da C#) | Collection'daki örnekler ya da OpenAPI |
| Çalıştığı yer | Yerel, Node.js | Yerel, .NET tool ya da test içinde | Postman bulutu; CLI ile yerel |
| Request doğrulama | Şema (422) ve security (401) | Eşleşmeyen istek 404 | Yerel mock token'sız isteği kabul etti |
| Durum ve gecikme | Yok, her istek bağımsız | Scenario, Delay | Header ile cevap seçimi |
| En uygun iş | Frontend'i sözleşmeye göre geliştirmek | Dış servisin hata ve gecikmelerini taklit etmek | Postman kullanan ekipte paylaşmak |
Bookstore'da ikisi birlikte duruyor: Prism kendi API'mizin henüz yazılmamış endpoint'leri için, WireMock.Net kontrol edemediğimiz dış servisler için.
Üretimde Dikkat
Tünel ve mock'lar geliştirme araçları. Production'da webhook'u ödeme sağlayıcısı doğrudan API'nin public adresine gönderir; tünel servisi ortada durmaz. Tünel trafiği bir üçüncü tarafın altyapısından geçiyor ve TLS (HTTPS'in şifreli bağlantısı) orada bitiyor, yani sağlayıcının gerçek ödeme verisini ve token'larını taşıyan bir tünel o şirketin sunucularında düz metin oluyor. Tünele yalnız sandbox ya da test modundaki sağlayıcı verisini gönder.
Webhook secret'ı ortam başına ayrı olsun. Development'ın okunur fake secret'ı sandbox'a, sandbox'ınki production'a taşınmamalı. Bookstore'da PaymentsOptions açılışta doğrulanıyor; production'da secret boşsa uygulama açılmıyor.
Mock'lar kod gibi bakım ister. Mapping dosyaları ve OpenAPI örnekleri repo'da, review'dan geçiyor. Gerçek sağlayıcı cevabını değiştirdiğinde mapping'ler sessizce eskir; onları sağlayıcının güncel dokümanı ve sandbox'ından alınmış gerçek cevaplarla düzenli karşılaştır.
Sık Düşülen Tuzaklar
Tüneli açık bırakmak. Geçici tünel process kapanınca gidiyor, ama kalıcı tünel ve anonim erişim kuralı sen kaldırana ya da süresi dolana kadar duruyor. Erişim kurallarını kısa süreyle aç, işin bitince tüneli sil.
Anonim tünelden her şeye erişilebilmesi. Tünel portu bütünüyle açar, yalnız webhook endpoint'ini değil. Bookstore.Api Development'ta çalışırken Scalar, /openapi/v1.json ve demo kullanıcılarıyla login de o adresten erişilebilir. Demo şifreleri de bu serinin yazılarında açıkça yazıyor. Webhook denerken API'yi ayrı bir launch profile'la, yalnız ihtiyaç duyulan endpoint'lerle çalıştırmak ya da tüneli kimlik doğrulamalı açmak daha güvenli.
Tünel adresini koda yazmak. https://...devtunnels.ms gibi bir adres appsettings.json'a ya da Angular'ın environment dosyasına girerse herkesin makinesinde yanlış yere gider. Visual Studio'nun VS_TUNNEL_URL'i gibi environment variable kullan.
Mock'u bütün arayüzlerde açmak. WireMock.Net'in varsayılan adresi ve açık admin arayüzü, aynı ağdakilere senin mapping'lerini değiştirme imkânı veriyor. --Urls http://127.0.0.1:... şart.
Prism'in rastgele cevabına güvenmek. Dinamik modda sayıların string gelmesi, mock'la yazılan frontend kodunun gerçek API'de değil mock'ta hata vermesine yol açar. Örnekleri dokümana ekle ve statik modu varsayılan yap.
Küçük Bir Deneme
★☆☆ send-payment-webhook.cs ile --age 290 ve --age 310 gönder; toleransın kenarında ne olduğuna ve logdaki saniyeye bak.
★★☆ WireMock'a üçüncü bir senaryo ekle: "Erzurum" için ilk iki istek 503, üçüncüsü 200. API'nin bu durumda 200 mü 503 mü döndüğünü tahmin et, sonra request journal'la doğrula.
★★★ Prism'i --errors seçeneğiyle başlat ve Angular checkout sayfasının hatalı body ile davranışını dene. Ardından ConfigureHttpJsonOptions'ta NumberHandling'i JsonNumberHandling.Strict yap, dokümanı yeniden al ve şemadaki "type" değerlerinin nasıl değiştiğine bak.
Bu yazıyla API Araçları serisinin sonuna geldik. Aynı Bookstore akışını curl, iki dialect'te .http dosyası, Postman ve Bruno ile koştuk; mitmproxy ile API'nin dışarıya ne gönderdiğini gördük; tünel ve mock'larla dışarıdan gelen ve henüz var olmayan servisleri yerelde karşıladık. API'nin davranışını codebase'te test etmek Integration ve E2E Test serisinin, aynı akışı yük altında ölçmek de Performance ve Load Testing serisinin konusu.