Her şey Altan Tanrıverdi‘nin Ubuntu üzerine nginx kurulumu yazısını görmemle başladı. Hayır, Nginx’i daha önce görmemiş değildim, ancak Tanrıverdi’yi Apache’den başka bir web sunucusu kurmaya yönelten şey Apache’nin sistem kaynakları açısından “pahalı” sayılabilecek bir sunucu olmasıydı.
Apache’de bir web sunucusundan beklediğimiz her şey ve hatta fazlası var. Apache modül listesine (http://httpd.apache.org/modules/ ve http://modules.apache.org/) daha önce göz atmadıysanız bir bakın; belki de adını bile duymadığınız, hiç işiniz düşmemiş olduğu için kullanmadığınız bir çok modül olduğunu göreceksiniz. Oysa çoğu LAMP/LAPP programcısı, günlük işlerinde php ve rewrite modulleri dışındakileri kullanmaya pek ihtiyaç duymaz.
Elbette asla yılların eskitemediği dost Apache’ye kafa tutmak için değildi, ancak kafama şu soruyu takmadan edemedim:
“Acaba yalnız PHP yorumlayan ve başka hiç bir iş yapmayan bir web sunucusunu yine PHP ile kodlasak nasıl olurdu?”
Tabii bu soru büyük olasılıkla benden başka bir sürü kişinin de aklından geçmişti veAmerika’yı yeniden keşfetmemek için önce Hz. Google’a sordum.
Google bana “Nano Web Server var.” dedi. Bir de baktım ki, gerçekten bu arkadaş Apache’yi neredeyse bire-bir PHP’ye port etmiş. Modüllerine kadar yazmış. Ubuntu için deb paketi bile var ve daemon olarak kuruluyor. Öte taraftan bu çalışma hem benim ölçeğimi aşıyor hem de başlangıçtaki mantığımla ters düşüyor: PHP’den başka şey çalıştırmak istesem zaten “runtime’da yorumlanan” değil “derlenmiş” kod kullanmayı tercih ederim. Benim fikrimin özü, zaten native olarak PHP yorumlayabilen bir kod yapısı kullanarak bir web sunucusu yapmak.
Bunun dışında bir de phpStack‘i buldum. phpStack benim amaçladığım şeye oldukça yakındı ancak bana göre işleri olması gerekenden daha karmaşık hale getiriyordu.
Böylece kendi denememi yapmaya karar verdim. Önce php5-cgi paketini kurdum. PHP Manual’dan jenerik bir soket kodu aldım. Kodu düzenledim, mime türlerine göre gönderilecek header’ları ayarladım, PHP yorumlayıcısı desteği verdim ve sonuç:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | <?php interface server { public function setWebRoot($path); public function setPort($port); public function serve(); } final class webserver implements server { private $_port , $_webroot; public function __construct() { //belki ileride lazım olur. şimdilik dummy } public function setPort($port) { $this->_port = $port; } public function setWebRoot($path) { $this->_webroot = $path; } private function _mimeHandler($input) { $fileinfo = pathinfo($input); switch ($fileinfo['extension']) { case 'png': $mime = "text/png"; break; case 'jpg': case 'jpeg': $mime = "text/jpeg"; break; case 'gif': $mime = "text/gif"; break; case 'css': $mime = "text/css"; break; default: $mime = "text/html; charset=UTF-8"; } return $mime; } private function _handleCode($contents) { //eval() edilecek kodun başında <?php ya da <? varsa çakar. halledelim. $contents = trim($contents); if(substr($contents , 0 , 5) == '<?php') { $contents = substr($contents , 5); } elseif(substr($contents , 0 , 2) == '<?') { $contents = substr($contents , 2); } return $contents; } public function serve() { $socket = @socket_create_listen($this->_port); if (!$socket) { echo "Soket olamiyor!\n"; exit; } while (1) //daire-i ilanihaye! { $client = socket_accept($socket); $input = trim(socket_read ($client, 4096)); echo $input; //tarayıcı ne diyor? bu veriyi ileride $_SERVER array'ini doldurmak için kullanmak lazım. $input = explode(" " , $input); $input = $input[1]; $mime = $this->_mimeHandler($input); if ($input == "" || $input == '/') { $input = "/index.html"; } $input = ".$input"; $inputArray = explode('?' , $input); $input = $this->_webroot . $inputArray[0]; if (file_exists($input) && is_readable($input)) { echo "Dosya: $input\n"; $contents = file_get_contents($input); if(strstr($input , '.php')) { $contents = $this->_handleCode($contents); //PHP çalıştıktan sonra sonucu echo etmesini değil, $contents stringine atmasını istiyoruz. ob_start(); eval($contents); //gidip $contents'in üstüne yazmak doğru alışkanlık değil ama yeni değişken register etmek istemiyorum $contents = ob_get_contents(); ob_end_clean(); } $output = "HTTP/1.0 200 OK\r\nServer: HyperTextServer\r\nConnection: close\r\nContent-Type: $mime\r\n\r\n$contents"; } else { $contents = "<title>UHU 404</title><h2>UHU 404</h2>Iın-ıın-ıın-ıın-cav-cav-cav-cav-oooooo - oooooo - zört - zört - zört -zört (araba alarmı efekti) Çok afedersin müdürüm ama yok öyle bir dosya."; $output = "HTTP/1.0 404 OBJECT NOT FOUND\r\nServer: HyperTextServer\r\nConnection: close\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n$contents"; } socket_write($client, $output); socket_close ($client); } socket_close ($socket); } } $server = new webserver(); $server->setWebRoot('/var/www/'); $server->setPort(8008); $server->serve(); |
Bu kodu (örneğin /var/www altına) hyperTextServer.php adıyla kaydediyoruz.
Daha sonra varsayılan olarak görüntülenmesi için örnek bir /var/www/index.html dosyası oluşturuyoruz.
Son olarak
1 | php-cgi /var/www/hyperTextServer.php |
ile sunucuyu çalıştırdıktan sonra tarayıcıdan
http://localhost:8008
adresini çağırdığımızda oluşturduğumuz index.html dosyasının servis edildiğini görüyoruz.
Artık bir adım daha ileri giderek bir PHP dosyası çağırmayı deneyebiliriz:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Bu kodu da /var/www/test.php adıyla kaydedip tarayıcıdan
http://localhost:8008/test.php adresini çağırdığımızda PHP kodumuzun da gayet başarılı şekilde yorumlandığını görüyoruz.
Gördüğünüz gibi test.php dosyası, serverBencmark.txt dosyasına, çalışmasının ne kadar zaman aldığını yeni bir satır olarak yazıyor. Böylece sevdiğimiz “benchmarkçılık” oyununu oynayabileceğiz.
test.php kodunu arka arkaya yüz kere önce kendi yazdığımız sunucu ile 8008′den, daha sonra da yine yüz kere apache üstünden (default 80.port) çalıştırıp basit bir aritmetik ortalama alacağız.
Benim lokal sistemimdeki sonuçlar söyle:
Apache ortalama: 0,0174917006493 ms
Bizim sunucu ortalama: 0,0098775601387 ms
Görüyoruz ki, beklediğimizin aksine PHP üstünde çalışan bizim sunucu, derlenmiş kod olarak çalışan Apache’den iki kat kadar hızlı çalışıyor. Bu durumun açıklamasının aslında çok da zor olmadığını düşünüyorum: Apache bir sürü kontrol yapıyor, aynı anda birden fazla bağlantı kabul edebiliyor, bir sürü port dinliyor, vs. Öte taraftan bizim sunucumuz yalnız PHP kodunu çalıştırmaya odaklanmış. $_SERVER’a, $_REQUEST’e bir değer atamak için tarayıcı verisini işlemiyor ve tek, basit bir süreç olarak çalışıyor.
Aynı işleri bizim sunucumuza yaptırdığımızda, büyük olasılıkla aradaki hız farkı kapanacak. Yine de şu andaki duruma bakarak iki kat yavaşlayacağını öngörmüyorum sonuç olarak başlangıçta umduğumun çok daha fazlasını alabildiğimi gözlemliyorum.
Bundan sonra yapılacaklar:
1- $_SERVER, $_REQUEST, $_POST, $_GET, $_FILES ve $_COOKIE arraylerinin doldurulması.
2- pcntl eklentisi ile süreçlerin “çatallanması”
Yukarıdaki iki maddelik yapılacaklar listesi de temizlendiğinde elimizde basbayağı düzgün çalışan bir web sunucumuz olacak.
Etiketler: Apache, Gnu/Linux, PHP, Web Server, Yazılım
-
yaz geldi yaz,
litespeed veya ngix kurup hayatı yaşamak varken bu icadlar ne diye
-
Çok içtenlikle söyleyeyim, naif bir düşünce tarzı. Ama sonuçlarının başarısız olacağı ne yazık ki garanti.
Öncelikle, bir soket uygulaması için internal olarak “threading” ya da “forking” desteklemeyen bir yorumlayıcı kullanmak sonucun hüsran olmasını garanti ediyor.
Bundan başka, php’nin eval ile ve yine naif bir düşünce ile sadece başında ve sonunda başlangıç – bitiş etiketi olacağını varsayarak çalıştırmak, işletmek çok da akıllıca bir hareket değil. PHP’nin kendi içindeki “form”suzluğunun, bu naif düşüncenin ilk formdan bağımsız kod parçasında çatlamasına neden olacağı ortada.
Bunlardan başka, uygulamanın, bir güvenlik modeli, bir loglama modeli, bir eklenti modeli vb. sunmak gibi dertlerinin olmaması, gerçek bir sunucu olmasının önündeki bir set. Bu haliyle, sadece deneysel bir çalışma.
Aynı fikrin,
– perl, ruby, c ya da java gibi bir wrapper sayesinde forking ve/veya threading desteği edinmesi
– php işletiminin fast-cgi yolu ile sağlanması
– temel bir erişim/hata loglama mekanizması eklenmesiile uygulanması, programın çok daha başarılı ve gelecek vaadeden bir hale gelmesini sağlardı.
Yine de eline sağlık
-
Bu şartlarda, eval yerine “include” kullanılabilir. Ancak eval ve include ikisinde de dert, çalıştırılan kodların, sunucunun global isim uzayına erişimi olması.
php-cgi komut satırı uygulaması evet fast-cgi desteği ile geliyor. Ancak fast-cgi çalışabilmesi için, bu yorumlayıcı ile fastcgi protokolü ile iletişime girilmesi gerekiyor.
Naif derken, boşa hayal demek gibi bir niyetim hiç olmadı.
-
Bu yorumu yazan arkadaş (@ediz) acaba ne cevap verildi diye gelip bir kez bakmaz bile bu sayfaya öyle boş boş konuşurlar.


10 yorum
Bu yazının yorumlarına abone ol
İzleme linki: http://can.logikit.net/2010/04/20/bir-web-sunucusu-kodlama-denemesi/trackback/