Lisp ile Web Programlamaya Dalış
Lisp ile web programlamaya giriş niteliğinde olan bu yazıda,
Debian GNU/Linux yüklü bir
sisteme gerekli olan programları
nasıl kuracağımızı gösterdikten "merhaba dünya" şeklinde
ufak bir web uygulamasını nasıl geliştiriceğimizi,
ardından da güçlü makro özellikleri ile neler
yapabileceğimize bakacağız. Bu yazıda anlatacaklarımız, Debian
GNU/Linux'un "testing" dağıtımı olan Etch isimli dağıtım
üzerinde denenmiştir. Aynı adımları "stable" olarak nitelenen
Debian Sarge ve "unstable" olarak nitelenen SID üzerinde de
pek bir sorun çıkmadan takip edebileceğinizi
düşünüyoruz. Unutmadan söyleyelim, bu yazıda Lisp dil ailesinin
büyük ağabeyi, endüstri standardı olan Common Lisp'i kullanacağız.
Bu yazı hiç Lisp bilmeyenlere Lisp öğreten bir yazı değildir. Ayrıca karmaşık web uygulamaları ile nasıl başa çıkacağınızı da göstermemektedir. Sadece Common Lisp ve web programlamaya dair mümkün olan en basit ısınma turlarından biridir.
Programlarımızı üzerinde geliştireceğimiz ortam olarak SBCL'yi (Steel Bank Common Lisp) seçtik. Tabii ki olmazsa olmaz editörümüz de Emacs. Eğer sisteminizde, bu yazıda kullanacağımız, Emacs, SBCL ve SLIME (bir hayli gelişmiş bir Emacs Lisp geliştirme ortamı) yüklü değilse, Emre Sevinç'in " Common Lisp Geliştirme Ortamı Kurulumu" başlıklı makalesine gözatmanızı öneriyoruz. Ayrıca, eğer Lisp'e eğlenceli bir başlangıç yapmak isterseniz Conrad Barski tarafından yazılmış ve Seda Çelebican tarafından Türk diline çevrilmiş " Lisp ile TILSIMLI ve Renkli Programlama: Lisperati" başlıklı yazıyı okumanızı öneririz.
Artık kuruluma başlayabiliriz. Web uygulamalarızı geliştirebilmemiz için gerekli olan web sunucumuzu, aşağıdaki komut ile sistemimize kuruyoruz.
Şimdi, IDE-ötesi programımız Emacs'ı açıp,
ve emacs açıldıktan sonra, emacs penceresinde
komutları yeterli olacaktır. (Burada M-x, Meta tuşunu basılı tutup, x tuşuna basmayı ifade ediyor. Meta tuşu, eğer IBM uyumlu PC klavyesi kullanıyorsanız genellikle üzerinde Alt yazan tuştur.)
SLIME'ı ilk kez çalıştırıyorsanız bir süre gerekli SLIME kodları derlenecek (sadece bir kereliğe mahsus) ve sonra da karşınıza Lisp'in ünlü Read-Eval-Print Loop'u (REPL yani Oku-Değerlendir-Bas Döngüsü) çıkacaktır.
Şimdi sistemimize AllegroServe'ü yüklemek için aşağıdaki komutu çalıştırmamız gerekiyor:
Burada yazdığımız komut, ilk satırdaki (require :aserve) kısmı. Baştaki CL-USER bize şu anda hangi paketin içerisinde olduğumuzu gösteriyor. > karakteri de Lisp'in bizden bir şeyler girmemizi istediğini söylüyor. İkinci satır ise, Lisp'in çalıştırdığımız komuta verdiği yanıt. Bunu çalıştırdığımız ifadenin geridönüş değeri olarak da düşünebiliriz. Artık AllegroServe programının bize sağladığı fonksiyonları kullanabiliriz. Bir sonraki bölüme geçmeden önce Lisp'teki paket kavramından bahsetmek ve bunun bir tür "namespace" tanımlamak için kullanılan kavramlardan biri olduğunu belirtmek faydalı olacaktır. Lisp'teki paket kavramını yazılım karmaşıklığı ile başa çıkmak, işlevselliği yalıtmak, vs. gibi işler için C#'taki "namespace" ya da Java'daki "package" mekanizması gibi düşünebilirsiniz. Daha detaylı bilgi için PCL'nin ilgili bölümüne ya da The Complete Idiot's Guide to Common Lisp Packages belgesine bakabilirsiniz.
Fonksiyonumuz
Artık net.aserve paketindeki -yani AllegroServe'ün- fonksiyonlarına, makrolarına ve global değişkenlerine, isimlerinin önünde paket ismi olmadan erişebileceğiz.
Web sunucumuzu başlatmamıza yarayan start fonksiyonu, hepsi "keyword" parametresi olmak üzere tam 19 parametre alıyor (Common Lisp bir fonksiyona parametre geçirmek için muazzam bir esneklik sunar, detaylar için lütfen Practical Common Lisp'in Functions bölümüne bakınız). Ancak bu parametrelerin çoğu için şimdilik varsayılan değerleri değiştirmemiz gerekli değil. Biz sadece
Eğer http://localhost:3000 adresini Firefox (ya da Emacs içinden w3m) gibi bir web tarayıcı programından açarsak, çok sevimli bir 404 sayfa bulunamadı sayfasıyla karşılaşıyoruz. Bu mesajı size Allegro web sunucusunun verdiğini sayfanın altındaki Allegro ve sürüm numarasından anlayabilirsiniz. (Eğer böyle bir şey olmaz da "connection refused" benzeri bir uyarı penceresi ile karşılaşırsanız o zaman lütfen adresi doğru yazdığınızı, sunucunun çalıştığını ve 3000 numaralı portu daha öncede başka bir programın dinlemediğini kontrol edin.) Sayfa bulunamadı şeklindeki bir web sayfası ile karşılaşmanızın sebebi web sunucusunda bırakın index'i, daha hiçbir sayfayı yayınlamıyor oluşumuz. Şimdi geleneklere saygı göstererek bir "merhaba dünya" yazısını tarayıcımızda göstermek istiyoruz. Bunun için Emacs'taki REPL ortamında yani Common Lisp komut satırında
yazdıktan sonra ve şimdi http://localhost:3000 adresine gittiğimizde bizi
Merhaba Dünya
yazısı karşılıyor. (Ne büyük sürpriz!)
AllegroServe'ü diğer web sunucularından (mesela Apache) ayıran en önemli özelliklerden biri, sadece dosyaları ve dizinleri değil doğrudan bir Lisp fonksiyonunu yayımlayabilmesi. Bu özellik sayesinde sunucumuza "eğer kullanıcı şu adrese giderse bu fonksiyonu çalıştır" diyebiliyoruz. Lisp'de fonksiyonlar birinci sınıf nesne olduklarından [1], bir adrese gidildiğinde çağrılacak fonksiyonu örnekte gösterildiği gibi dinamik olarak da yaratabiliriz.
Fonksiyonlarımızla, URL'leri eşleştirmemize yarayan
Bir adresle eşleştirdiğimiz fonksiyon iki parametre alan bir fonksiyon olmalı. Birincisi http istek (request) nesnesi, ikinci ise
Gelelim oluşturduğumuz fonksiyonun içinde bulunan
Fonksiyonumuzda ekrana "merhaba dünya" yazısını yazdıran kısım,
AllegroServe'ün Apache'nin yaptığı gibi dosyaları ya da dizinleri yayınlamasını istersek, kullanmamız gereken fonksiyonlar
Artık http://localhost:3000/makale.txt adresinden ev dizinimdeki lisp-web.01.txt dosyasına ulaşabilir durumdayım.
Şimdi biraz daha gelişmiş bir "merhaba dünya" uygulamasıyla, HTTP isteğinden gelen GET ve POST değişkenlerine nasıl ulaşacağımıza bakalım. Henüz Lisp ile HTML üretmeyi bilmediğimiz için form sayfasını durağan (statik) bir HTML sayfası olarak bir dosyaya kaydediyor ve bu dosyayı yayınlıyoruz.
Form sayfamız:
Bu yazı hiç Lisp bilmeyenlere Lisp öğreten bir yazı değildir. Ayrıca karmaşık web uygulamaları ile nasıl başa çıkacağınızı da göstermemektedir. Sadece Common Lisp ve web programlamaya dair mümkün olan en basit ısınma turlarından biridir.
Kolları Sıvıyoruz
Lisp ile web uygulamaları geliştirmek için Franz Inc. tarafından geliştirilen AllegroServe web sunucusunu kullanacağız. AllegroServe, tamamen Common Lisp ile yazılmış olup, LLGPL ile dağıtılmaktadır. Esas olarak Franz'ın kendi Lisp ortamı olan AllegroCL üzerinde çalışması için tasarlanan AllegroServe'ü diğer Lisp ortamlarında çalıştırabilmek için bir ara katman kullanılıyor. Adı Portable AllegroServe olan bu katman programı, içinde AllegroServe programını da barındırıyor ve beraber dağıtılıyor. Debian GNU/Linux paket deposunda bulunan cl-aserve isimli paket de aslında Portable AllegroServe'ü içeriyor.Programlarımızı üzerinde geliştireceğimiz ortam olarak SBCL'yi (Steel Bank Common Lisp) seçtik. Tabii ki olmazsa olmaz editörümüz de Emacs. Eğer sisteminizde, bu yazıda kullanacağımız, Emacs, SBCL ve SLIME (bir hayli gelişmiş bir Emacs Lisp geliştirme ortamı) yüklü değilse, Emre Sevinç'in " Common Lisp Geliştirme Ortamı Kurulumu" başlıklı makalesine gözatmanızı öneriyoruz. Ayrıca, eğer Lisp'e eğlenceli bir başlangıç yapmak isterseniz Conrad Barski tarafından yazılmış ve Seda Çelebican tarafından Türk diline çevrilmiş " Lisp ile TILSIMLI ve Renkli Programlama: Lisperati" başlıklı yazıyı okumanızı öneririz.
Artık kuruluma başlayabiliriz. Web uygulamalarızı geliştirebilmemiz için gerekli olan web sunucumuzu, aşağıdaki komut ile sistemimize kuruyoruz.
# apt-get install cl-aserve
Şimdi, IDE-ötesi programımız Emacs'ı açıp,
M-x slime
komutunu verip Emacs ile sbcl arasında iletişim kuracak ve
Lisp kodlarımızı yazarken bize pek çok kolaylık sağlayacak olan SLIME'ı
çalıştırıyoruz. Komut satırından
$ emacs
ve emacs açıldıktan sonra, emacs penceresinde
M-x slime
komutları yeterli olacaktır. (Burada M-x, Meta tuşunu basılı tutup, x tuşuna basmayı ifade ediyor. Meta tuşu, eğer IBM uyumlu PC klavyesi kullanıyorsanız genellikle üzerinde Alt yazan tuştur.)
SLIME'ı ilk kez çalıştırıyorsanız bir süre gerekli SLIME kodları derlenecek (sadece bir kereliğe mahsus) ve sonra da karşınıza Lisp'in ünlü Read-Eval-Print Loop'u (REPL yani Oku-Değerlendir-Bas Döngüsü) çıkacaktır.
CL-USER>
Şimdi sistemimize AllegroServe'ü yüklemek için aşağıdaki komutu çalıştırmamız gerekiyor:
CL-USER> (require :aserve)
("URI" "ACL-SOCKET" "ASERVE")
Burada yazdığımız komut, ilk satırdaki (require :aserve) kısmı. Baştaki CL-USER bize şu anda hangi paketin içerisinde olduğumuzu gösteriyor. > karakteri de Lisp'in bizden bir şeyler girmemizi istediğini söylüyor. İkinci satır ise, Lisp'in çalıştırdığımız komuta verdiği yanıt. Bunu çalıştırdığımız ifadenin geridönüş değeri olarak da düşünebiliriz. Artık AllegroServe programının bize sağladığı fonksiyonları kullanabiliriz. Bir sonraki bölüme geçmeden önce Lisp'teki paket kavramından bahsetmek ve bunun bir tür "namespace" tanımlamak için kullanılan kavramlardan biri olduğunu belirtmek faydalı olacaktır. Lisp'teki paket kavramını yazılım karmaşıklığı ile başa çıkmak, işlevselliği yalıtmak, vs. gibi işler için C#'taki "namespace" ya da Java'daki "package" mekanizması gibi düşünebilirsiniz. Daha detaylı bilgi için PCL'nin ilgili bölümüne ya da The Complete Idiot's Guide to Common Lisp Packages belgesine bakabilirsiniz.
Biraz Aserve
İlk fonksiyonumuzstart
fonksiyonu.
Bu fonksiyon http sunucumuzu başlatmamıza yarıyor.
Bu fonksiyonu aşağıdaki gibi çağırıyoruz:
CL-USER> (net.aserve:start :port 3000)
#<NET.ASERVE:WSERVER port 3000 {A3BD9C9}>
Fonksiyonumuz
net.aserve
isimli pakete ait olduğu ve şu anda biz
CL-USER paketinin içinde çalıştığımız için, paket ismini
fonksiyon isminin önüne yazmamız ve aralarına : işareti koymamız
gerekiyor. Bu uzun yazış şeklinden kaçınmak için use-package
fonksiyonunu kullanmamız yeterli. (Aman dikkat, eğer şu anda içinde çalışılan
paketle use-package
ile ihraç (export) edilen
paketler aynı isimli fonksiyon, makro veya değişken kullanıyorlarsa,
"sembol çakışması hatası" gerçekleşir. Bu yüzden use-package dikkatli
kullanılmalıdır. Burada use-package kullanmamızın nedeni, uzun
fonksiyon isimlerinden kaçınmak istememizdir. Common Lisp'teki paket
yönetimi ile ilgili olarak
Practical Common Lisp'in ilgili bölümüne göz atabilirsiniz.)
CL-USER> (use-package :net.aserve)
T
Artık net.aserve paketindeki -yani AllegroServe'ün- fonksiyonlarına, makrolarına ve global değişkenlerine, isimlerinin önünde paket ismi olmadan erişebileceğiz.
Web sunucumuzu başlatmamıza yarayan start fonksiyonu, hepsi "keyword" parametresi olmak üzere tam 19 parametre alıyor (Common Lisp bir fonksiyona parametre geçirmek için muazzam bir esneklik sunar, detaylar için lütfen Practical Common Lisp'in Functions bölümüne bakınız). Ancak bu parametrelerin çoğu için şimdilik varsayılan değerleri değiştirmemiz gerekli değil. Biz sadece
:port
parametresinin
değerini 3000 olarak belirledik ve web sunucumuzun 3000 numaralı
porttan yayın yapacağını söyledik. 3000 sayısının pek özel bir anlamı
yok, standart 80 numaralı http port numarasını vermememizin sebebi
öncelikle bunun "root" kullanıcı yetkisini gerektirmesi (UNIX ve türevi
sistemlerde 1024 numaralı portun altındaki port numaralarının
"root" yetkileri gerektirmesi gibi) ve/veya aynı zamanda 80
numaralı portunuzun başka programlar tarafından kullanımda olma
olasılığı (Apache web sunucusu, vb.). start
fonksiyonu da bize bir
WSERVER
nesnesi geri gönderdi (Lisp ve nesneye
yönelik programlama! :). Fazla detaya inmeden şunu
söyleyelim ki, start fonksiyonu başarıyla bir WSERVER nesnesi
yarattı, yani sunucumuz localhost'umuzun 3000 numaralı portunda
ziyaretçilerini beklemeye koyuldu.
Eğer http://localhost:3000 adresini Firefox (ya da Emacs içinden w3m) gibi bir web tarayıcı programından açarsak, çok sevimli bir 404 sayfa bulunamadı sayfasıyla karşılaşıyoruz. Bu mesajı size Allegro web sunucusunun verdiğini sayfanın altındaki Allegro ve sürüm numarasından anlayabilirsiniz. (Eğer böyle bir şey olmaz da "connection refused" benzeri bir uyarı penceresi ile karşılaşırsanız o zaman lütfen adresi doğru yazdığınızı, sunucunun çalıştığını ve 3000 numaralı portu daha öncede başka bir programın dinlemediğini kontrol edin.) Sayfa bulunamadı şeklindeki bir web sayfası ile karşılaşmanızın sebebi web sunucusunda bırakın index'i, daha hiçbir sayfayı yayınlamıyor oluşumuz. Şimdi geleneklere saygı göstererek bir "merhaba dünya" yazısını tarayıcımızda göstermek istiyoruz. Bunun için Emacs'taki REPL ortamında yani Common Lisp komut satırında
CL-USER> (publish :path "/"
:content-type "text/html"
:function
#'(lambda (req ent)
(with-http-response (req ent)
(with-http-body (req ent)
(format (request-reply-stream req) "merhaba dünya!")))))
#<COMPUTED-ENTITY {9FDFBD1}>
yazdıktan sonra ve şimdi http://localhost:3000 adresine gittiğimizde bizi
Merhaba Dünya
yazısı karşılıyor. (Ne büyük sürpriz!)
AllegroServe'ü diğer web sunucularından (mesela Apache) ayıran en önemli özelliklerden biri, sadece dosyaları ve dizinleri değil doğrudan bir Lisp fonksiyonunu yayımlayabilmesi. Bu özellik sayesinde sunucumuza "eğer kullanıcı şu adrese giderse bu fonksiyonu çalıştır" diyebiliyoruz. Lisp'de fonksiyonlar birinci sınıf nesne olduklarından [1], bir adrese gidildiğinde çağrılacak fonksiyonu örnekte gösterildiği gibi dinamik olarak da yaratabiliriz.
Fonksiyonlarımızla, URL'leri eşleştirmemize yarayan
publish
fonksiyonuna verdiğimiz parametrelere kısaca bir gözatalım.
-
:path
anahtar sözcüğü kullanarak fonksiyona geçirdiğimiz değer (:path "/"
) fonksiyonumuzu eşleştireceğimiz adresi belirtiyor. Yukarıdaki örnekte "/", yani kök adresi belirttik. -
:content-type
çalıştıracak fonksiyonun üreteceği MIME tipini belirtiyor ki bu da genellikle "text/html"dir. -
:function
Çalıştırmak istediğimiz fonksiyon.
Bir adresle eşleştirdiğimiz fonksiyon iki parametre alan bir fonksiyon olmalı. Birincisi http istek (request) nesnesi, ikinci ise
publish
fonksiyonunun döndürdüğü "entity" nesnesi.
Bunların değerleri, bir http isteği gerçekleştiğinde,
AllegroServe tarafından fonksiyonumuza veriliyor.
Gelelim oluşturduğumuz fonksiyonun içinde bulunan
with-http-response
ve with-http-body
sembollerine. Bu semboller AllegroServe'de tanımlanmış iki makroyu ifade
ediyor. with-http-response
makrosu sayesinde, kullanıcıya bir
yanıt (response) hazırladığımızı belirtiyoruz. with-http-response
,
gerekli olan HTTP başlık (header) değerlerini bize iş bırakmadan hazır hale
getiriyor. Diğer sevimli makromuz with-http-body
ile artık HTTP
cevabımızın içeriğini olşuturmaya hazır olduğumuzu
belirtiyoruz. İki makroya da fonksiyonumuza gelen parametreleri
vermek zorundayız.
Fonksiyonumuzda ekrana "merhaba dünya" yazısını yazdıran kısım,
format
fonksiyonunu çağırdığımız kısım. format
fonksiyonunu C'deki
sprintf
fonksiyonuna benzetebiliriz (ancak
burada belirtmeliyiz ki format çok güçlü bir fonksiyon olup,
barındırdığı kurallardan ötürü bir mini dil (DSL, Domain Specific
Language) olarak görülmelidir, tıpkı LOOP gibi. Lisp'teki FORMAT
ile ne kadar acayip şeyler yapılabileceğini merak edenler
Practical Common Lisp'in ilgili bölümüne bakabilirler).
Kısaca bir "stream"e,
belirli bir şablona göre oluşturduğu bir karakter
katarını gönderir. Örneğimizde format
'a akım olarak
kullanıcıya yanıt vermek için kullandığımız "stream" nesnesini
veriyoruz. Bu nesneye ulaşmak için de request-reply-stream
fonksiyonunu kullandık.
AllegroServe'ün Apache'nin yaptığı gibi dosyaları ya da dizinleri yayınlamasını istersek, kullanmamız gereken fonksiyonlar
publish-file
ve publish-directory
fonksiyonları.
CL-USER> (publish-file :path "/makale.txt"
:file "/home/hb/lisp-web.01.txt")
#<NET.ASERVE::FILE-ENTITY {98CB3A1}>
Artık http://localhost:3000/makale.txt adresinden ev dizinimdeki lisp-web.01.txt dosyasına ulaşabilir durumdayım.
Şimdi biraz daha gelişmiş bir "merhaba dünya" uygulamasıyla, HTTP isteğinden gelen GET ve POST değişkenlerine nasıl ulaşacağımıza bakalım. Henüz Lisp ile HTML üretmeyi bilmediğimiz için form sayfasını durağan (statik) bir HTML sayfası olarak bir dosyaya kaydediyor ve bu dosyayı yayınlıyoruz.
Form sayfamız:
<html>
<head>
<title>Form sayfasi</title>
</head>
<body>
<form action="/handle-form" method="post">
<span>Gezegen:</span>
<input type="text" name="gezegen" size="40"/>
<input type="submit" value="Yolla gitsin"/>
</form>
</body>