2017-11-27 147 views
9

WAI arayüzüne meraklı bir şekilde baktım ve basit görünmekle birlikte, şu anki haliyle stabilize etmek için kaç tane deneme yapıldığını görmek beni şaşırttı!WAI Uygulamasının beş kez yeniden tasarlanması için hangi kuvvetler geldi?

Kaynak güvenliği için CPS stilinin en ilginç şey olacağını düşünmüştüm ama öğrenilmesi gereken daha çok şey var gibi görünüyor!

$ git log -p --reverse -- wai/Network/Wai.hs | grep '\+type Application' 
+type Application = Request -> Iteratee B.ByteString IO Response 
+type Application = Request -> ResourceT IO Response 
+type Application = Request -> C.ResourceT IO Response 
+type Application = Request -> IO Response 
+type Application = Request -> (forall b. (Response -> IO b) -> IO b) 
+type Application = Request -> (Response -> IO ResponseReceived) 
          -> IO ResponseReceived 

Bazı arkeoloji ürünleri bir ölçüde tatmin edici sonuçlar:

$ git log --reverse -G 'type Application' --pretty=oneline -- wai/Network/Wai.hs | cat 
879d4a23047c3585e1cba4cdd7c3e8fc13e17592 Moved everything to wai subfolder 
360442ac74f7e79bb0e320110056b3f44e15107c Began moving wai/warp to conduit 
af7d1a79cbcada0b18883bcc5e5e19a1cd06ae7b conduit 0.3 
fe2032ad4c7435709ed79683acac3b91110bba04 Pass around an InternalState instead of living in ResourceT 
63ad533299a0a5bad01a36171d98511fdf8d5821 Application uses bracket pattern 
1e1b8c222cce96c3d58cd27318922c318642050d ResponseReceived, to avoid existential issues 
+0

Yeniden tasarlamanın ardındaki * kuvvetin * haklı çıkma arzusu olduğunu öne sürmek isterim. – Carl

cevap

4

Tüm tasarımlar üç ana kaygıları ile tahrik gibi görünüyor:

  • talepler organları (böylece biz değil akışa sahip olabilir bunları işlemeye başlamadan önce hepsini bellekte yüklemelisiniz). En iyi nasıl temsil edilir?
  • Yanıtlar da yayınlanabilir. En iyi nasıl temsil edilir?
  • Bir yanıtın üretimine ayrılan kaynakların uygun şekilde nasıl serbest bırakıldığından emin olunur? (Örneğin, emin olmak için nasıl dosya kolları? Bir dosya yattıktan sonra serbest bırakılan)

type Application = Request -> Iteratee B.ByteString IO Response 

This version Haskell akış verileri için erken çözüm olduğunu iteratees kullanır. Yinelemeli tüketiciler, modern akan kütüphanelerde kullanılan “çekmeye dayalı” tüketicilerden daha az doğal olan “basma temelli” bir şekilde yazılmalıydı.

İsteğin akış halindeki gövdesi yinelemeye beslenir ve sonunda bir Response değeri alırız. Response, bir numaralandırıcı (sunucu tarafından sağlanan yanıt yinelemesine iletilen yanıt baytlarını besleyen bir işlev) içerir. Muhtemelen sayımcı, bracket gibi işlevleri kullanarak kaynak tahsisini kontrol edecektir.


type Application = Request -> ResourceT IO Response 

This version

yerine sýralayýcý bunu yapmanın, kaynak yönetimi için resourcet monad trafo kullanır. Aktarılan veriyi işleyen (ve IMHO için biraz zor olan) hem de Request ve Response içinde özel bir Source türü vardır.


type Application = Request -> IO Response 

Bu sürüm conduit gelen akış soyutlamalar kullanır, ancak resourcet kaçınan ve bunun yerine akış yanıtlarda kaynaklarını işlemek için bir braket benzeri responseSourceBracket fonksiyonunu sağlar. Kaynak tahsisi kontrol etmek için düzenli olarak bracket benzeri işlevleri kullanmak için işleyici çalışmasını sağlayan bir devamı tabanlı yaklaşım


type Application = Request -> (forall b. (Response -> IO b) -> IO b) 
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived 

This version hareket eder. Bu açıdan, bire dönüş!

Akışlar artık akış için kullanılmamaktadır.Şimdi, istek akışının parçalarını okumak için Request -> IO ByteStringfunction ve yanıt akışını oluşturmak için Response'da (Builder -> IO()) -> IO() -> IO()function bulunmaktadır. (Builder -> IO() yazma işlevi, bir yıkama eylemi ile birlikte sunucu tarafından sağlanır.)

Yeniden oluşturmaya dayalı sürümler gibi ve yinelemeye dayalı sürümden farklı olarak, bu uygulama yanıt akışını gerçekleştirerek istek gövdesinin okunmasını sağlar. işleyici bir b dönmek gerekiyor ve bir tane almak için tek yol aslında geri arama çağırmaktır:

polimorfik işleyicisi yanıtı alma geri arama Response -> IO b hep çağrılmasını sağlamak için düzgün bir hile!

Bu polimorfik çözüm bazı sorunlara yol açmış gibi görünüyor (belki de kapsayıcılarda işleyicileri depolamakla birlikte). Polimorfizm kullanmak yerine, ortak kurucu olmadan ResponseReceived belirtecini kullanabiliriz. Efekt aynıdır: İşleyici kodunun geri dönmesi gereken belirteci tutması için tek yol geri çağırma işlemini çağırmaktır.