Skip to content

17. Web hizmetleri

Not: Burada “web hizmeti” terimi, bir istemci tarafından kullanılan ham verileri sağlayan tüm web uygulamalarını ifade eder; aşağıdaki örneklerde ise bir konsol komut dosyası kastedilmektedir. Belirli bir teknolojiyle ilgilenmiyoruz; örneğin, REST (REpresentational State Transfer) veya SOAP (Simple Object Access Protocol) gibi, iyi tanımlanmış bir formatta az ya da çok ham veriler sağlayan teknolojiler. REST, jSON verisini sağlarken, SOAP ise XML verisini sağlar. Bu teknolojilerin her biri, istemcinin sunucuya nasıl sorgu göndermesi gerektiğini ve sunucunun yanıtının nasıl olması gerektiğini ayrıntılı olarak tanımlar. Bu derste, istemci sorgusunun ve sunucu yanıtının niteliği konusunda çok daha esnek davranacağız. Bununla birlikte, yazılan komut dosyaları ve kullanılan araçlar, REST teknolojisindekilere yakındır.

17.1. Introduction

PHP programları bir WEB sunucusu tarafından çalıştırılabildiğinden, bu tür bir program birden fazla istemciye hizmet verebilen bir sunucu programı haline gelir. İstemci açısından bir web hizmetini çağırmak, bu hizmetin URL'sini talep etmekle eşdeğerdir. İstemci, herhangi bir dilde, özellikle de PHP ile yazılabilir. Bu son durumda, az önce incelediğimiz ağ işlevleri kullanılır. Ayrıca, bir web hizmetiyle “iletişim kurabilmemiz”, yani bir WEB sunucusu ile istemcileri arasındaki http iletişim protokolünü anlamamız gerekir. Bağlantı paragrafının amacı da buydu.

"Bağlantı" başlıklı paragrafta açıklanan web istemcisi, HTTP protokolünün bir kısmını keşfetmemizi sağladı.

Image

En basit haliyle, istemci/sunucu arasındaki iletişim şu şekildedir:

  • istemci, web sunucusunun 80 numaralı bağlantı noktasına bir bağlantı açar;
  • bir belgeye ilişkin bir istek gönderir;
  • web sunucusu istenen belgeyi gönderir ve bağlantıyı kapatır;
  • ardından istemci de bağlantıyı kapatır;

Belge çeşitli türlerde olabilir: HTML formatında bir metin, bir resim, bir video… Bu, mevcut bir belge (statik belge) veya bir komut dosyası tarafından anında oluşturulan bir belge (dinamik belge) olabilir. İkinci durumda, web programlamasından söz edilir. Dinamik belge oluşturma komut dosyası çeşitli dillerde yazılabilir: PHP, Python, Perl, Java, Ruby, C#, VB.net…

Bundan sonra, metin belgelerini dinamik olarak oluşturmak için PHP komut dosyalarını kullanacağız.

Image

  • [1]'te, istemci sunucuya bir bağlantı kurar, bir PHP betiği ister ve bu betiğe parametre gönderir ya da göndermez;
  • [2]'te, web sunucusu PHP betiğini PHP yorumlayıcısı aracılığıyla çalıştırır. Betik, [3] istemcisine gönderilen bir belge oluşturur;
  • sunucu bağlantıyı sonlandırır. İstemci de aynısını yapar;

Web sunucusu aynı anda birden fazla istemciyi işleyebilir.

[Laragon] yazılım paketinde, web sunucusu Apache Foundation’ın (http://www.apache.org/) açık kaynaklı bir sunucusu olan Apache’dir. Aşağıdaki uygulamalarda [Laragon] çalıştırılmalıdır:

Image

Bu komut, Apache web sunucusunu ve SGBD ile MySQL'i başlatır.

Web sunucusu tarafından çalıştırılan komut dosyaları NetBeans aracıyla yazılacaktır. Şimdiye kadar konsol ortamında çalıştırılan PHP komut dosyalarını yazdık:

Image

Kullanıcı, konsolu kullanarak bir PHP betiğinin çalıştırılmasını talep eder ve sonuçları alır.

Aşağıda ele alınacak istemci/sunucu uygulamalarında:

  • istemci komut dosyası konsol ortamında çalıştırılır;
  • sunucu komut dosyası web ortamında çalıştırılır;

Image

Sunucunun PHP komut dosyası, dosya sisteminde herhangi bir yerde bulunamaz. Çünkü web sunucusu, kendisine istenen statik ve dinamik belgeleri yapılandırmada belirtilen yerlerde arar. Laragon'un varsayılan yapılandırması, belgelerin <Laragon>/www klasöründe aranmasını sağlar; burada <Laragon>, Laragon'un kurulum klasörüdür. Dolayısıyla, bir web istemcisi URL [http://localhost/D] yolundaki D belgesini talep ederse, web sunucusu ona [<Laragon>/www/D] yolundaki D belgesini sunacaktır.

Aşağıdaki örneklerde, sunucu komut dosyalarını [www/php7/scripts-web] klasörüne yerleştireceğiz. Bir sunucu komut dosyasının adı S.php ise, web sunucusuna URL ve [http://localhost/php7/scripts-web/S.php] ile talep edilecektir. Böylece, [<Laragon>/www/php7/scripts-web/S.php] adlı belge sunulacaktır.

Image

  • [1]'e, [<laragon>/www] dosyası;
  • [2] içinde, [php7/scripts-web] dosyası;

NetBeans ile sunucu komut dosyaları oluşturmak için şu adımları izleyeceğiz:

Image

  • [1-2]'te yeni bir proje oluşturuyoruz
  • [3-4]'te, [PHP] kategorisini ve [PHP Application] projesini seçiyoruz

Image

  • [5]'te, projenin adı;
  • [6]'te, dosya sistemindeki proje klasörü. Bunun olması gerektiği gibi [<laragon>/www] klasöründe bulunduğuna dikkat edin;
  • [7-8]'te, önerilen varsayılan değerleri kabul edin;
  • [9-10]'te, önerilen varsayılan değerleri kabul edin. [10]'te, bu projeye yerleştireceğimiz komut dosyalarının URL'in [http://localhost/php7/scripts-web/] yoluyla başlayacağına dikkat edin;

Image

  • [11]'te, PHP ile yazılmış web çerçeveleri sunulmaktadır. Bu çerçeveler, web uygulaması biraz büyüdüğünde vazgeçilmez hale gelir;
  • [12]'te, [Composer] aracı kullanılarak PHP kütüphaneleri eklenebilir. Bu aracı, Laragon'daki bir [Terminal] penceresinde iki kez kullandık:
    • e-posta göndermeye olanak tanıyan [SwiftMailer] kütüphanesini yüklemek için;
    • e-postaları okumayı sağlayan [php-mime-mail-parser] kütüphanesini yüklemek için;
  • [13]'te, proje oluşturma sihirbazı onaylandıktan sonra proje, projeler sekmesinde [13] olarak görünür;

17.2. Statik bir sayfa yazma

Not: Devam etmek için [Laragon]'in çalışır durumda olması gerekir.

NetBeans kullanarak HTML (HyperText İşaretleme Dili) statik sayfasının nasıl oluşturulacağını göstereceğiz:

Image

  • [1-5] içinde, [01] adında bir klasör oluşturuyoruz;

Image

Image

  • [6-12] dosyasını, HTML ve [exemple-01.html] dosyalarını oluşturuyoruz;

[exemple-01.html] dosyası şu şekilde önceden doldurulmuş olarak oluşturulur (Mayıs 2019):


<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div>TODO write content</div>
    </body>
</html>

İçeriğini şu şekilde değiştirelim:


<!DOCTYPE html>
<html>
    <head>
        <title>PHP7 par l'exemple</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div><b>Ceci est un exemple de page statique</b></div>
    </body>
</html>

Sayfanın başlığını (4. satır) ve içeriğini (9. satır) değiştirdik.

Şimdi bu HTML sayfasını Laragon’un Apache sunucusu üzerinden görüntüleyelim:

Image

  • [1-2]'te, sayfayı Laragon'un Apache sunucusu üzerinden görüntüleyelim;
  • [3]'te, görüntülenen sayfanın URL'i;
  • [4]'te, değiştirdiğimiz başlığı;
  • [5]'te, değiştirdiğimiz içeriği;

Görüntülenen sayfa statik bir sayfadır: tarayıcıda istediğimiz kadar yükleyebiliriz (F5), her seferinde aynı içerik görüntülenir.

Çoğu tarayıcı, “bağlantı” başlığı altında açıklanan, istemci ile sunucu arasında alışverişi yapılan verilere erişim sağlar. Firefox tarayıcısında (Mayıs 2019), bu verilere erişmek için F12 komutunu girmek gerekir:

Image

[1]'te belirtildiği gibi, sayfayı yeniden yükleyelim (F5):

Image

  • [2]'te, tarayıcı tarafından yüklenen belgeyi seçelim;

Image

  • [5]'e, analiz edilecek belge seçilir;
  • [3-4]'te, istemci/sunucu arasındaki iletişimleri görüntülemeyi talep ediyoruz;
  • [6]'te, bu iletişimler;

Image

  • [7]'te, başlık sekmesi seçilir;
  • [8]'te, tarayıcı tarafından istenen URL;
  • [9]'te, sunucuya gönderilen komut [GET http://localhost/php7/scripts-web/01/exemple-01.html HTTP/1.1]'tir;
  • [10]'te, tarayıcı (istemci) tarafından daha sonra gönderilen HTTP başlıkları;
  • [11]'te, sunucunun yanıtındaki HTTP başlıkları;

Image

  • [12-14]'te, HTTP başlıklarından sonra gönderilen sunucu yanıtı;
  • [14]'te, istemci tarayıcısının oluşturduğumuz HTML sayfasını aldığını görüyoruz. Ardından bu kodu yorumlayarak aşağıdakini görüntüledi:

Image

17.3. PHP formatında dinamik bir sayfa oluşturma

Şimdi PHP formatında dinamik bir sayfa yazıyoruz:

Image

Image

  • [1-8]'te, bir [exemple-01.php] sayfası oluşturuyoruz;

[exemple-01.php] dosyası şu şekilde önceden doldurulmuş olarak oluşturulur (Mayıs 2019):


<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <?php
        // kodunuzu buraya yazın
        ?>
    </body>
</html>

Yukarıdaki kodu şu şekilde değiştiriyoruz:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemple de page dynamique</title>
    </head>
    <body>
        <?php
        // zaman: şu an ile 01/01/1970 arasındaki milisaniye sayısı
        // tarih-saat görüntüleme biçimi
        // d: 2 basamaklı gün
        // m: 2 basamaklı ay
        // y: 2 basamaklı yıl
        // H: saat 0,23
        // I: dakika
        // s: saniye
        print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
        ?>
    </body>
</html>

Açıklamalar

  • 5. satır: sayfanın başlığını değiştirdik;
  • 17. satır: Şu anki tarih ve saati yazıyor;

Temel olarak, yukarıdaki PHP komut dosyası, konsola geçerli saati yazdırır. Ancak, bir web sunucusu tarafından çalıştırıldığında, normalde komut dosyasının çalıştırıldığı konsola yönlendirilen [print] komutunun çıktı akışı, burada sunucuyu istemcisiyle bağlayan bağlantıya yönlendirilir. Dolayısıyla, bir web ortamında yukarıdaki komut dosyası, geçerli saati metin biçiminde istemciye (burada bir tarayıcıya) gönderir.

[exemple-01.php] betiğini çalıştıralım:

Image

  • [3] olarak, Apache web sunucusuna istenen URL;
  • [4]'e, değiştirdiğimiz sayfanın başlığı;
  • [5]'te, [print] komutuyla oluşturulan içerik;

Burada dinamik bir sayfa ile karşı karşıyayız; çünkü sayfayı tarayıcıda birkaç kez yeniden yüklediğimizde (F5), içeriği değişir (saat değişir).

Tarayıcı, HTML akışını aldı. Bunu öğrenmek için, tarayıcıda sayfanın kaynak kodunu görüntülemek gerekir:

Image

  • [1] menüsünü görmek için tarayıcıda sayfaya sağ tıklayın;
  • [2]'te, [exemple-01.php] sayfasının URL'i, ancak önüne [view-source :] ve [3] önekleri eklenmiş hali;
  • [4] olarak, tarayıcının görüntülediği HTML içeriği;

Bu nedenle, bir web sunucusu tarafından çalıştırılmak üzere tasarlanmış bir PHP betiğinin bir HTML akışı üretmesi gerektiğini unutmamak gerekir.

Şimdi (F12) sunucu tarafından istemci tarayıcısına gönderilen HTTP başlıklarına bir göz atalım:

Image

  • [3]'te, statik sayfa istendiğinde mevcut olmayan bir HTTP başlığı bulunmaktadır. Bu başlık, sunucunun yanıtının bir PHP betiği tarafından oluşturulduğunu göstermektedir;

Sunucunun yanıtının (burada HTML akışı) bir PHP betiği tarafından oluşturulabileceğini gördük. Bu betik, HTTP başlıklarını ve sunucu yanıtındaki hemen hemen tüm öğeleri de oluşturabilir.

17.4. HTML Dilinin Temelleri

Bu bölüm, WEB dilinde PHP programlama konusuna ayrıntılı olarak girmeyecektir. MVC web uygulaması, bağlantı paragrafında geliştirilmiştir. Bu bölüm daha çok web hizmetlerine odaklanmaktadır: bir web sunucusu aracılığıyla diğer PHP istemcilere veri sağlayan PHP sayfaları. Bununla birlikte, okuyucuya HTML hakkında bazı temel bilgiler vermek yararlı görünmüştür.

Bir web tarayıcısı çeşitli belgeleri görüntüleyebilir; bunlardan en yaygın olanı HTML (HyperText İşaretleme Dili) belgesidir. Bu belge, <balise>texte</balise> biçimindeki etiketlerle biçimlendirilmiş bir metindir. Örneğin, <b>important</b> metni, important metnini kalın olarak görüntüler. Yalnızca tek başına kullanılan etiketler de vardır; örneğin, <hr/> etiketi yatay bir çizgi görüntüler. HTML metninde bulunabilecek etiketleri burada ele almayacağız. Tek bir satır HTML kodu yazmadan bir WEB sayfası oluşturmaya olanak tanıyan birçok WYSIWYG yazılımı mevcuttur. Bu araçlar, fare ve önceden tanımlanmış denetimler kullanılarak oluşturulan bir sayfa düzeninin HTML kodunu otomatik olarak üretir. Böylece sayfaya (fareyle) bir tablo ekleyebilir ve ardından yazılım tarafından oluşturulan HTML koduna bakarak, bir WEB sayfasında tablo tanımlamak için kullanılacak etiketleri öğrenebilirsiniz. İşin sırrı bundan ibarettir. Ayrıca, dinamik web uygulamalarının HTML kodunu kendileri oluşturup WEB istemcilere göndermeleri gerektiğinden, HTML diline hakim olmak zorunludur. Bu kod program tarafından oluşturulur ve elbette, kullanıcının istediği web sayfasını alabilmesi için neyin oluşturulması gerektiğini bilmek gerekir.

Özetle, web programlamaya başlamak için HTML dilini tamamen bilmek gerekmez. Ancak bu bilgi gereklidir ve WYSIWYG gibi web sayfası oluşturma yazılımları (WEB) ile DreamWeaver ve diğer onlarca yazılımın kullanımı yoluyla edinilebilir. HTML dilinin inceliklerini keşfetmenin bir başka yolu da, internette gezinerek ilginç ve sizin için henüz bilinmeyen özellikler içeren sayfaların kaynak kodlarını incelemektir.

Aşağıdaki örneği ele alalım; bu örnekte, bir WEB belgesinde bulunabilecek bazı öğeler gösterilmektedir, örneğin:

  • bir tablo;
  • bir resim;
  • bir bağlantı.

Image

Bir HTML belgesinin genel biçimi şöyledir:

<html> <head> <title>Bir başlık</title> ... </head> <body öznitelikleri> ... </body></html>

Belgenin tamamı <html>…</html> etiketleri ile çevrelenmiştir. Belge iki bölümden oluşur:

  1. <head>…</head>: Bu, belgenin görüntülenemeyen kısmıdır. Belgeyi görüntüleyecek tarayıcıya bilgi verir. Burada genellikle tarayıcının başlık çubuğunda görüntülenecek metni belirleyen <title>…</title> etiketleri bulunur. Ayrıca, belgenin anahtar kelimelerini tanımlayan ve daha sonra arama motorları tarafından kullanılan diğer etiketler de bulunabilir. Bu bölümde, çoğunlukla javascript veya vbscript ile yazılmış ve tarayıcı tarafından çalıştırılacak komut dosyaları da bulunabilir.
  1. <body öznitelikleri>…</body>: Bu, tarayıcı tarafından görüntülenecek kısımdır. Bu kısımda yer alan etiketler, tarayıcıya belgenin "istenen" görsel biçimini belirtir. Her tarayıcı bu etiketleri kendine özgü bir şekilde yorumlar. Bu nedenle, iki farklı tarayıcı aynı web belgesini farklı şekilde görüntüleyebilir. Bu durum genellikle web tasarımcıları için bir baş ağrısıdır.

Örnek belgemizin HTML kodu şöyledir:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Quelques balises HTML</title>
    </head>

    <body style="background-image: url(images/standard.jpg)">
        <h1 style="text-align: left">Quelques balises HTML</h1>
        <hr />

        <table border="1">
            <thead>
                <tr>
                    <th>Colonne 1</th>
                    <th>Colonne 2</th>
                    <th>Colonne 3</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>cellule(1,1)</td>
                    <td style="text-align: center;">cellule(1,2)</td>
                    <td>cellule(1,3)</td>
                </tr>
                <tr>
                    <td>cellule(2,1)</td>
                    <td>cellule(2,2)</td>
                    <td>cellule(2,3</td>
                </tr>
            </tbody>
        </table>
        <br/><br/>
        <table border="0">
            <tr>
                <td>Une image</td>
                <td>
                    <img border="0" src="images/cerisier.jpg"/></td>
            </tr>
            <tr>
                <td>Le site de Polytech'Angers</td>
                <td><a href="http://www.polytech-angers.fr/fr/index.html">ici</a></td>
            </tr>
        </table>
    </body>
</html>
Elément
HTML etiketleri ve örnekleri
titre du document
<title>Bazı etiketler HTML</title> (5. satır)
[Quelques balises HTML] metni, belgeyi görüntüleyen tarayıcının başlık çubuğunda görünecektir
barre horizontale
<hr /> : yatay bir çizgi gösterir (satır 10)
tableau
<table öznitelikleri>….</table> : tabloyu tanımlamak için (satır 12, 32)
<thead>…</thead> : sütun başlıklarını tanımlamak için (13. ve 19. satırlar)
<tbody>…</tbody>: tablonun içeriğini tanımlamak için (20. ve 31. satır)
<tr öznitelikleri>…</tr>: bir satırı tanımlamak için (21. ve 25. satırlar)
<td öznitelikler>…</td>: bir hücreyi tanımlamak için (22. satır)
örnekler:
<table border="1">…</table>: border özniteliği, tablonun kenarlığının kalınlığını belirler
<td style="text-align: center;">hücre(1,2)</td> (satır 23): içeriği hücre(1,2) olacak bir hücre tanımlar. Bu içerik yatay olarak ortalanır (text-align: center).
image
<img border="0" src="images/cerisier.jpg"/> (satır 38): web sunucusundaki [images/cerisier.jpg] dosyasını kaynak alan, kenarlığı olmayan (border="0") bir resim tanımlar (src="images/cerisier.jpg"). Bu bağlantı, URL http://localhost/php7/scripts-web/01/balises.html ile elde edilen bir web belgesinde yer almaktadır. Ayrıca, tarayıcı burada referans verilen görüntüyü almak için http://localhost/php7/scripts-web/01/images/cerisier.jpg adresindeki URL dosyasını isteyecektir.
lien
<a href="http://www.polytech-angers.fr/fr/index.html">burada</a> (42. satır): ici metninin http://www.polytech-angers.fr/fr/index.html adresindeki URL dosyasına bağlantı olarak işlev görmesini sağlar.
fond de page
<body style="background-image: url(images/standard.jpg)"> (8. satır): Sayfa arka planı olarak kullanılacak resmin, WEB sunucusundaki URL [images/standard.jpg] adresinde bulunduğunu belirtir. Örneğimizde, tarayıcı bu arka plan resmini almak için http://localhost/php7/scripts-web/01/images/standard.jpg adresindeki URL dosyasını isteyecektir.

Bu basit örnekte görüldüğü gibi, belgenin tamamını oluşturmak için tarayıcının sunucuya üç istek göndermesi gerekir:

  1. http://localhost/php7/scripts-web/01/images/balises.html adresine, belgenin HTML kaynağını almak için
  2. http://localhost/php7/scripts-web/01/images/cerisier.jpg adresi, cerisier.jpg resmini almak için
  3. http://localhost/php7/scripts-web/01/images/standard.jpg adresinden standard.jpg arka plan resmini elde etmek için

Müşteri ile sunucu arasındaki ağ iletişimi (tarayıcıda F12) bunu göstermektedir:

Image

  • [3-5]'te, tarayıcı tarafından yapılan üç istek görülebilir;

17.5. Statik bir sayfayı dinamik hale getirme

HTML ve [exemple-01.html] sayfalarını nasıl dinamik hale getirebileceğimizi gösterelim. İçeriği kopyalayalım

Image

[exemple-01.html] dosyasının içeriğini [page-01.php] dosyasına kopyaladık. [2] adlı bu web betiğini çalıştırırsak, tarayıcıda şu sonuçla karşılaşırız:

Image

  • [3] dosyasında, istenen URL dosyası;
  • [4]'te, sayfanın başlığı;
  • [5], sayfanın içeriği;

Tarayıcı tarafından alınan kodu görüntülediğimizde şunu görürüz:

Image

  • [7]'te, HTML kodu, [exemple-01.php] komut dosyasına yerleştirilmiştir

PHP yorumlayıcısı, [page-01.php] betiğini yorumladı ve [exemple-01.html] statik sayfasıyla aynı HTML akışını üretti. [page-01.php] betiğinde PHP yoktu, sadece HTML vardı. Buradan şunu öğreniyoruz: PHP yorumlayıcısı, bir PHP betiğinde HTML bulduğunda, buna dokunmaz ve olduğu gibi istemciye gönderir.

Şimdi, PHP yorumlayıcısının yapacak bir işi olması için [page-01.php] betiğine birkaç PHP komutu ekleyelim:


<!DOCTYPE html>
<html>
    <head>
        <title><?php print $page->title ?></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div><b><?php print $page->contents ?></b></div>
    </body>
</html>
  1. ve 9. satırlara, sayfanın başlığını ve içeriğini dinamik olarak oluşturmak için PHP kodu ekledik. Burada, [$page] değişkeninin, görüntülenecek verileri içeren bir nesne olduğu varsayımıyla hareket ediyoruz.

Bu yeni kodu çalıştırdığımızda, tarayıcıda şu sonuç ortaya çıkar:

Image

  • [1]'te, istenen URL;
  • [2]'te, [$page] değişkeni tanımlanmadığı için sayfanın başlığı görüntülenemedi;
  • [3]'te, içerik için de aynı durum geçerli;

Şimdi, aşağıdaki [exemple-02.php] web betiğini yazalım:

Image

[exemple-02.php] betiği şu şekilde olacaktır:


<?php

// görüntülenecek sayfa öğeleri tanımlanır
$page=new \stdclass();
$page->title="Un nouveau titre";
$page->contents="Un nouveau contenu généré dynamiquement";
// [page-01] görüntülenir
require_once "page-01.php";
  • 4-6. satırlar: [$page] nesnesini tanımlıyoruz;
  • 8. satır: [page-01.php] komut dosyası eklenir. Bu komut dosyasının kodu da sırayla yorumlanacaktır:
    • [$page] değişkeni artık tanımlanmıştır ve PHP yorumlayıcısı bunu kullanacaktır;
    • [page-01.php]'in HTML kodu olduğu gibi istemciye gönderilecektir;
    • PHP ve [print] işlemlerinin sonuçları, müşteriye gönderilen metin akışına dahil edilecek;

Şimdi [exemple-02.php] web betiğini çalıştırırsak, tarayıcıda şu sonuçları elde ederiz:

Image

Tarayıcı tarafından alınan metin içeriğini görüntülersek:

Image

  • PHP kodları, [2] ve [3]'te yer alırken, iki [print] komutunun sonuçlarıyla değiştirilmiştir;

Bu örnekten iki şey çıkarılabilir:

  • tarayıcıya yönelik HTML sayfaları, yalnızca bu HTML kodunu ve PHP kodu tarafından oluşturulan birkaç dinamik bölümü içeren PHP komut dosyalarında izole edilebilir. Bu sayfalarda mümkün olduğunca az PHP bulunmalıdır;
  • HTML sayfalarında yer alan dinamik verileri oluşturan tüm mantık, sayfa sunum kodunu içermeyen saf PHP komut dosyalarında izole edilmelidir (HTML, CSS, Javascript…) içermeyen saf PHP komut dosyalarına ayrılmalıdır;

Bu, görevlerin ayrılmasını sağlar:

  • görüntülenecek web sayfalarının oluşturulması görevi (HTML, CSS, Javascript…);
  • oluşturduğumuz web uygulamasının mantığıyla ilgili görev. Bu mantık, konsol komut dosyalarında yaptığımız gibi üç katmanlı bir mimari ile uygulanabilir;

Daha sonra, özel web komut dosyaları oluşturacağız;

  • bu komut dosyaları, istemciye yalnızca veri gönderecek, herhangi bir biçimlendirme yapmayacaktır (HTML, CSS, Javascript). Dolayısıyla bunlar, web sayfalarından ziyade veri sunucuları olacaktır;
  • Bu web komut dosyalarının istemcileri, sunucu tarafından gönderilen verileri alıp bunlarla bir şeyler yapmakla görevli konsol komut dosyaları olacaktır;

17.6. Tarih/saat istemci/sunucu uygulaması

Şimdi şu yapılandırmaya geçiyoruz:

Image

Şunu yazacağız:

  • Müşterisine o anki tarih ve saati gönderen [1] adlı bir web betiği;
  • web betiğinin istemcisi olacak bir konsol betiği [2]: bu betik, web betiği tarafından gönderilen tarih ve saati alacak ve bunları konsolda görüntüleyecek;

Image

  • [1] içinde, [date-time-server.php] web betiği;
  • [2]'te, web betiğinin istemci tarafındaki [date-time-client] konsol betiği;

17.6.1. Sunucu betiği

Bağlantı paragrafında, şu anki tarih ve saati oluşturan bir web betiği yazmıştık. Bu, aşağıdaki [exemple-01.php] betiğiydi:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemple de page dynamique</title>
    </head>
    <body>
        <?php
        // time: 01/01/1970'den bu yana geçen milisaniye sayısı
        // tarih-saat görüntüleme biçimi
        // d: 2 basamaklı gün
        // m: 2 basamaklı ay
        // y: 2 basamaklı yıl
        // H: saat 0,23
        // i: dakika
        // s: saniye
        print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
        ?>
    </body>
</html>

Verileri biçimlendirmeden ham haliyle sunan veri sunucuları yazacağımızı söylemiştik: HTML. [date-time-server.php] sunucu betiği ise şu şekilde olacaktır:


<?php

// başlık sabitlenir HTP [Content-Type]
header('Content-Type: text/plain; charset=UTF-8');
//
// tarih ve saat gönderilir
// zaman: 01/01/1970'ten bu yana geçen milisaniye sayısı
// tarih-saat görüntüleme biçimi
// d: 2 basamaklı gün
// m: 2 basamaklı ay
// y: 2 basamaklı yıl
// H: saat 0,23
// i: dakika
// s: saniye
print date("d/m/y H:i:s", time());
  • 4. satır: HTTP [Content-Type] başlığını belirleriz; bu, istemciye alacağı belgenin türünü bildirir. Şimdiye kadar [Content-Type] şuydu: [Content-Type: text/html; charset=UTF-8]. Burada, müşteriye belgenin HTML biçiminde, biçimlendirilmemiş metin olduğunu bildiriyoruz. Bu, bu başlığı kullanmayacak olan konsol müşterimiz için önemli değildir. Bu, bu başlığı kullanan tarayıcı müşterileri için daha önemlidir;

Şu sunucu komut dosyasını çalıştıralım:

Image

Tarayıcıda sunucunun yanıtını (F12) incelersek, [5]'te sunucu betiğinin ayarladığı HTTP başlığını ve [8]'te alınan metin belgesini görürüz;

Image

17.6.2. İstemci komut dosyası

"Bağlantı" başlığı altında, birkaç HTTP istemcisi geliştirdik. Bunları, [date-time-server.php] sunucu betiği tarafından gönderilen metin belgesini almak için kullanabiliriz. Ancak bunu yapmayacağız. SMTP ve IMAP protokollerinde yaptığımız gibi, bir üçüncü taraf kütüphane, yani Symfony [https://symfony.com/doc/master/components/http_client.html] çerçevesinin [HttpClient] bileşenini kullanacağız.

Önceki iki kütüphanede olduğu gibi, Symfony’nin [HttpClient] bileşenini kurmak için [Composer] aracını kullanacağız. Laragon’daki bir [Terminal] penceresinde (bkz. bağlantı paragrafı), aşağıdaki komutu yazıyoruz:

Image

  • [3] komutunda, <laragon> Laragon'un kurulum klasörü olduğundan, [<laragon>/www/] klasöründe olduğunuzdan emin olun;
  • [4]'e geçin, [composer] komutunu girin; bu komut, Symfony'nin [HttpClient] kütüphanesini yükler;
  • [5]'te, [HttpClient] kütüphanesi bu bilgisayara zaten yüklenmiş olduğu için hiçbir şey yüklenmez;
  • [6-7]'te, [<laragon>/www/vendor/symfony] içinde yeni klasörler ortaya çıkıyor;

[5] yerine, aşağıdakine benzer bir şey görmelisiniz:


C:\myprograms\laragon-lite\www
? composer require symfony/http-client
Using version ^4.3 for symfony/http-client
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 4 installs, 0 updates, 0 removals
  - Installing symfony/polyfill-php73 (v1.11.0): Downloading (100%)
  - Installing symfony/http-client-contracts (v1.1.1): Downloading (100%)
  - Installing psr/log (1.1.0): Loading from cache
  - Installing symfony/http-client (v4.3.0): Downloading (100%)
Writing lock file
Generating autoload files

[<laragon>/www/vendor] klasörünün, projenizin [Include Path] dalının bir parçası olduğundan emin olun (bkz. bağlantı paragrafı):

Image

Bu işlem tamamlandıktan sonra, [date-time-client.php] konsol komut dosyasını yazabiliriz:

Image

[date-time-client.php] konsol komut dosyası, aşağıdaki jSON ve [config-date-time-client.json] dosyalarını işleyecektir:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/02/date-time-server.php"
}
  • 2. satır: sunucu komut dosyasındaki URL;

[date-time-client.php] istemci komut dosyası şu şekilde olacaktır:


<?php

// tarih/saat hizmeti istemcisi
//
// hata yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-date-time-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create();

try {
  // istek gönderiliyor
  $response = $httpClient->request('GET', $config['url']);
  // yanıt durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıkları alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // yanıt gövdesi alınır
  $content = $response->getContent();
  // görüntüleniyor
  print "---Réponse du serveur : [$content]\n";
} catch (TypeError | RuntimeException $ex) {
  // hatası görüntüleniyor
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
  exit;
}

Açıklamalar

  • 10. satır: önceki kütüphanelerde yaptığımız gibi, [<laragon>/www/vendor/autoload.php] dosyasını yükliyoruz;
  • 11. satır: Kullanacağımız [HttpClient] sınıfını tanımlıyoruz;
  • 13-24. satırlar: [$config] sözlüğünden komut dosyasının yapılandırmasını alıyoruz;
  • 27. satır: [HttpClient] türünde bir nesne oluşturuyoruz;
  • 31. satır: GET komutunu kullanarak sunucu betiğinden URL'i istiyoruz: [GET URL HTTTP/1.1]. Bu işlem asenkron olarak gerçekleşir. Yanıt alınana kadar beklenmeden 33. satırda yürütme devam eder;
  • 33. satır: Yanıtın durumu sorgulanır. Bu durum, sunucu tarafından geri gönderilen ilk HTTP başlığında bulunur. Dolayısıyla, bu başlık [HTTP/1.1 200 OK] ise, yanıtın durumu 200'dür. Bu işlem engelleyicidir: yalnızca istemci sunucudan yanıtın tamamını aldığında geri dönülür;
  • 37. satır: Yanıtın HTTP başlıkları istenir;
  • 42. satır: Sunucu tarafından gönderilen belge istenir: Bu belgenin burada bir metin olduğu bilinmektedir.
  • 45-49. satırlar: Hata durumunda hata mesajı görüntülenir;

İstemci betiği çalıştırıldığında (sunucu betiğine erişilebilmesi için Laragon'un çalışıyor olması gerekir), konsolda şu sonuç elde edilir:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Thu, 30 May 2019 14:42:03 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
content-length: 17
content-type: text/plain; charset=UTF-8
---Réponse du serveur : [30/05/19 14:42:03]
  1. satırda, o anki tarih ve saati doğru bir şekilde alıyoruz.

İstemci komut dosyasının sunucuya ne gönderdiğini merak edebilirsiniz. Bunun için genel amaçlı TCP sunucumuzu kullanacağız (bkz. bağlantı paragrafı):

Image

  • [1]'te, yardımcı programlar klasörü;
  • [2]'te, TCP sunucusu 100 numaralı bağlantı noktasında başlatılır;
  • [3]'te, klavyeden girilecek bir komut beklenir;

[date-time-client.php] komut dosyasının yapılandırma dosyasını değiştiriyoruz:


{
    "url": "http://localhost:100/php7/scripts-web/02/date-time-server.php"
}

Bu sefer, istemci 100 numaralı bağlantı noktası üzerinden [localhost] sunucusuna bağlanıyor. Dolayısıyla, genel TCP sunucumuz çağrılacak. [date-time-client.php] konsol komut dosyasını çalıştırdığımızda, genel TCP sunucusunun konsolu şu şekilde değişir:

Image

  • [3]’e, istemci komut dosyası tarafından oluşturulan HTTP GET komutuna;
  • [4]'e, konsol betiğinin imzası;
  • [5]'te, sunucunun istemci komut dosyasına verdiği yanıt. Bunun geçerli bir HTTP yanıtı olmadığına dikkat edilmelidir:
    • HTTP başlıkları bulunmalıdır;
    • ardından boş bir satır;
    • ardından istemciye gönderilen metin belgesi;
  • [6]'te, istemci komut dosyasının yanıtın tamamını aldığını algılaması için iletişim sonlandırılır;

İstemci komut dosyası tarafında ise konsolda şu çıktı görülür:

Image

  • [7]'te, Symfony istemcisinin aldığı içerik;

17.6.3. Sunucu komut dosyası – sürüm 2

Temel olarak, bir web betiği yazmak için kullanılan PHP işlevleri nesne yönelimli değildir. Sunucu tarafında, bu nedenle sınıfları ve klasik PHP işlevlerini bir arada kullanmak zorunda kalırız. Daha tutarlı bir kod yazımı elde etmek için, Symfony çerçevesinin [HttpFoundation] kütüphanesini kullanacağız. Bu kütüphane, bir web hizmeti için gerekli tüm klasik PHP işlevlerini bir sınıf ve arayüz sistemine sarmalamıştır. Kütüphanenin belgeleri URL [https://symfony.com/doc/current/components/http_foundation.html] adresinde (Mayıs 2019) mevcuttur.

Kütüphaneyi kurmak için, Laragon terminalinde (bkz. bağlantı paragrafı) aşağıdaki adımları izliyoruz:

Image

  • [2-3]: [<laragon>/www] klasöründe olduğunuzdan emin olun;
  • [4]: [HttpFoundation] kütüphanesini yükleyecek olan [composer] komutu;
  • [5]: Bu örnekte, kütüphane zaten kurulmuştu;

İlk kurulumda, konsol günlükleri aşağıdaki gibi görünmelidir:


C:\myprograms\laragon-lite\www
? composer require symfony/http-foundation
Using version ^4.3 for symfony/http-foundation
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing symfony/mime (v4.3.0): Downloading (100%)
  - Installing symfony/http-foundation (v4.3.0): Downloading (100%)
Writing lock file
Generating autoload files

[date-time-server-2.php] web sunucusunun ikinci sürümü şöyledir:


<?php

// Symfony kütüphanelerinin kullanımı

// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpFoundation\Response;

// Content-Type başlığı ayarlanıyor
$response=new Response();
$response->headers->set("content-type","text/plain");
$response->setCharset("utf-8");

// yanıt içeriği ayarlanıyor
//
// tarih ve saatin gönderilmesi
// zaman: 01/01/1970'ten bu yana geçen milisaniye sayısı
// tarih-saat görüntüleme biçimi
// d: 2 basamaklı gün
// m: 2 basamaklı ay
// y: 2 basamaklı yıl
// H: saat 0,23
// i: dakika
// s: saniye
$response->setContent(date("d/m/y H:i:s", time()));

// yanıt gönderilir
$response->send();

Açıklamalar

  • 7. satır: Symfony'nin [HttpFoundation] kütüphanesindeki [Response] sınıfı, web hizmetinin istemcilerine verilen yanıtın tamamını yönetir;
  • 10. satır: [Response] sınıfının bir örneği oluşturulur;
  • 11. satır: yanıtın [text/plain] türü olduğu belirtilir;
  • 12. satır: yanıt, UTF-8 metnidir;
  • 25. satır: müşterinin talep ettiği yanıt belgesi belirlenir;
  • 28. satır: yanıt müşteriye gönderilir;

17.6.4. İstemci komut dosyası – sürüm 2

İstemci komut dosyasında değişiklik yapılmaz. Yalnızca [config-date-time-client.json] yapılandırma dosyası değiştirilir:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/02/date-time-server-2.php"
}

Sonuçlar, sürüm 1'dekiyle aynıdır.

17.7. Bir jSON veri sunucusu

Bir web betiğinin yanıtı, tablolar ve nesneler halinde bir araya getirilebilecek çeşitli verilerden oluşabilir. Betik, bu çeşitli öğeleri jSON dizesi içinde gönderebilir ve istemci bu diziyi çözebilir.

Image

17.7.1. Sunucu betiği

[json-server.php] betiği, aşağıdaki [Personne] sınıfını kullanır:


<?php

namespace Modèles;

class Personne implements \JsonSerializable {
  // özellikler
  private $nom;
  private $prénom;
  private $âge;

  // bir ilişkisel dizinin bir nesneye dönüştürülmesi [Personne]
  public function setFromArray(array $assoc): Personne {
    // geçerli nesneyi ilişkisel tablo ile başlatıyoruz
    foreach ($assoc as $attribute => $value) {
      $this->$attribute = $value;
    }
    // sonuç
    return $this;
  }

  // alıcı ve ayarlayıcılar
  public function getNom() {
    return $this->nom;
  }

  public function getPrénom() {
    return $this->prénom;
  }

  public function setNom($nom) {
    $this->nom = $nom;
    return $this;
  }

  public function setPrénom($prénom) {
    $this->prénom = $prénom;
    return $this;
  }

  public function getÂge() {
    return $this->âge;
  }

  public function setÂge($âge) {
    $this->âge = $âge;
    return $this;
  }

  // toString
  public function __toString(): string {
    return "Personne [$this->prénom, $this->nom, $this->âge]";
  }

  // JsonSerializable arayüzünü uygular
  public function jsonSerialize(): array {
    // nesnenin özniteliklerini anahtar olarak kullanan bir ilişkisel dizi döndürür
    // bu tablo daha sonra jSON formatında kodlanabilir
    return get_object_vars($this);
  }

  // bir jSON nesnesinin [Personne] nesnesine dönüştürülmesi
  public static function jsonUnserialize(string $json): Personne {
    // jSON dizesinden bir kişi oluşturulur
    return (new Personne())->setFromArray(json_decode($json, true));
  }

}

Açıklamalar

  • 5. satır: Sınıf, PHP [JsonSerializable] arayüzünü uygular. Bu, sınıfın 55-59. satırlardaki [jsonSerialize] yöntemini uygulamasını gerektirir. Yöntem, jSON formatında serileştirilmesi gereken bir ilişkisel dizi döndürmelidir. [json_encode($personne)] ifadesi kullanıldığında, [json_encode] işlevi, [Personne] sınıfının [JsonSerializable] arayüzünü uygulayıp uygulamadığını kontrol eder. Eğer öyleyse, ifade [json_encode($personne→serialize())] haline gelir;
  • 12-19. satırlar: Sınıfın yapıcı işlevi yoktur, ancak bir başlatıcısı vardır. [Personne] sınıfı, [(new Personne())→setFromArray($array)] ifadesiyle örneklenebilir. Yalnızca bir yapıcı olabilirken, çeşitli türlerde başlatıcılar olabilir. Bu başlatıcılar, [(new Personne())→initialiseuri(…)] türünün çeşitli şekillerde örneklenmesine olanak tanır;
  • 62-65. satırlar: [jsonUnserialize] statik işlevi, jSON dizesinden bir [Personne] nesnesi oluşturulmasına olanak tanır;

[json-server.php] komut dosyası şu şekilde olacaktır:


<?php

// bağımlılıklar
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;

// Content-Type başlığı ve kullanılan karakter kümesi belirlenir
$response = new Response();
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");

// bir Kişi nesnesi oluşturulur
$personne = (new Personne())->setFromArray([
  "nom" => "de la Hûche",
  "prénom" => "jean-paul",
  "âge" => 27]);
// bir ilişkisel dizi
$assoc = ["attr1" => "value1",
  "attr2" => [
    "prenom" => "Jean-Paul",
    "nom" => "de la Hûche"
  ]
];
// yanıt içeriği jSON'tir
$response->setContent(json_encode([$personne, $assoc]));

// yanıtın gönderilmesi
$response->send();

Açıklamalar

  • 4-5. satırlar: [Personne] sınıfı içe aktarılır;
  • 11. satır: belgenin [application/json] türü olacağı belirtilir. Bu başlık alındığında, tarayıcılar düz metin yerine jSON dizesinin biçimlendirilmiş halini görüntüler;
  • 12. satır: jSON dizesi, UTF-8 karakterlerini içerecektir;
  • 15-18. satırlar: [Personne] nesnesi oluşturulur;
  • 20-25. satırlar: iki seviyeli bir ilişkisel dizi oluşturulur;
  • 27. satır: Bir diziden jSON dizesi istemciye gönderilir:
    • [$personne] öğesi, [jsonSerialize] yöntemi sayesinde jSON olarak serileştirilecektir;
    • [$assoc] öğesi, doğal olarak jSON olarak serileştirilecektir;

Bu sunucu komut dosyasını çalıştırırsak (Laragon'un çalışır durumda olması gerekir), tarayıcıda şu yanıtı alırız:

Image

Image

Yorumlar

  • [2]'te, biçimlendirilmiş jSON yanıtı;
  • [4]'te, jSON yanıtı işlenmemiş haliyle; aksanlı karakterlerin kodlamasına dikkat edilmelidir;
  • [6]'te, tarayıcının bu biçimlendirmeyi yapmasına neden olan, sunucu tarafından gönderilen [application/json] içerik türüdür;

17.7.2. İstemci

Image

[json-client.php] istemcisi, aşağıdaki jSON [config-json-client.json] dosyası ile yapılandırılmıştır:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/03/json-server.php"
}

[json-client.php] komut dosyası şöyledir:


<?php

// bir hizmetin istemcisi jSON
//
// hata yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-json-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturulur HTTP
$httpClient = HttpClient::create();

try {
  // istek gönderiliyor
  $response = $httpClient->request('GET', $config['url']);
  // yanıt durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıkları alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // yanıtın gövdesini jSON olarak alınır
  list($personne, $assoc) = json_decode($response->getContent(), true);
  // özellikler tablosundan bir kişi oluşturuluyor
  $personne = (new Personne())->setFromArray($personne);
  // sunucunun yanıtı görüntülenir
  print "---Réponse du serveur\n";
  print "$personne\n";
  print "tableau=" . json_encode($assoc, JSON_UNESCAPED_UNICODE) . "\n";
} catch (TypeError | RuntimeException $ex) {
  // hatayı görüntüler
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}


Açıklamalar

  • 12-13. satırlar: [Personne] sınıfının içe aktarılması;
  • 30. satır: HTTP istemcisinin oluşturulması;
  • 44. satır: sunucu tarafından gönderilen jSON dizesi deşifre edilir. Kodlanan şeyin, iki ilişkisel diziden oluşan iki elemanlı bir dizi olduğu bilinmektedir;
  • satır 46: [Personne] nesnesi oluşturulur ve daha sonra satır 49'da görüntülenir;
  • 50. satır: İkinci ilişkisel dizi görüntülenir. [print] komutu dizileri görüntüleyemez. Bu nedenle bu dizi, jSON dizesine dönüştürülür. Aksanlı karakterlerin doğru şekilde görüntülenmesi için ikinci parametre olarak [JSON_UNESCAPED_UNICODE] kullanılmalıdır. Aksanlı karakterlerin jSON dizesinde kodlandığını gördük;

İstemci komut dosyasının çalıştırılması şu sonuçları verir:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Sun, 02 Jun 2019 09:56:29 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 143
connection: close
content-type: application/json
---Réponse du serveur
Personne [jean-paul, de la Hûche, 27]
tableau={"attr1":"value1","attr2":{"prenom":"Jean-Paul","nom":"de la Hûche"}}
  1. ve 12. satırlarda, aksanlı karakterler doğru şekilde alınmıştır.

17.8. Web hizmetinin ortam değişkenlerinin alınması

Bir sunucu betiği, tanıyabileceği bir web ortamında çalışır. Bu ortam, $_SERVER sözlüğünde, PHP'in bir küresel değişkeni olarak saklanır. [HttpFoundation] kütüphanesini kullanırsak, bu ortam [Request→server] alanında bulunur; burada [Request], web betiği tarafından işlenen HTTP isteğidir.

17.8.1. Sunucu betiği

Müşterilerine çalışma ortamını gönderen bir sunucu uygulaması yazıyoruz.

Image

[env-server.php] web betiği şu şekildedir:


<?php

// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

// isteği alınır
$request = Request::createFromGlobals();
// yanıt oluşturulur
$response = new Response();
// yanıtın içeriği utf-8 formatında json'dur
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// yanıtın içeriği jSON olarak belirlenir
$response->setContent(json_encode($request->server->all()));
// yanıt gönderiliyor
$response->send();
  • 9. satır: Web betiği tarafından alınan HTTP isteği ve bu betiğin çalışma ortamı hakkında mevcut tüm bilgileri içeren [Request] türündeki nesneyi alıyoruz;
  • 13-14. satırlar: UTF-8 karakterlerini içeren düz metni istemciye göndereceğiz;
  • 16. satır: İstemciye gönderilecek bilgi, jSON nesnesinin [$request→server→all()] serileştirmesiyle elde edilen bir karakter dizisi olacaktır: [$request→server], web betiğinin çalışma ortamını temsil eder. Bu, bir tür sözlük olan [ServerBag] türünde bir nesnedir. [$request→server→all()] ise [ServerBag]'in içeriğine ait gerçek bir sözlüktür;
  • 18. satır: bilgi gönderilir;

Bu komut dosyasını NetBeans'ten çalıştırırsak, tarayıcı şu sayfayı görüntüler:

Image

  • [2]'te, ortam sözlüğündeki çeşitli anahtarlar;
  • [3] dosyasında, bu anahtarların değerleri;

17.8.2. İstemci komut dosyası

Image

[env-client.php] istemci komut dosyası, aşağıdaki jSON ve [config-env-client.json] dosyalarıyla yapılandırılır:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/04/env-server.php"
}

[env-client.php] istemci komut dosyası şöyledir:


<?php

// sunucu komut dosyası ortamı
//
// hata yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-env-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturulur HTTP
$httpClient = HttpClient::create();
try {
  // sunucuya istek gönderiliyor
  $response = $httpClient->request('GET', $config['url']);
  // yanıt durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıkları alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // sunucunun yanıtı görüntülenir
  print "---Réponse du serveur\n";
  $env = json_decode($response->getContent());
  foreach ($env as $key => $value) {
    print "[$key]=>$value\n";
  }
} catch (TypeError | RuntimeException $ex) {
  // hata görüntüleniyor
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Açıklamalar

  • 42. satır: Sunucudan gelen jSON yanıtı deserialize edilir. Bir ilişkisel dizi elde edilir;
  • 43-45. satırlar: Bu ilişkisel dizinin tüm değerleri görüntülenir;

Konsolda şu sonuç elde edilir:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Sun, 02 Jun 2019 17:35:50 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 1505
connection: close
content-type: application/json
---Réponse du serveur
[HTTP_HOST]=>localhost
[HTTP_USER_AGENT]=>Symfony HttpClient/Curl
[HTTP_ACCEPT_ENCODING]=>deflate, gzip
[PATH]=>C:\Program Files (x86)\Mail Enable\BIN;C:\windows\system32;C:\windows;C:\windows\System32\Wbem;C:\windows\System32\WindowsPowerShell\v1.0\;C:\windows\System32\OpenSSH\;C:\Program Files\dotnet\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files (x86)\Mail Enable\BIN64;C:\Users\serge\AppData\Local\Microsoft\WindowsApps;;C:\myprograms\Microsoft VS Code\bin
[SystemRoot]=>C:\windows
[COMSPEC]=>C:\windows\system32\cmd.exe
[PATHEXT]=>.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
[WINDIR]=>C:\windows
[SERVER_SIGNATURE]=>
[SERVER_SOFTWARE]=>Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
[SERVER_NAME]=>localhost
[SERVER_ADDR]=>::1
[SERVER_PORT]=>80
[REMOTE_ADDR]=>::1
[DOCUMENT_ROOT]=>C:/myprograms/laragon-lite/www
[REQUEST_SCHEME]=>http
[CONTEXT_PREFIX]=>
[CONTEXT_DOCUMENT_ROOT]=>C:/myprograms/laragon-lite/www
[SERVER_ADMIN]=>admin@example.com
[SCRIPT_FILENAME]=>C:/myprograms/laragon-lite/www/php7/scripts-web/04/env-server.php
[REMOTE_PORT]=>63744
[GATEWAY_INTERFACE]=>CGI/1.1
[SERVER_PROTOCOL]=>HTTP/1.1
[REQUEST_METHOD]=>GET
[QUERY_STRING]=>
[REQUEST_URI]=>/php7/scripts-web/04/env-server.php
[SCRIPT_NAME]=>/php7/scripts-web/04/env-server.php
[PHP_SELF]=>/php7/scripts-web/04/env-server.php
[REQUEST_TIME_FLOAT]=>1559496950.644
[REQUEST_TIME]=>1559496950

İşte bazı değişkenlerin anlamları (Windows için. Linux'ta farklı olurlar):

HTTP_HOST
istemci tarafından gönderilen HTTP [Host: xxx] başlığındaki xxx değeri
HTTP_USER_AGENT
istemci tarafından gönderilen HTTP [User_Agent: xxx] başlığının xxx değeri
HTTP_ACCEPT_ENCODING
Müşteri tarafından gönderilen HTTP [Accept-Encoding: xxx] başlığındaki xxx değeri
PATH
sunucu betiğinin çalıştığı makinedeki yürütülebilir dosyaların yolu
COMSPEC
komut yorumlayıcısının yolu DOS
PATHEXT
yürütülebilir dosyaların uzantıları
WINDIR
Windows kurulum klasörü
SERVER_SIGNATURE
web sunucusunun imzası. Burada bir şey yok.
SERVER_SOFTWARE
web sunucusunun türü
SERVER_NAME
web sunucusu makinesinin İnternet adı
SERVER_PORT
web sunucusunun dinleme bağlantı noktası
SERVER_ADDR
web sunucusu makinesinin IP adresi, burada 127:0:0:1
REMOTE_ADDR
IP istemcinin adresi. Burada istemci, sunucuyla aynı makinedeydi.
REMOTE_PORT
İstemcinin iletişim bağlantı noktası
DOCUMENT_ROOT
web sunucusu tarafından sunulan belgelerin dizin yapısının kökü
REQUEST_SCHEME
TCP isteğinin protokolü URL http://localhost/php7/…
SERVER_ADMIN
web sunucusu yöneticisinin e-posta adresi
SCRIPT_FILENAME
sunucu betiğinin tam yolu
REMOTE_PORT
İstemcinin isteğini gönderdiği bağlantı noktası
SERVER_PROTOCOL
web sunucusu tarafından kullanılan protokol sürümü HTTP
REQUEST_METHOD
İstemci tarafından kullanılan HTTP komutu. Dört tane vardır: GET, POST, PUT, DELETE
QUERY_STRING
GET /url?parametreler emriyle gönderilen parametreler
REQUEST_URI
istemci tarafından talep edilen URL. Tarayıcı http://machine[:port]/uri adresindeki URL'i talep ederse, REQUEST_URI=uri elde edilir
SCRIPT_NAME
$_SERVER['SCRIPT_FILENAME']=$_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME']

17.9. Sunucu tarafından bir istemci tarafından gönderilen parametrelerin alınması

17.9.1. Giriş

HTTP protokolünde, bir istemcinin WEB sunucusuna parametreleri iletmek için iki yöntemi vardır:

  • hizmetin URL'ini şu biçimde talep eder:

GET url?param1=val1&param2=val2&param3=val3… HTTP/1.0

şeklinde hizmetin URL kodunu talep eder; burada vali değerleri, belirli ayrılmış karakterlerin onaltılık değerleriyle değiştirilmesi için önceden kodlanmalıdır;

  • hizmetin URL değerini şu biçimde talep eder:

POST url HTTP/1.0

şeklinde talep eder; ardından sunucuya gönderilen HTTP başlıkları arasına şu başlığı ekler:


Content-length=N

İstemci tarafından gönderilen diğer başlıklar boş bir satırla sona erer. Ardından verilerini şu biçimde gönderebilir:


val1&param2=val2&param3=val3…

şeklinde gönderebilir; burada vali değerleri, GET yönteminde olduğu gibi önceden kodlanmalıdır. Sunucuya gönderilen karakter sayısı N olmalıdır; burada N,


Content-length=N

Müşteri tarafından gönderilen yukarıdaki parami parametrelerini alan web hizmetinin PHP komut dosyası, bu parametrelerin değerlerini aşağıdaki tablodan alır:

  • $_GET["parami"] komutu için;
  • $_POST["parami"] komutu için POST;

bu, PHP'in temel işlevleri içindir. [HttpFoundation] kütüphanesi kullanılıyorsa, bu parametreler şu yerde bulunur:

  • [Request]->query->get(‘parami’) komutu için GET;
  • POST komutu için [Request]->request->get('parami');

burada [Request], web betiği tarafından alınan isteğe ilişkin tüm bilgileri temsil eder;

17.9.2. GET istemcisi – sürüm 1

Image

İstemci komut dosyaları, aşağıdaki jSON [config-parameters-client.json] dosyası ile yapılandırılır:

1
2
3
4
{
    "url-get": "http://localhost/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost/php7/scripts-web/05/parameters-server.php"
}
  • 1. satır: GET istemcilerinin hedef web betiğinin URL;
  • 2. satır: POST istemcisinin hedef web betiği olan URL;

GET istemcileri, sunucuya üç [nom, prenom, age] parametresi gönderir. [parameters-get-client.php] istemcisi şöyledir:


<?php

// bir web sunucusunun GET istemcisi
//
// hata yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-parameters-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create();

try {
  // ayarları hazırlıyoruz
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
// bilgileri kodluyoruz
  $parameters = "prenom=" . urlencode($prenom) .
    "&nom=" . urlencode($nom) .
    "&age=$age”;
  // istek gönderiliyor
  $response = $httpClient->request('GET', $config['url-get'] . "?$parameters");
  // yanıt durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıklar alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // sunucunun yanıtı görüntüleniyor
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // hatası görüntüleniyor
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Açıklamalar

  • 33-35. satırlar: sunucuya gönderilen parametrelerin kodlanması. [$prenom, $nom] karakterleri içerebilen UTF-8 parametreleri, [urlencode] işleviyle kodlanır. (İlişkisel ifadeler anlamında) alfanümerik olmayan tüm karakterler, xx'in karakterin onaltılık değeri olduğu %xx ile değiştirilir. Boşluklar ise + işaretiyle değiştirilir;
  • 37. satır: İstenen URL, $URL?$parameters şeklindedir; burada $parameters, nom=val1&prenom=val2&age=val3 biçimindedir;
  • 48. satır: istemci, sunucunun yanıtını görüntülemekle yetinecektir;

GET parametresiyle yapılan bir istek sırasında sunucunun ne aldığını merak edebiliriz. Bunun için, bir Laragon terminalinden (bkz. bağlantı paragrafı) yerel makinenin 100 numaralı bağlantı noktasında genel amaçlı [RawTcpServer] sunucumuzu başlatıyoruz:

Image

[4] komutunda, yardımcı programlar klasöründe olduğunuzdan emin olun.

jSON ve [parameters-get-client.json] dosyalarını, GET ve POST istemcilerini yapılandıran şekilde değiştiriyoruz:


{
    "url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost/php7/scripts-web/05/parameters-server.php"
}
  • 2. satır: Web sunucusunun bağlantı noktasını değiştirdik. Böylece [RawTcpServer] ile bağlantı kurulacaktır;

İstemciyi çalıştırıyoruz. [RawTcpServer] penceresinde şu bilgileri alıyoruz:

Image

  • [1]'te, istemci tarafından gönderilen ve ayarları yapılmış GET komutu. Bazı karakterlerin kodlamasını açıkça görebiliyoruz;

17.9.3. GET / POST sunucusu

Image

[parameters-server.php] sunucu komut dosyası şu şekildedir:


<?php

// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

// istek alınır
$request = Request::createFromGlobals();
// isteğin parametreleri alınır
$getParameters = $request->query->all();
$bodyParameters = $request->request->all();

// yanıt oluşturuluyor
$response = new Response();
// yanıt içeriği utf-8 metindir
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// yanıt içeriği - jSON ile kodlanmış bir tablo
$response->setContent(json_encode([
  "method" => $request->getMethod(),
  "uri" => $request->getRequestUri(),
  "getParameters" => $getParameters,
  "bodyParameters" => $bodyParameters
    ], JSON_UNESCAPED_UNICODE));
// yanıtın gönderilmesi
$response->send();

Açıklamalar

  • 9. satır: Web betiğinin [Request] nesnesinin oluşturulması. Bu nesne, web betiğinin istemciden aldığı tüm bilgileri kapsar;
  • 11. satır: [Request→query] nesnesi, [ParameterBag] türündedir ve bir istemcinin olası GET işleminin parametrelerini bir araya getirir. [Request→query→get(«X»)] ifadesi, GET ve [nom=val1&prenom=val2&age=val3] parametreleri içindeki X adlı parametreye erişilmesini sağlar. [Request→query→all()] ifadesi, GET'in parametre sözlüğünü elde etmeyi sağlar;
  • 12. satır: [Request→request] nesnesi, [ParameterBag] türündedir ve istemci tarafından sunucuya belge olarak gönderilen parametreleri bir araya getirir. Bu parametreler, istemcinin sunucuya gönderdiği bir belgeye ait oldukları için “yüklenen” parametreler olarak da adlandırılır. [Request→request→get(«X»)] ifadesi, yüklenen [nom=val1&prenom=val2&age=val3] parametreleri içindeki X adlı parametreye erişilmesini sağlar. [Request→request→all()] ifadesi ise yüklenen parametrelerin sözlüğüne erişilmesini sağlar;
  • 17-18. satırlar: İstemciye, UTF-8 formatında kodlanmış jSON verisinin gönderileceği bildirilir;
  • satır 20-25: sunucu, aldığı tüm parametreleri, istemci tarafından gerçekleştirilen [GET / POST / …] işlem türünü ve istenen URI'i istemciye geri gönderir. Bu yöntem, [$request→getMethod()] ifadesiyle elde edilir. İstemciye gönderilen belge, bazı değerleri kendileri de ilişkisel tablolar olan bir ilişkisel tablonun jSON dizisidir. [JSON_UNESCAPED_UNICODE] parametresi, Unicode karakterlerinin (örneğin aksanlı karakterler gibi) kodlanmadan olduğu gibi gönderilmesini talep eder;
  • 27. satır: yanıt müşteriye gönderilir;

İstemci komut dosyasının çalıştırılması aşağıdaki sonuçları verir:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 10:08:45 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 207
connection: close
content-type: application/json
---Réponse du serveur [{"method":"GET","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom=jean-paul&nom=de+la+h%C3%BBche&age=45","getParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"},"bodyParameters":[]}]
  • 10. satır:
    • [method]: yöntem GET'tir;
    • [uri]: istenen URI'te GET isteğinin URL kodlanmış parametreleri görülüyor;
    • [getParameters]: GET isteğinin parametre tablosu;
    • [bodyParameters]: Yüklenen parametreler tablosu: boştur;

17.9.4. GET istemcisi – sürüm 2

İstemci betiğinin önceki sürümünde, eğitim amaçlı olarak sunucuya gönderilen parametreleri kendimiz URL kodlamıştık. [HttpClient] nesnesi bu işi kendi başına yapabilir. Aşağıdaki [parameters-get-client-2.php] betiğidir:


<?php

// bir web sunucusunun GET istemcisi
//
// hata yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-parameters-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create();
try {
  // ayarları hazırlıyoruz
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // sunucuya istek gönderiliyor
  $response = $httpClient->request('GET', $config['url-get'],
    ["query" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
  ]]);
  // yanıt durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıkları alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // sunucunun yanıtı görüntüleniyor
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // hata görüntüleniyor
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Açıklamalar

  • 33-37. satırlar: 32. satırdaki GET isteğine parametrelerin eklenmesi. [HttpClient] nesnesi, URL'in kodlamasını kendisi halledecektir;

17.9.5. POST istemcisi

Bir HTTP istemcisi, web sunucusuna şu metin dizisini gönderir: HTTP başlıkları, boş satır, belge. Önceki istemcide bu dizi şöyleydi:

1
2
3
GET /url?paramètres HTTP/1.1
… autres entêtes HTTP
ligne vide

Belge yoktu. Parametreleri iletmenin başka bir yolu daha vardır; buna POST yöntemi denir. Bu durumda, web sunucusuna gönderilen metin dizisi şöyledir:

1
2
3
4
POST /url HTTP/1.1
… autres entêtes HTTP
ligne vide
paramètres

Bu sefer, GET istemcisinde başlıklara dahil edilen parametreler, HTTP istemcisinde ise başlıkların ardından gönderilen belgenin bir parçasıdır.

POST [parameters-postclient.php] istemcisinin komut dosyası şu şekildedir:


<?php

// bir web sunucusunun POST istemcisi
//
// hata yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-parameters-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create();
try {
  // ayarları hazırlıyoruz
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // sunucuya istek gönderiliyor
  $response = $httpClient->request('POST', $config['url-post'],
    ["body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
  ]]);
  // yanıtın durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıkları alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // sunucunun yanıtı görüntüleniyor
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // hata görüntüleniyor
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
  • 32. satır: Artık POST türünde bir HTTP isteği var;
  • 33-37. satırlar: POST parametreleri, POST isteğinin gövdesi (body) olarak adlandırılır: bu, istemci tarafından sunucuya gönderilen belgedir. Burada üç parametre gönderilir: [nom, prenom, age];
  • 48. satır: Sunucudan gelen jSON yanıtı görüntülenir;

İstemci komut dosyasının çalıştırılmasının sonuçları şunlardır:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 11:43:02 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 163
connection: close
content-type: application/json
---Réponse du serveur [{"method":"POST","uri":"\/php7\/scripts-web\/05\/parameters-server.php","getParameters":[],"bodyParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"}}]
  • 10. satır: yöntem [Post] ve parametreler [bodyParameters] türündedir. [uri]'te görüldüğü gibi [getParameters] parametresi yoktur;

Bir POST isteği sırasında sunucunun ne aldığını merak edebiliriz. Bunun için, bir Laragon terminalinden (bkz. bağlantı paragrafı) yerel makinenin 100 numaralı bağlantı noktasında genel amaçlı [RawTcpServer] sunucumuzu başlatıyoruz:

Image

[4] komutunda, yardımcı programlar klasöründe olduğunuzdan emin olun.

POST istemcisini yapılandıran jSON [config-parameters-client.json] dosyasını düzenliyoruz:


{
    "url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost:100/php7/scripts-web/05/parameters-server.php"
}
  • 3. satır: Web sunucusunun bağlantı noktasını değiştirdik. Böylece [RawTcpServer] ile bağlantı kurulacaktır;

İstemciyi çalıştırıyoruz. [RawTcpServer] penceresinde şu bilgileri alıyoruz:

Image

  • [6]'te, POST komutu;
  • [7]'te: HTTP başlığı; [Content-Length], istemcinin sunucuya göndereceği belgenin bayt sayısını verir. HTTP ve [Content-Type] başlıkları, bu belgenin türünü belirtir. [application/x-www-form-urlencoded] türü, URL kodlu bir metni ifade eder;
  • [8] ise, HTTP başlıklarının sonunu ve 44 baytlık belgenin başlangıcını bildiren boş satırdır. Ekran görüntüsünde görünmeyen şey ise belgenin kendisidir. Bu, parametrelerin URL kodlu dizesidir: [prenom=jean-paul&nom=de+la+h%C3%BBche&age=45]. Okuyucu, bunun gerçekten 44 karakterden oluştuğunu doğrulayabilir;

17.9.6. Karışık bir POST istemcisi

Bir POST'te, URL'te kodlanmış parametreler ile HTTP başlıklarından sonra istemci tarafından gönderilen belgede kodlanmış parametreler birleştirilebilir. İşte bir [parameters-mixte-postclient.php] örneği:


<?php

// bir web sunucusunun POST istemcisi
//
// hata yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-parameters-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create();
try {
  // ayarları hazırlıyoruz
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // sunucuya istek gönderiliyor
  $response = $httpClient->request('POST', $config['url-post'],
    [
      // belge parametreleri (gövde)
      "body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
      ],
      // URL (sorgu) parametreleri
      "query" => [
        "prenom2" => $prenom,
        "nom2" => $nom,
        "age2" => $age
  ]]);
  // yanıt durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıklar alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // sunucunun yanıtı görüntüleniyor
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // hatayı görüntüleme
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Açıklamalar

  • 32. satır: bir POST isteği;
  • 40-45. satırlar: URL'te URL kodlanmış parametreler;
  • 35-39. satırlar: isteğin gövdesinde (body, belge) yer alan URL kodlu parametreler;

Çalıştırıldığında, konsolda şu sonuçlar elde edilir:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 12:34:23 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 270
connection: close
content-type: application/json
---Réponse du serveur [{"method":"POST","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom2=jean-paul&nom2=de%20la%20h%C3%BBche&age2=45","getParameters":{"prenom2":"jean-paul","nom2":"de la hûche","age2":"45"},"bodyParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"}}]
  • 10. satır: Sunucunun her iki parametre türünü de alabildiği görülmektedir;

17.9.7. Karışık bir GET istemcisi

GET isteği ile daha önce yaptığımızın aynısını yapmaya çalışıyoruz. [parameters-mixte-get-client.php] komut dosyası şöyledir:


<?php

// bir web sunucusunun POST istemcisi
//
// hata yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-parameters-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create();
try {
  // ayarları hazırlama
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  // sunucuya istek gönderiliyor
  $response = $httpClient->request('GET', $config['url-post'],
    [
      // belge parametreleri (gövde)
      "body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
      ],
      // URL (sorgu) parametreleri
      "query" => [
        "prenom2" => $prenom,
        "nom2" => $nom,
        "age2" => $age
  ]]);
  // yanıt durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıklar alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // sunucunun yanıtı görüntüleniyor
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  // hatayı görüntüleme
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Açıklamalar

  • 32. satır: bir POST isteği;
  • 40-45. satırlar: URL'te URL kodlanmış parametreler;
  • 35-39. satırlar: isteğin gövdesinde (body, document) bulunan URL kodlu parametreler;

Çalıştırıldığında, konsolda şu sonuçlar elde edilir:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 12:41:19 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 217
connection: close
content-type: application/json
---Réponse du serveur [{"method":"GET","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom2=jean-paul&nom2=de%20la%20h%C3%BBche&age2=45","getParameters":{"prenom2":"jean-paul","nom2":"de la hûche","age2":"45"},"bodyParameters":[]}]
  • 10. satır: Sunucunun, istemci tarafından gönderilen belgede URL kodlu parametreler almadığı görülmektedir. İstemci tarafından gönderilen HTTP başlıklarına bakıldığında, istemcinin 44 karakterlik bir belge gönderdiği ancak sunucunun bunu işlemediği anlaşılmaktadır;

Sonuç olarak, sunucuya bilgi göndermek için hangi yöntemi seçmeliyiz?

  • [GET URL?param1=val1&param2=val2&…] yöntemi, bağlantı görevi görebilen, parametreleri ayarlanmış bir URL kullanır. Bu, yöntemin en önemli avantajıdır: kullanıcı bu tür bağlantıları yer imlerine ekleyebilir;
  • Diğer uygulamalarda, sunucuya gönderilen parametrelerin bir URL içinde görüntülenmesini istemeyebilirsiniz. Örneğin güvenlik nedenleriyle. Bu durumda bir [POST] yöntemi kullanılır ve URL kodlanmış parametreler sunucuya gönderilen bir belgeye eklenir;

17.10. Web oturum yönetimi

Önceki istemci/sunucu örneklerinde şu şekilde çalışıyordu:

  • İstemci, web hizmeti sunucusunun 80 numaralı bağlantı noktasına bir bağlantı kurar;
  • şu metin dizisini gönderir: HTTP başlıkları, boş satır, [document];
  • yanıt olarak sunucu da aynı türde bir dizi gönderir;
  • sunucu, istemciyle olan bağlantıyı sonlandırır;
  • istemci, sunucuya olan bağlantıyı sonlandırır;

Aynı istemci kısa bir süre sonra web sunucusuna yeni bir istek gönderirse, istemci ile sunucu arasında yeni bir bağlantı kurulur. Sunucu, bağlanan istemcinin daha önce gelip gelmediğini veya bunun ilk istek olup olmadığını bilemez. İki bağlantı arasında sunucu istemcisini "unutur". Bu nedenle, HTTP protokolünün durum bilgisi içermeyen bir protokol olduğu söylenir. Oysa sunucunun müşterilerini hatırlaması yararlıdır. Örneğin, bir uygulama güvenliyse, müşteri kimliğini doğrulamak için sunucuya bir kullanıcı adı ve şifre gönderir. Sunucu iki bağlantı arasında müşterisini “unutursa”, müşteri her yeni bağlantıda kimliğini doğrulamak zorunda kalır ve bu da mümkün değildir.

Bir müşteriyi takip etmek için sunucu şu şekilde hareket eder: Bir müşteriden gelen ilk istek sırasında, yanıtına bir tanımlayıcı ekler ve müşteri daha sonra her yeni istekte bu tanımlayıcıyı sunucuya geri göndermelidir. Her müşteri için farklı olan bu tanımlayıcı sayesinde sunucu, bir müşteriyi tanıyabilir. Böylece, müşterinin tanımlayıcısına benzersiz bir şekilde eşleştirilmiş bir bellek şeklinde bu müşteri için bir bellek yönetebilir.

Teknik olarak süreç şu şekildedir:

  • yeni bir müşteriye verdiği yanıtta sunucu, HTTP Set-Cookie: MotClé=Kimlik başlığını ekler. Bunu yalnızca ilk istekte yapar;
  • sonraki isteklerinde ise müşteri, sunucunun kendisini tanıması için HTTP Cookie: MotClé=Kimlik başlığı aracılığıyla kimliğini geri gönderir;

Sunucunun, karşısındaki kişinin daha önce gelmiş bir müşteri değil de yeni bir müşteri olduğunu nasıl anladığı merak edilebilir. Bunu, müşterinin HTTP başlıklarında HTTP Cookie başlıklarının bulunması gösterir. Yeni bir müşteri için bu başlık yoktur.

Belirli bir müşterinin tüm bağlantıları bir oturum olarak adlandırılır.

17.10.1. [php.ini] yapılandırma dosyası

Oturum yönetiminin PHP ile düzgün çalışması için, bu dosyanın doğru şekilde yapılandırılmış olması gerekir. Windows'ta yapılandırma dosyası php.ini'tir. Çalıştırma ortamına (konsol, web) göre, [php.ini] yapılandırma dosyası farklı klasörlerde aranmalıdır. Bu klasörleri bulmak için aşağıdaki komut dosyası kullanılacaktır:

1
2
3
4
<?php

// bilgiler PHP
phpinfo();
  1. satırda, phpinfo işlevi, betiği çalıştıran PHP yorumlayıcısı hakkında bilgi verir. Özellikle, kullanılan [php.ini] yapılandırma dosyasının yolunu gösterir.

Bu komut dosyasını daha önce bir konsol ortamında kullanmıştık (bkz. bağlantı paragrafı). Bir web ortamında ise şu sonuç elde edilir:

Image

  • [1-2] dosyasında, web komut dosyası yorumlayıcısını yapılandıran [php.ini] dosyası bulunur. Bu dosyada bir session bölümü vardır:
[Session]
session.save_handler = files
session.save_path = "C:/myprograms/laragon-lite/tmp"
session.use_strict_mode = 0
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 36000
session.referer_check =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.trans_sid_tags = "a=href,area=href,frame=src,form="
session.sid_bits_per_character = 5
  • 2. satır: bir istemci oturumunun verileri bir dosyaya kaydedilir;
  • 3. satır: oturum verilerinin kaydedildiği klasör. Bu klasör yoksa, herhangi bir hata bildirilir ve oturum yönetimi çalışmaz;
  • 4-6. satırlar: oturum kimliğinin HTTP, Set-Cookie ve Cookie başlıkları tarafından yönetildiğini belirtir;
  • 7. satır: Set-Cookie başlığı, Set-Cookie: PHPSESSID=identifiant_de_session biçiminde olacaktır;
  • 8. satır: Bir istemci oturumu otomatik olarak başlatılmaz. Sunucu komut dosyası, session_start() komutuyla bunu açıkça talep etmelidir;
  • 9. satır: Oturum çerezi, istemci tarayıcısı kapatılana kadar geçerlidir;
  • satır 10: Oturum çerezinin geri gönderilmesi gereken yol. Eğer [session.cookie_path = /xxx] ise, tarayıcı her URL türünde bir [/xxx/yyy/zzz] talebinde bulunduğunda çerezi geri göndermelidir. Burada [/] yolu, sitedeki tüm URL istekleri için çerezin geri gönderilmesi gerektiğini belirtir;
  • 13. satır: Bazı oturum nesneleri, bir dosyada depolanabilmeleri için serileştirilmelidir. PHP, [serialize / unserialize] işlevleriyle bu serileştirme/serileştirme işlemini gerçekleştirir;
  • satır 16: Yedekleme dosyasına kaydedilen oturum nesnelerinin, bu süreden sonra geçersiz sayılacağı süre;
  • satır 19: Bir oturumun ömrü. Bu süre geçtikten sonra yeni bir oturum oluşturulur ve önceki oturumda yedeklenen nesneler kaybolur;

17.10.2. Örnek 1

17.10.2.1. Sunucu

Image

Oturum kimliğinin yönetimi, bir web hizmeti için şeffaftır. Bu kimlik, web sunucusu tarafından yönetilir. Bir web hizmeti, session_start() komutu aracılığıyla müşterinin oturumuna erişebilir. Bu andan itibaren web hizmeti, $_SESSION sözlüğü aracılığıyla müşterinin oturumundaki verileri okuyabilir ve yazabilir. [HttpFoundation] kütüphanesi kullanılıyorsa, oturum [Request→getSession] ifadesi aracılığıyla erişilebilir.

Aşağıdaki [session-server.php] kodu, üç sayacın oturum içinde yönetilmesini göstermektedir. Her yeni istekte, web betiği bu sayaçları artırır ve bir sonraki istekte geri alınabilmeleri için oturuma kaydeder.


<?php
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

//
// isteği alınır
$request = Request::createFromGlobals();
// oturum
$session = new Session();
$session->start();
// oturumdan üç sayaç alınır
if ($session->has("N1")) {
  // N1 sayacının artırılması
  $session->set("N1", (int) $session->get("N1") + 1);
} else {
  // N1 sayacı oturumda yok - oluşturuluyor
  $session->set("N1", 0);
}
if ($session->has("N2")) {
  // N2 sayacının artırılması
  $session->set("N2", (int) $session->get("N2") + 1);
} else {
  // N2 sayacı oturumda değil - oluşturuluyor
  $session->set("N2", 10);
}
if ($session->has("N3")) {
  // N3 sayacının artırılması
  $session->set("N3", (int) $session->get("N3") + 1);
} else {
  // N3 sayacı oturumda değil - oluşturuluyor
  $session->set("N3", 100);
}
// yanıt hazırlanıyor
$response = new Response();
// yanıtın içeriği utf-8 metindir
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// yanıt, üç sayacı içeren bir tablonun jSON biçimi olacaktır
$response->setContent(json_encode([
  "N1" => $session->get("N1"),
  "N2" => $session->get("N2"),
  "N3" => $session->get("N3")]));

// yanıtın gönderilmesi
$response->send();
  • 10. satır: [$request] nesnesi, web betiği tarafından alınan istekle ilgili tüm bilgileri kapsar;
  • 12-13. satırlar: Bir oturum oluşturulur ve etkinleştirilir. [Session] nesnesi, istemci tarafından gönderilen oturum çerezine karşılık gelen oturum verilerini içerir. İstemci böyle bir çerez göndermediyse, [Session]'te depolanmış hiçbir veri olmaz. Web komut dosyası, ilk yanıtına HTTP [Set-Cookie : PHPSESSID=xxx] başlığını ekleyecektir. Sonraki isteklerinde, istemci içeriğini kullanmak istediği oturumu belirtmek için HTTP [Cookie : PHPSESSID=xxx] başlığını gönderecektir. Oturum, bir istemcinin belleğidir;
  • 15. satır: oturumda [N1] adlı bir anahtar olup olmadığına bakılır. Bu, ilk sayacımızın adı olacaktır. Eğer yoksa (20. satır), ona 0 değeri verilir ve oturuma eklenir. Eğer varsa (23. satır), o zaman:
    • oturumdan bu sayacı alırız;
    • değerini 1 artırırız;
    • onu tekrar oturuma kaydederiz;
  • 22-35. satırlar: diğer iki sayaç olan N2 ve N3 için de aynı işlemi yaparız;
  • satır 36-40: [application/json] türünde bir yanıt hazırlanır;
  • satır 42-45: yanıt, üç sayaç içeren bir tablodan alınan jSON dizesi olacaktır;
  • 48. satır: yanıt müşteriye gönderilir;

İstemci-sunucu ilişkisinde, sunucu üzerindeki istemci oturumunun yönetimi hem istemciye hem de sunucuya bağlıdır:

  • sunucu, ilk istek sırasında müşterisine bir kimlik göndermekle yükümlüdür
  • istemcinin görevi ise her yeni istekte bu tanımlayıcıyı geri göndermektir. Bunu yapmazsa, sunucu bunun yeni bir istemci olduğunu varsayacak ve yeni bir oturum için yeni bir tanımlayıcı oluşturacaktır.

Sonuçlar

İstemci olarak bir web tarayıcısı kullanıyoruz. Varsayılan olarak (aslında yapılandırma gereği), tarayıcı sunucunun kendisine gönderdiği oturum tanımlayıcılarını sunucuya geri gönderir. İstekler ilerledikçe, tarayıcı sunucu tarafından gönderilen üç sayacı alacak ve bunların değerlerinin arttığını görecektir.

Image

  • [2], web hizmetine yapılan ilk istek;
  • [4]'te ise dördüncü istek, sayaçların gerçekten artırıldığını göstermektedir. İstekler ilerledikçe sayaç değerleri gerçekten kaydedilmektedir;

Geliştirme modunu kullanarak sunucu ile istemci arasında alışverişi yapılan HTTP başlıklarını inceleyelim. Sunucuyla olan mevcut oturumu sonlandırmak için Firefox’u kapatıyoruz, yeniden açıyoruz ve geliştirme modunu etkinleştiriyoruz (F12). Bu işlem, tarayıcının mevcut oturumunu silecek ve tarayıcı yeni bir oturum başlatacaktır. [session-server.php] hizmetini talep ediyoruz:

Image

[5]'te, sunucunun müşterinin ilk isteğine yanıt olarak gönderdiği oturum kimliğini görüyoruz. HTTP Set-Cookie başlığını kullanıyor.

Web tarayıcısında sayfayı yenileyerek (F5) yeni bir istek gönderelim:

Image

Yukarıda iki husus dikkat çekmektedir:

  • [11]'te, web tarayıcısı HTTP Cookie başlığıyla oturum kimliğini geri gönderir.
  • [12] örneğinde ise web hizmeti, yanıtında bu kimlik numarasını artık dahil etmemektedir. Artık her isteğinde bu kimlik numarasını göndermek istemcinin sorumluluğundadır.

17.10.2.2. İstemci

Şimdi, önceki sunucu betiğinin bir istemci betiğini yazıyoruz. Oturum yönetiminde, web tarayıcısı gibi davranmalıdır:

  • Sunucunun ilk isteğine verdiği yanıtta, sunucunun kendisine gönderdiği oturum kimliğini bulması gerekir. Bu kimliği HTTP Set-Cookie başlığında bulacağını bilir.
  • Sonraki her isteğinde, aldığı kimlik numarasını sunucuya geri göndermelidir. Bunu HTTP Cookie başlığıyla yapacaktır.

Image

[session-client] istemcisi, aşağıdaki jSON [config-session-client.json] dosyası ile yapılandırılmıştır:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/06/session-server.php"
}

[session-client] istemcisinin kodu şöyledir:


<?php

// oturum yönetimi
//
// hataların yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-session-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create();
try {
  // 10 istek gönderilecek
  for ($i = 0; $i < 10; $i++) {
    // sunucuya istek gönderiliyor
    if (!isset($sessionCookie)) {
      // oturum olmadan
      $response = $httpClient->request('GET', $config['url']);
    } else {
      // oturumlu
      $response = $httpClient->request('GET', $config['url'],
        ["headers" => ["Cookie" => $sessionCookie]]);
    }
    // yanıt durumu
    $statusCode = $response->getStatusCode();
    print "---Réponse avec statut : $statusCode\n";
    // başlıkları alıyoruz
    print "---Entêtes de la réponse\n";
    $headers = $response->getHeaders();
    foreach ($headers as $type => $value) {
      print "$type: " . $value[0] . "\n";
    }
    // varsa oturum çerezi alınır
    if (isset($headers["set-cookie"])) {
      // oturum çerezi var mı?
      foreach ($headers["set-cookie"] as $cookie) {
        $match = [];
        $match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
        if ($match) {
          $sessionCookie = "PHPSESSID=" . $champs[1];
        }
      }
    }
  }
  // sunucudan gelen jSON yanıtı görüntüleniyor
  print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
  // hatayı görüntüler
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}


Açıklamalar

  • 27. satır: HTTP istemcisinin oluşturulması;
  • 30. satır: [session-server.php] sunucusuna aynı isteği 10 kez göndereceğiz;
  • 32. satır: [$sessionCookie] değişkeni, istemci tarafından alınan HTTP [Set-Cookie] başlığının değerini alacaktır;
  • 32-34. satırlar: Bu değişken mevcut değilse, oturum henüz başlamamıştır. [Cookie] başlığı olmadan [GET] komutu gönderilir;
  • satır 35-38: Aksi takdirde oturum başlamıştır ve [Cookie] başlığıyla birlikte [GET] komutu gönderilir. Bu başlığın değeri [$sessionCookie] olacaktır;
  • 50. satır: [Set-Cookie] başlığı, alınan HTTP başlıklarından biriyse, oturum çerezi aranır;
  • 52. satır: Web sunucusu birden fazla [Set-Cookie] başlığı gönderebilir. Oturum çerezi bunlardan sadece biridir. Örneğimizde, bu çerezin [PHPSESSID=xxx;] biçiminde olması bir özelliğidir;
  • 53-57. satırlar: Oturum çerezini bulmak için bir düzenli ifade kullanılır;
  • 62. satır: 10 istek gönderildikten sonra, sunucudan gelen son yanıt olan jSON görüntülenir;

Sonuçlar

İstemci komut dosyasının çalıştırılması, NetBeans konsolunda aşağıdaki çıktının görüntülenmesine neden olur:


"C:\myprograms\laragon-lite\bin\php\php-7.2.11-Win32-VC15-x64\php.exe" "C:\Data\st-2019\dev\php7\poly\scripts-console\clients web\06\session-client.php"
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
set-cookie: PHPSESSID=1cerjgsgdlc35e1mkenvtltmh8; path=/
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
…………………………………………………………
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse du serveur : {"N1":9,"N2":19,"N3":109}
  • 8. satır: Sunucu, ilk yanıtında oturum kimliğini gönderir. Sonraki yanıtlarda bunu artık göndermez;
  • 41. satır: Üç sayaç [N1, N2, N3], 9 kez artırılmıştır. 1 numaralı istek sırasında sıfırlanmıştır;

Aşağıdaki örnek, bir dizinin veya nesnenin değerlerinin de oturumda saklanabileceğini göstermektedir.

17.10.3. Örnek 2

17.10.3.1. Sunucu

Image

[Personne] nesnesini oturuma ekleyeceğiz. Bu sınıfın tanımı şöyledir:


<?php

namespace Modèles;

class Personne implements \JsonSerializable {
  // özellikler
  private $nom;
  private $prénom;
  private $âge;

  // bir ilişkisel dizinin bir nesneye dönüştürülmesi [Personne]
  public function setFromArray(array $assoc): Personne {
    // geçerli nesne, ilişkisel dizi ile başlatılıyor
    foreach ($assoc as $attribute => $value) {
      $this->$attribute = $value;
    }
    // sonuç
    return $this;
  }

  // alıcı ve ayarlayıcılar
  public function getNom() {
    return $this->nom;
  }

  public function getPrénom() {
    return $this->prénom;
  }

  public function setNom($nom) {
    $this->nom = $nom;
    return $this;
  }

  public function setPrénom($prénom) {
    $this->prénom = $prénom;
    return $this;
  }

  public function getÂge() {
    return $this->âge;
  }

  public function setÂge($âge) {
    $this->âge = $âge;
    return $this;
  }

  // toString
  public function __toString(): string {
    return "Personne [$this->prénom, $this->nom, $this->âge]";
  }

  // JsonSerializable arayüzünü uygular
  public function jsonSerialize(): array {
    // nesnenin özniteliklerini anahtar olarak kullanan bir ilişkisel dizi döndürür
    // bu tablo daha sonra jSON formatında kodlanabilir
    return get_object_vars($this);
  }

  // bir jSON nesnesinin [Personne] nesnesine dönüştürülmesi
  public static function jsonUnserialize(string $json): Personne {
    // jSON dizesinden bir kişi oluşturulur
    return (new Personne())->setFromArray(json_decode($json, true));
  }

}

Sunucu komut dosyası şu şekilde olacaktır:


<?php

// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;

//
// geçerli isteği alıyoruz
$request = Request::createFromGlobals();

// oturum
$session = new Session();
$session->start();

// oturumdan çeşitli veriler alınır
// dizi
if ($session->has("tableau")) {
  // dizi oturumda bulunur - tüm değerleri bir artırılır
  $tableau = $session->get("tableau");
  for ($i = 0; $i < count($tableau); $i++) {
    $tableau[$i] += 1;
  }
  // diziyi oturuma geri yerleştirilir
  $session->set("tableau", $tableau);
} else {
  // dizi oturumda yok - dizi oluşturulur
  $tableau = [0, 10, 100];
  // seansa ekleniyor
  $session->set("tableau", $tableau);
}
// sözlük
if ($session->has("assoc")) {
  // [assoc] oturumda mevcut - tüm öğeleri artırılıyor
  $assoc = $session->get("assoc");
  foreach ($assoc as $key => $value) {
    $assoc[$key] = $value + 1;
  }
  // $assoc'i oturuma ekliyoruz
  $session->set("assoc", $assoc);
} else {
  // [assoc] oturumda yok - oluşturuluyor
  $assoc = ["un" => 0, "deux" => 10, "trois" => 100];
  // $assoc oturuma eklenir
  $session->set("assoc", $assoc);
}
// Kişi nesnesi
if ($session->has("personne")) {
  // [personne] oturumda mevcut - yaşı artırılıyor
  $personne = $session->get("personne");
  $personne->setÂge($personne->getÂge() + 1);
} else {
  // [personne] oturumda yok - oluşturuluyor
  $personne = (new Personne())->setFromArray(
    ["prénom" => "Léonard", "nom" => "Hûche", "âge" => 0]);
  // $personne oturuma ekleniyor
  $session->set("personne", $personne);
}
// yanıt oluşturulur
$response = new Response();
// yanıtın içeriği jSON utf-8 formatındadır
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
$response->setContent(json_encode([
  "tableau" => $tableau,
  "assoc" => $assoc,
  "personne" => $personne], JSON_UNESCAPED_UNICODE));

// yanıt gönderiliyor
$response->send();

Açıklamalar

  • 16-17. satırlar: mevcut oturumu alıyoruz ve etkinleştiriyoruz;
  • 21-34. satırlar: oturuma eklenmiş [tableau] dizisini yönetiriz. Her yeni istekte, dizinin elemanları 1 artırılır;
  • 36-49. satırlar: oturuma eklenmiş [assoc] adlı bir ilişkisel dizi yönetilir. Her yeni istekte, elemanları 1 artırılır;
  • satır 51-61: oturumda tutulan bir [Personne] nesnesi yönetilir. Her yeni istekte, bu kişinin yaşı 1 artırılır;
  • satır 62-73: İstemciye jSON yanıtı gönderilir: bir ilişkisel dizideki jSON dizesi;

Bu komut dosyasını NetBeans'ten çalıştıralım. İlk iki istek şu sonuçları verir (ikincisi için tarayıcıda F5):

Image

  • Görüldüğü gibi, [6-8]'te tüm sayaçlar artırılmıştır;

17.10.3.2. Müşteri

Image

Müşteri, Örnek 1’deki (bağlantı paragrafı) ile aynıdır. Yalnızca [config-session-client] yapılandırma dosyasında değişiklik yapılır:


{
    "url": "http://localhost/php7/scripts-web/07/session-server.php"
}

Çalıştırıldığında şu sonuçlar elde edilir:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 14:25:24 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
set-cookie: PHPSESSID=qbfrj8clr20mod3eriur71mao6; path=/
content-length: 119
connection: close
content-type: application/json
---Réponse avec statut : 200
………….……………………………………………………….
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 14:25:24 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 119
connection: close
content-type: application/json
---Réponse du serveur : {"tableau":[9,19,109],"assoc":{"un":9,"deux":19,"trois":109},"personne":{"nom":"Hûche","prénom":"Léonard","âge":9}}
  • [22] satırında, tüm sayaçların artırıldığını görüyoruz;

17.11. Authentification

Şimdi, yalnızca belirli kullanıcılara yönelik web hizmetlerine odaklanacağız. Bu durumda, istemci yanıt alabilmek için önce web hizmetinde kimliğini doğrulamalıdır.

17.11.1. İstemci

Image

[auth-client.php] istemcisinin kodu şöyledir:


<?php

// oturum yönetimi
//
// hataların yönetimi
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// istemci yapılandırması
const CONFIG_FILE_NAME = "config-auth-client.json";

// yapılandırmayı alıyoruz
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create([
    'auth_basic' => ['admin', 'admin'],
    // "verify_peer" => false,
    // "verify_host" => false
  ]);


try {
  // sunucuya istek gönderilir
  $response = $httpClient->request('GET', $config['url']);
  // yanıtın durumu
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // başlıklar alınır
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // sunucudan gelen yanıt jSON görüntüleniyor
  print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
  // hata görüntüleniyor
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

Açıklamalar

  • 27-31. satırlar: statik [HttpClient::create] yöntemine bir parametre, yani bir ilişkisel dizi aktarıldı;
  • 28. satır: [auth_basic] anahtarının değeri, iki elemanlı bir dizi olan [user, password]'tir. Müşteri, bu elemanları kullanarak web hizmetinde kimlik doğrulaması yapacaktır. [auth_basic] anahtarı, müşterinin göndereceği HTTP başlığının adından gelen [Autorization Basic] adlı bir kimlik doğrulama türünü belirtir. Başka kimlik doğrulama türleri de mevcuttur;
  • bu kod dışında, istemci öncekilerle aynıdır;

İstemci tarafından gönderilen HTTP başlıklarını görmek için, daha önce birçok kez yaptığımız gibi, istemciyi genel TCP sunucusuna bağlayacağız:

Image

İstemciyi aşağıdaki [config-auth-client.json] yapılandırmasıyla başlatıyoruz:


{
    "url": "http://localhost:100/php7/scripts-web/08/auth-server.php"
}

[RawTcpServer] sunucusu daha sonra aşağıdaki satırları alır:

Image

  • [5]'te, istemci tarafından gönderilen [Autorization : Basic XXX] başlığını görüyoruz. XXX dizesi, Base64 ile kodlanmış [user:password] dizesidir;

Bundan emin olmak için, alınan dizgiyi [https://www.base64decode.org/] sitesinde deşifre edebilirsiniz:

Image

17.11.2. Sunucu

Image

[auth-server.php] sunucusu şöyledir:


<?php

// bağımlılıklar
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;

// yetkili kullanıcılar
$users = ["admin" => "admin"];
//
// geçerli istek alınır
$request = Request::createFromGlobals();
// kimlik doğrulama
$requestUser = $request->headers->get('php-auth-user');
$requestPassword = $request->headers->get('php-auth-pw');
// kullanıcı mevcut mu?
$trouvé = array_key_exists($requestUser, $users) && $users[$requestUser] === $requestPassword;
// yanıtın hazırlanması
$response = new Response();
// yanıtın durum kodu belirleniyor
if (!$trouvé) {
  // bulunamadı - kod 401
  $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
  $response->headers->add(["WWW-Authenticate"=> "Basic realm=".utf8_decode("\"PHP7 par l'exemple\"")]);
} else {
  // bulundu - kod 200
  $response->setStatusCode(Response::HTTP_OK);
}
// yanıtta içerik yok, sadece başlıklar var HTTP
$response->send();

Açıklamalar

  • 9. satır: yetkili kullanıcılar; burada, [admin] kullanıcı adı ve [admin] şifresine sahip tek bir kullanıcı bulunmaktadır;
  • 14. satır: Kullanıcı kimliği, [PHP-AUTH-USER] başlığından alınır. Bu, istemci tarafından gönderilen bir başlık değil, sunucudaki PHP tarafından oluşturulan bir başlıktır;
  • 15. satır: Kullanıcının şifresi, PHP tarafından oluşturulan [PHP-AUTH-PW] başlığından alınır;
  • 17. satır: Oturum açmak isteyen kullanıcı, yetkili kullanıcılar listesinde aranır;
  • 23-24. satırlar: Kullanıcı tanınmazsa, istemciye
    • 23. satır: [401 Unauthorized] kodu;
    • 24. satır: [WWW-Authenticate: Basic realm=”quelque chose”] başlığı. Çoğu tarayıcı bu başlığı tanır ve kullanıcıdan kimlik bilgilerini girmesini isteyen bir kimlik doğrulama penceresi açar. HTTP başlıkları, ISO 8859-1 olarak kodlanmalıdır. NetBeans metinleri ise UTF-8 olarak kodlanmıştır. [utf8_decode] işlevi, UTF-8'ten ISO 8859-1'e dönüştürmeyi sağlar. Burada bu işlev gerekli değildi çünkü [PHP7 par l’exemple] dizesindeki karakterler, UTF-8 ve ISO 8859-1'de de aynıdır. Bu işlev, yalnızca HTTP başlıklarında kullanılan kodlamayı hatırlatmak amacıyla eklenmiştir;
  • 25. satır: Kullanıcı tanındıysa, istemciye [200 OK] kodu gönderilir;

Bir tarayıcıyla URL ve [auth-server.php] kodlarını isteyelim:

Image

Tarayıcının bir kimlik doğrulama penceresi görüntülediğini görüyoruz. [2]'te, sunucu tarafından gönderilen [WWW-Authenticate] başlığının değerini görüyoruz. Tarayıcı tarafından alınan HTTP başlıklarına bakarsak, şunu görürüz:

1
2
3
4
5
6
7
8
9
HTTP/1.0 401 Unauthorized
Date: Fri, 07 Jun 2019 09:11:23 GMT
Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
X-Powered-By: PHP/7.2.11
Cache-Control: no-cache, private
WWW-Authenticate: Basic realm="PHP7 par l'exemple"
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
  • 1. satır: yanıtın [401 Unauthorized] kodu;
  • 6. satır: HTTP [WWW-Authenticate] başlığı;
  • 7. satır: yanıtın gövdesi boştur;

[3-4]'te [admin] iki kez girilirse, sunucunun yanıtı şu şekilde olur:

1
2
3
4
5
6
7
8
HTTP/1.0 200 OK
Date: Fri, 07 Jun 2019 09:21:00 GMT
Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
X-Powered-By: PHP/7.2.11
Cache-Control: no-cache, private
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
  • 1. satır: yanıtın 200 kodu OK;
  • 6. satır: yanıtın gövdesi boştur;

[3-4] adresine hatalı kimlik bilgileri girilirse, testler için kullanılan [Firefox] tarayıcısı, doğru kimlik bilgileri girilene kadar kimlik doğrulama penceresini süresiz olarak görüntüler. Sunucuyla her gidiş-dönüş işleminde, tarayıcının kimlik doğrulama penceresini tetikleyen her zaman aynı yanıt alınır.

[auth-client.php] istemcisini yetkisiz bir kullanıcıyla çalıştıralım. Sunucunun yanıtı şöyledir:


---Réponse avec statut : 401
---Entêtes de la réponse
Erreur de communication avec le serveur : HTTP/1.0 401 Unauthorized returned for "https://localhost/php7/scripts-web/08/auth-server.php".
  • [1]'te istemci 401 kodunu doğru bir şekilde almıştır;
  • [3]'te ise istemcide bir istisna atılmıştır. Bu istisnayı tetikleyen Symfony istemcisi [HttpClient]'tir: HTTP yanıtının durum kodu, sunucu tarafında bir hata olduğunu gösterdiğinde ve istemci sunucu yanıtının başlıklarını veya içeriğini okumaya çalıştığında bir istisna tetikler. 3. satırdaki mesajdan, sunucunun kullanıcının tanınmadığını belirtmek için [HTTP/1.0 401 Unauthorized] yanıtını verdiğini görebiliriz;

Şimdi, yetkili kullanıcı [‘admin’,’admin’] ile [auth-client.php] istemcisini çalıştıralım. Sunucunun yanıtı şu şekildedir:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Wed, 05 Jun 2019 10:11:02 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 0
connection: close
content-type: text/html; charset=UTF-8
---Réponse du serveur :

  • 1. satır: sunucu [HTTP/1. 200 OK] yanıtını verdi;
  • 7. satır: yanıtta içerik yok (0 bayt);

17.11.3. İstemci/sunucu bağlantısını güvenli hale getirme

Gördüğümüz gibi, sunucuya kimlik doğrulaması yapmak için istemci şu başlığı gönderdi:

authorization: Basic YWRtaW46YWRtaW4=

Bu satır bir casus yazılım tarafından ele geçirilirse, bu yazılım [login, mot de passe] kimlik bilgilerini [YWRtaW46YWRtaW4=] dizesinde base64 ile kodlanmış haliyle kolayca bulabilir. Bu nedenle, kimlik doğrulamasının istemci ile sunucu arasındaki güvenli bir bağlantı üzerinden gerçekleştirilmesi gerekir. Güvenli URL bağlantıları, HTTP protokolü yerine [HTTPS] protokolünü kullanır. [HTTPS] protokolü, güvenli bir istemci/sunucu bağlantısı içindeki HTTP protokolüdür. Güvenli URL'ler, [https://chemin_document] biçimindedir.

Tüm web sunucuları bu biçimdeki URL'leri kabul etmez. Güvenli hale getirmek için bunların değiştirilmesi gerekir. Laragon'un Apache sunucusu güvenli bir sunucudur, ancak HTTPS protokolü varsayılan olarak etkin değildir. Laragon menüsünden etkinleştirilmesi gerekir:

Image

  • [4] olarak değiştirin; Apache sunucusunun SSL şifrelemesini etkinleştirin;

Bu işlem tamamlandıktan sonra Apache sunucusu otomatik olarak yeniden başlatılır:

Image

  • [1]'e dönüştüğünde, yeşil bir kilit simgesi belirir: bu, HTTPS protokolünün etkinleştirildiğinin işaretidir;
  • [2]'te yeni bir hizmet bağlantı noktası belirir; burada 443 numaralı bağlantı noktası. Bu, HTTPS güvenli protokolünün hizmet bağlantı noktasıdır;

Artık güvenli bir sunucumuz olduğuna göre, istemcinin [config-auth-client.json] yapılandırma dosyasını şu şekilde değiştirelim:


{
    "url": "https://localhost:443/php7/scripts-web/08/auth-server.php"
}

[2]'te protokol [https] ve bağlantı noktası [443] olarak değiştirilmiştir.

Şimdi, yetkili kullanıcı [admin, admin] ile [auth-client.php] istemcisini çalıştıralım. Konsol çıktıları şu şekildedir:

Erreur de communication avec le serveur : Peer certificate cannot be authenticated with given CA certificates for"https://localhost/php7/scripts-web/08/auth-server.php".

Symfony istemcisi [HttpClient], sunucunun kendisine [HttpClient] tarafından kabul edilmeyen bir güvenilir sertifika göndermesi nedeniyle bir istisna oluşturdu. SSL iletişimi, resmi kurumlar tarafından onaylanmış güvenilir sertifikalarla gerçekleştirilir. Laragon’daki Apache sunucusunda HTTPS protokolü etkinleştirildiğinde, Apache sunucusu için kendinden imzalı bir sertifika oluşturuldu. Kendinden imzalı sertifika, resmi bir kurum tarafından doğrulanmamış bir sertifikadır. Symfony istemcisi [HttpClient] bu kendinden imzalı sertifikayı reddetti.

[HttpClient]'ten, sunucu tarafından gönderilen sertifikanın geçerliliğini kontrol etmemesini istemek mümkündür. Bu, [HttpClient::create] yöntemindeki seçeneklerle yapılır:


// bir müşteri oluşturuluyor HTTP
$httpClient = HttpClient::create([
    'auth_basic' => ['admin', 'admin'],
    "verify_peer" => false
  ]);
  1. satır, sunucu sertifikasının doğrulanmamasını talep eder. Bu sorunu, bağlantı paragrafındaki [http-02.php] betiğinde daha önce de karşılaşmıştık. Bu komut dosyası, HTTP ve HTTPS sitelerine bağlanmak için [libcurl] kütüphanesini kullanıyordu. O zaman bu kütüphane için aşağıdaki yapılandırmayı kullanmıştık:

// Bir oturum başlatılıyor: cURL
  $curl = curl_init($url);
  if ($curl === FALSE) {
    // bir hata oluştu
    return "Erreur lors de l'initialisation de la session cURL pour le site [$site]";
  }
  // curl seçenekleri
  $options = [
    // ayrıntılı mod
    CURLOPT_VERBOSE => true,
    // yeni bağlantı - önbellek yok
    CURLOPT_FRESH_CONNECT => true,
    // istek zaman aşımı (saniye cinsinden)
    CURLOPT_TIMEOUT => $timeout,
    CURLOPT_CONNECTTIMEOUT => $timeout,
    // sertifikaların geçerliliğini kontrol etme SSL
    CURLOPT_SSL_VERIFYPEER => false,
    // yönlendirmeleri takip et
    CURLOPT_FOLLOWLOCATION => true,
    // istenen belgeyi bir karakter dizisi olarak al
    CURLOPT_RETURNTRANSFER => true
  ];

  // curl ayarları
curl_setopt_array($curl, $options);
  1. satırda, [CURLOPT_SSL_VERIFYPEER] sabiti, sunucu tarafından gönderilen sertifikanın doğrulanıp doğrulanmayacağını kontrol eder. [HttpClient] istemcisi, burada olduğu gibi PHP yapılandırmasında [curl] uzantısı etkinleştirildiğinde, aslında bir [curl] istemcisidir. Bu durumda, [HttpClient::create] tarafından örneklenen sınıf, [CurlHttpClient] sınıfıdır. [curl]'in sabitleri bu sınıfta da mevcuttur, ancak farklı isimler altında bulunur:

$curlopts = [
            CURLOPT_URL => $url,
            CURLOPT_USERAGENT => 'Symfony HttpClient/Curl',
            CURLOPT_TCP_NODELAY => true,
            CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
            CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 0 < $options['max_redirects'] ? $options['max_redirects'] : 0,
            CURLOPT_COOKIEFILE => '', // Yönlendirmeler sırasında çerezleri takip et
            CURLOPT_CONNECTTIMEOUT_MS => 1000 * $options['timeout'],
            CURLOPT_PROXY => $options['proxy'],
            CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '',
            CURLOPT_SSL_VERIFYPEER => $options['verify_peer'],
            CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0,
            CURLOPT_CAINFO => $options['cafile'],
            CURLOPT_CAPATH => $options['capath'],
            CURLOPT_SSL_CIPHER_LIST => $options['ciphers'],
            CURLOPT_SSLCERT => $options['local_cert'],
            CURLOPT_SSLKEY => $options['local_pk'],
            CURLOPT_KEYPASSWD => $options['passphrase'],
            CURLOPT_CERTINFO => $options['capture_peer_cert_chain'],
        ];

[CurlHttpClient] tarafından kullanılan sabitleri sarı renkle vurguladık.

Şimdi, [auth-client] istemcisini [admin, admin] kullanıcısıyla çalıştırırsak şu sonucu elde ederiz:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Wed, 05 Jun 2019 10:44:37 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 0
connection: close
content-type: text/html; charset=UTF-8
---Réponse du serveur :

Kullanıcı doğru bir şekilde tanındı. [auth-client] istemcisini [admin, admin] dışındaki bir kullanıcıyla çalıştırırsak, şu sonucu elde ederiz:

1
2
3
---Réponse avec statut : 403
---Entêtes de la réponse
Erreur de communication avec le serveur : HTTP/1.0 403 Forbidden returned for "https://localhost/php7/scripts-web/08/auth-server.php".

Artık güvenli bir sunucuda kimlik doğrulaması yapabiliyoruz.