Skip to content

22. Flask çerçeveli web hizmetleri

Burada web hizmeti derken, bir istemci tarafından tüketilen ham verileri sağlayan tüm web uygulamalarını kastediyoruz; takip eden örneklerde bu genellikle bir konsol betiğidir. 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 sunan teknolojiler. REST, jSON verisini sunarken, SOAP ise XML verisini sunar. 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, istemcinin isteğinin ve sunucunun 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.

22.1. Giriş

Python betikleri bir web sunucusu tarafından çalıştırılabilir. Böyle bir betik, 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 Python'da yazılabilir. Bu durumda, az önce incelediğimiz internet 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. |le protocole HTTP| paragrafının amacı da buydu. Dersin bu bölümünde anlatılan web istemcileri, HTTP protokolünün bir kısmını keşfetmemizi sağladı.

Image

En basit haliyle, istemci-sunucu iletişimi ş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 Python komut dosyaları kullanacağız.

Image

  • [1]'te, istemci sunucuyla bir bağlantı kurar, bir Python betiği ister ve bu betiğe parametre gönderir ya da göndermez;
  • [3]'te, web sunucusu Python yorumlayıcısı aracılığıyla Python betiğini çalıştırır. Betik, [2]'te olduğu gibi istemciye 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.

Bundan sonra iki web sunucusu kullanacağız:

  • hafif Werkzeug sunucusu [https://werkzeug.palletsprojects.com/en/1.0.x/]. Bu sunucu, Flask web çerçevesi [https://flask.palletsprojects.com/en/1.1.x/] tarafından kullanılır. Buna genellikle Flask sunucusu diyeceğiz;
  • Apache 2 sunucusu [https://httpd.apache.org/];

Flask sunucusu tüm örneklerde kullanılacaktır. Apache sunucusu ise geliştireceğimiz web uygulamasını barındırmak için kullanılacaktır.

Flask çerçeve programı Python ile geliştirilmiştir. Bu, terminale kurulan bir modüldür PyCharm:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\inet\utilitaires>pip install flask
Collecting flask
  Downloading Flask-1.1.2-py2.py3-none-any.whl (94 kB)
     || 94 kB 1.1 MB/s
Collecting click>=5.1
  Downloading click-7.1.2-py2.py3-none-any.whl (82 kB)
     || 82 kB 5.8 MB/s
Collecting itsdangerous>=0.24
  Downloading itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB)
Collecting Jinja2>=2.10.1
  Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
     || 125 kB 6.4 MB/s
Collecting Werkzeug>=0.15
  Downloading Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB)
     || 298 kB 6.4 MB/s
Collecting MarkupSafe>=0.23
  Downloading MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl (16 kB)
Installing collected packages: click, itsdangerous, MarkupSafe, Jinja2, Werkzeug, flask
Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.2 flask-1.1.2 itsdangerous-1.1.0
  • 1. satır: çalıştırılan komut;
  • 19. satır: Yüklenen bileşenler:
    • [flask-1.1.2]: Python ile web geliştirme çerçevesidir;
    • [Werkzeug-1.0.1]: istemcilerin isteklerine yanıt verecek web sunucusudur;
    • [Jinja2-2.11.2]: normalde statik olacak sayfalara dinamik öğeler eklemeye olanak tanıyan bir araçtır;

22.2. [flask/01] komut dosyaları: web programlamanın temel unsurları

Image

Örneklerimiz aşağıdaki mimaride çalıştırılacaktır:

Image

  • [1]'te, bir Python komut dosyası, klasik bir konsol komut dosyası gibi çalıştırılacaktır;
  • [2]'te, şeffaf bir şekilde bir web sunucusu başlatılır ve istekleri bekler. Aslında, yalnızca tek bir URL'i kabul edecektir;
  • [3]'te, tarayıcı sunucudan tek bir URL isteğinde bulunacaktır;
  • [4] adımında, sunucu [1] konsolu tarafından belirtilen Python betiğini çalıştırır;
  • [5] olduğunda, betik sonuçlarını web sunucusuna bir metin belgesi olarak gönderecektir;
  • [6]'te, web sunucusu bu metin belgesini tarayıcıya gönderecektir;

22.2.1. [exemple_01] betiği: HTML dilinin temelleri

Bir web tarayıcısı çeşitli belgeleri görüntüleyebilir; 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> etiketi, önemli metni 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 tek tek ele almayacağız. Tek bir satır HTML kodu yazmadan bir WEB sayfası oluşturmaya olanak tanıyan çok sayıda 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 web istemcilerine gönderilecek HTML kodunu kendileri oluşturması gerektiğinden, HTML dilini bilmek zorunludur. Bu kod program tarafından oluşturulur ve elbette, istemcinin istediği web sayfasını alabilmesi için neyin oluşturulması gerektiğini bilmek gerekir.

Özetle, web programlamaya başlamak için HTML dilinin tamamını bilmek gerekmez. Ancak bu bilgi gereklidir ve WYSIWYG gibi web sayfası oluşturma yazılımları (örneğin WEB ve diğer onlarca yazılım) kullanılarak edinilebilir. HTML dilinin inceliklerini keşfetmenin bir başka yolu da, web'de 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 belgesi, <html>…</html> etiketleri ile çevrelenmiştir. Bu belge iki bölümden oluşur:

  • <head>…</head>: Bu, belgenin görüntülenemeyen kısmıdır. Bu kısım, 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> etiketine rastlanır. Burada, özellikle belgenin anahtar kelimelerini tanımlayan ve daha sonra arama motorları tarafından kullanılan anahtar kelimeleri belirleyen 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;
  • <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(/static/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="/static/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>
Etiket
Etiketler ve örnekler HTML
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 (satır 20, 31)
<tr öznitelikleri>…</tr>: bir satırı tanımlamak için (21., 25. satırlar)
<td öznitelikler>…</td>: bir hücreyi tanımlamak için (satır 22)
ö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 ortalanacaktır (text-align: center).
image
<img border="0" src="/static/images/cerisier.jpg"/> (satır 38): web sunucusundaki [/static/images/cerisier.jpg] dosyasını kaynak alan, kenarlığı olmayan (border="0") bir resim tanımlar (src="/static/images/cerisier.jpg"). Bu bağlantı, URL [http://server/chemin/balises.html] ile oluşturulan bir web belgesinde bulunuyorsa, tarayıcı burada referans verilen görüntüyü almak için URL [http://server/ static/images/cerisier.jpg] dosyasını isteyecektir.
lien
<a href="http://www.polytech-angers.fr/fr/index.html">burada</a> (43. satır): ici metninin http://www.polytech-angers.fr/fr/index.html adresine bağlantı görevi görmesini sağlar.
fond de page
<body style="background-image: url(/static/images/standard.jpg)"> (8. satır): Sayfa arka planı olarak kullanılacak resmin, web sunucusundaki URL [/static/images/standard.jpg] adresinde bulunduğunu belirtir. Örneğimizde, tarayıcı bu arka plan resmini almak için URL ve [http://server/static/images/standard.jpg] adreslerine istek gönderecektir.

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

  • [http://server/chemin/balises.html], belgenin HTML kaynağını almak için;
  • [http://server/static/images/cerisier.jpg], cerisier.jpg resmini almak için;
  • [http://server/static/images/standard.jpg], standard.jpg arka plan resmini almak için;

[exemple_01] komut dosyası, önceki [balises.html] statik sayfasını görüntülememizi sağlayacaktır:

Image

  • [1]'te, çalıştırılacak olan [exemple_01] komut dosyası;
  • [3]'e, komut dosyası tarafından görüntülenecek olan HTML belgesini;
  • [2] olarak, HTML belgesindeki resimler;

[exemple_01] komut dosyası şu şekildedir:


import os

from flask import Flask, make_response, render_template

# Flask uygulaması
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")


# Ana Sayfa URL
@app.route('/')
def index():
    # sayfanın görüntülenmesi
    return make_response(render_template("balises.html"))


# ana sayfa
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 7. satır: Bir Flask uygulaması başlatılır. Flask uygulaması, bir web uygulamasıdır;
    • 1. parametre, uygulamaya verilen addır. İstediğimiz adı verebiliriz. Burada, değeri [__main__] olan (18. satır) önceden tanımlanmış [__name__] özniteliği kullanılmıştır;
    • ikinci parametre, adlandırılmış bir parametredir; yani parametre sırasındaki konumu önemli değildir. [template_folder] adlı parametre, web uygulamasının statik sayfalarının bulunduğu klasörü belirtir. Statik sayfalar, tarayıcıya olduğu gibi gönderilir. Burada, statik sayfalar proje dizin yapısındaki [templates] klasöründe bulunacaktır. 7. satırda, çalıştırılan [exemple_01] komut dosyasını içeren [script_dir] klasörüne göreli bir yol belirledik;
    • üçüncü parametre de adlandırılmış bir parametredir. [static_folder], HTML belgesinin kaynaklarının (resimler, videolar vb.) bulunduğu klasörü belirtir. Burada da, çalıştırılan [exemple_01] betiğini içeren [script_dir] klasörüne göreli bir yol belirledik;
  • 10-14. satırlar: Web uygulaması tarafından kabul edilen URL'leri tanımlıyoruz. Her bir URL, bir web tarayıcısı tarafından URL talep edildiğinde çalıştırılan bir işleve eşleştirilmiştir;
  • 11. satır: Uygulamadaki tek URL, URL ve [/]'tir. [@app.route('/')]'te, [app]'in 7. satırda başlatılan değişken olduğunu unutmayın. Dolayısıyla, rotaların tanımı (uygulama tarafından yönetilen çeşitli URL'ler) mutlaka [app] uygulamasının tanımından sonra gelir. Bu son isim serbesttir;
  • 12-14. satırlar: URL [/] web uygulamasına [exemple_01] talebinde bulunulduğunda çalıştırılan işlev;
  • 12. satır: Bir URL ile ilişkili işlevin adı herhangi bir şekilde belirlenebilir. Bazen, ilişkili URL'ten öğeleri almak için parametreleri olabilir. Burada parametre yoktur;
  • 14. satır:
    • [render_template] işlevi, parametresi tarafından üretilen metin belgesini içeren bir karakter dizesi döndürür. Burada bu parametre [balises.html]'tir. 7. satırdaki [template_folder] nedeniyle, bu belge [f"{script_dir}/../templates"] klasöründe aranacaktır. Belge gerçekten de orada bulunmaktadır;
    • [make_response] işlevi, URL [/]'i talep eden tarayıcı için HTTP yanıtını üretir. |le protocole HTTP| paragrafında gördüğümüz gibi, bir HTTP yanıtı iki öğeden oluşur:
      • HTTP başlıkları;
      • tarayıcı tarafından istenen belge, bu örnekte bir HTML belgesi;
  1. satırda, [make_response] işlevine HTTP başlıklarını oluşturması için hiçbir parametre verilmemiştir. Bu durumda işlev, varsayılan başlıkları oluşturacaktır. Bu HTTP başlıklarını nasıl ayarlayacağımızı daha sonra göreceğiz.
  • Son olarak, tarayıcı Flask uygulamasından URL / adresini istediğinde, [balises.html] sayfasını alır;
  • 17-20. satırlar: Bu satırlar, [exemple_01] web uygulamasını çalıştıracak web sunucusunu başlatmaya yarar;
    • 18. satır: Bu koşul, yalnızca [exemple_01] betiği bir konsol içinde çalıştırıldığında geçerlidir;
    • 19. satır: 7. satırdaki [app] uygulaması yapılandırılır:
    • [ENV="development"] adlı parametre, web sunucusunu geliştirme moduna geçirir: geliştirici uygulamanın bir öğesini değiştirdiğinde, uygulama yeniden oluşturulur ve web sunucusuna gönderilir. Geliştiricinin yeni bir çalıştırma talebinde bulunmasına gerek yoktur;
    • [DEBUG=True] adlı parametre, geliştiricinin uygulama koduna durak noktaları eklemesine olanak tanır;
    • 20. satır: web uygulaması başlatılır: bir web sunucusu oluşturulur ve web uygulaması, web istemcilerinin isteklerine yanıt verebilmek üzere bu sunucuya dağıtılır;

İşte bir yürütme örneği:

Image

Ardından yürütme konsolunda aşağıdaki günlükler görüntülenir:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/01/main/exemple_01.py
 * Serving Flask app "exemple_01" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Çıkmak için CTRL+C tuşlarına basın)
  • 2. satır: sunucu, yürütülen komut dosyasını görüntüler;
  • 3. satır: Geliştirme modundayız;
  • satır 4-5: sunucu, [debug] modunda başlatıldığını algılar. Ardından yeniden başlar (satır 5). Dolayısıyla [debug] modu, başlatma sürecini biraz yavaşlatır;
  • satır 8: URL, burada dağıtılmış web uygulaması [exemple_01] kullanılabilir durumdadır;

Bir web tarayıcısı kullanarak URL [http://127.0.0.1:5000/] adresini ziyaret edelim:

Image

Beklenen [balises.html] belgesini başarıyla elde ediyoruz.

22.2.2. [exemple_02] komut dosyası: HTML belgesini dinamik olarak oluşturma

Image

[exemple_02] [1] komut dosyası, aşağıdaki [exemple_02.html] [2] belgesini oluşturacaktır:


<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>{{page.title}}</title>
</head>
<body>
    <b>{{page.contents}}</b>
</body>
</html>

Bu belge dinamiktir, çünkü içeriği ancak web sunucusu tarafından sunulduğu anda tam olarak bilinir. Nitekim, 5. ve 8. satırlarda, sayfanın yazıldığı sırada bilinmeyen iki öğe bulunmaktadır. Bu öğeler, ancak sayfa bir istemciye gönderildiğinde bilinir hale gelir. Daha sonra, karakter dizilerinden oluşan değerleriyle değiştirilirler.

  • 5. ve 8. satırlar: {{ifade}} sözdizimi, Jinja2 şablon dilinin bir sözdizimidir [https://jinja.palletsprojects.com/en/2.11.x/]. Sayfa bir istemciye gönderilmeden önce, sayfanın dinamik öğeleri (5. ve 8. satırlar) değerlendirilir ve değerleriyle değiştirilir;
  • 5. satır: [page.title] sözdizimi kullanılmıştır. Dolayısıyla, sayfanın gönderilmeden önce oluşturulurken [page] değişkeninin bilindiği varsayılmıştır; bunun nasıl olacağına daha sonra bakacağız. {{ifade}} sözdiziminde istediğimiz değişken adlarını kullanabiliriz. 5. ve 8. satırlarda, örneğin {{title}} ve {{contents}} kullanabiliriz. Bu durumda [title] ve [contents]’in sayfanın parametreleri olduğunu söyleyebiliriz. Bundan sonra daima aynı tekniği kullanacağız:
    • sayfanın tek parametresi [page] adlı bir sözlük olacaktır;
    • bu sözlüğün öznitelikleri sayfada kullanılacak. Burada [page.title] 5. satır ve [page.contents] 8. satır;

[exemple_02.py] web uygulaması şu şekildedir:


from flask import Flask, make_response, render_template

# Flask uygulaması
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")


# Ana Sayfa URL
@app.route('/')
def index():
    # sayfa içeriği sözlük biçiminde
    page = {"title": "un titre", "contents": "un contenu"}
    # sayfanın görüntülenmesi
    return make_response(render_template("exemple_02.html", page=page))


# ana sayfa
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • önceki örnekte 4-5 ve 18-20. satırları zaten açıklamıştık. Örneklerimizde her zaman bu şemayı kullanacağız;
  • 9. satır: Web uygulaması tarafından sunulan tek URL, URL'tir /;
  • 14. satır: URL / adresine sunulan belge, az önce yorumladığımız [exemple_02.html] belgesidir. Bu belgenin, [page] adlı bir sözlük olan bir parametreye sahip olduğunu biliyoruz;
  • 12. satır: [exemple_02.html] sayfasına parametre olarak aktarılacak sözlüğü tanımlıyoruz. Bu sözlüğe herhangi bir isim verilebilir. Ancak, HTML belgesinde kullanılan [title, contents] özniteliklerine sahip olması gerekir;
  • 14. satır: [render_template] işlevinin görevi, [exemple_02.html] belgesindeki karakter dizisini oluşturmaktır. Bu belge parametreli bir belge olduğundan, [render_template] işlevine beklenen parametreyi veya parametreleri aktarıyoruz. Burada bunu, [page] adlı parametreye bir değer atayarak yapıyoruz. [page=page] işleminde:
    • = işaretinin solunda, [exemple_02.html] belgesinde kullanılan [page] parametresi bulunur;
    • = işaretinin sağında, 12. satırda tanımlanan [page] değeri bulunur;
    • genel olarak, bir HTML belgesinde [param1, param2, …, paramn] parametreleri varsa, bunların değerleri [render_template] işlevine [render_template(document, param1=valeur1, param2=valeur2, …] biçiminde aktarılır;

[exemple_02]'i çalıştırmadan önce, [exemple_01]'in çalışmasını durdurmamız gerekir:

Image

Bir komut dosyası 1'i çalıştırırken, sanki komut dosyası 2 çalışıyormuş gibi bir izlenim edindiyseniz, bunun nedeni muhtemelen komut dosyası 2'nin hâlâ çalışır durumda olmasıdır. Bilinen bir duruma geri dönmek için, PyCharm'te (PyCharm penceresinin sağ üst köşesinde) çalışmakta olan tüm işlemleri durdurabilirsiniz:

Image

[exemple_02] komut dosyasını çalıştıralım:

Image

Konsol günlükleri şu şekildedir:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/01/main/exemple_02.py
 * Serving Flask app "exemple_02" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Çıkmak için CTRL+C tuşlarına basın)
  1. satır, [exemple_02] uygulamasının (1. satır) [localhost] makinesindeki dağıtım bağlantı noktasını (5000) gösterir. Önceki satırlar her zaman aynı olduğundan, bunları bir daha göstermeyeceğiz.

Bir tarayıcı kullanarak URL [http://localhost:5000/] adresini çağırdığımızda:

Image

  • {{page.title}} ifadesi [1] sonucunu verdi;
  • {{page.contents}} ifadesi [2] sonucunu verdi;

22.2.3. [exemple_03] komut dosyası: sayfa parçacıklarını kullanma

Image

  • [1]'te, [exemple_03.py] komut dosyası, [exemple_03.html] ve [2] dinamik belgelerini oluşturacaktır. Bu belge, [fragment_01.html, fragment_02.html] ve [3] sayfa parçalarından oluşturulacaktır;

[exemple_03.html] belgesi şu şekilde olacaktır:


<!DOCTYPE html>
<html lang="fr">
{% include "fragments/fragment_01.html" %}
<body>
{% include "fragments/fragment_02.html" %}
</body>
</html>
  • 3. ve 5. satırlarda, belgeye dış unsurları dahil etmek için Jinja2'nin [include] yönergesi kullanılır;
  • sözdizimi {% include … %} şeklindedir. [include] yönergesinin parametresi, eklenecek belgenin yoludur. Bu yol, Flask uygulamasının [template_folder] parametresine göredir:

app = Flask(__name__, template_folder="../templates", static_folder="../static")

Dolayısıyla burada, belge yollarının referans noktası [templates] klasörüdür.

[fragment_01.html] parçası (adlar elbette isteğe bağlıdır) şöyledir:


<meta charset="UTF-8">
<title>{{page.title}}</title>

[fragment_02.html] parçası şöyledir:


<b>{{page.contents}}</b>

Bu parçaları kullanarak [exemple_03.html] belgesini yeniden oluşturursak, şu kodu elde ederiz:


<!DOCTYPE html>
<html lang="fr">
<meta charset="UTF-8">
<title>{{page.title}}</title>
<body>
<b>{{page.contents}}</b>
</body>
</html>

Böylece, [exemple_02.html] ile aynı olan, ancak parçalardan oluşturulmuş bir belge elde ederiz.

[exemple_03.py] web betiği şöyledir:


import os

from flask import Flask, make_response, render_template

# Flask uygulaması
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}/../templates", static_folder=f"{script_dir}/../static")


# Ana Sayfa URL
@app.route('/')
def index():
    # sayfa içeriği
    page = {"title": "un autre titre", "contents": "un autre contenu"}
    # sayfa görüntüsü
    return make_response(render_template("views/exemple_03.html", page=page))


# ana işlev
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()

Kod, [exemple_02.py]'inkine benzerdir. 16. satırda, 7. satırdaki [template_folder]'in alt klasörlerinde bulunan belgelere nasıl başvurulabileceği gösterilmektedir.

[exemple_03.py] komut dosyasının çalıştırılması, tarayıcıda aşağıdaki sonuçları verir:

Image

22.3. [flask/02] komut dosyaları: tarih ve saat web hizmeti

Image

[date_time_server.html] belgesi şöyledir:


<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Date et heure du moment</title>
</head>
<body>
    <b>Date et heure du moment : {{page.date_heure}}</b>
</body>
</html>
  • 8. satır: sayfa, [page.date_heure] parametresini kabul eder;

[date_time_server.py] web hizmeti şöyledir:


# ithalatlar
import os
import time

from flask import Flask, make_response, render_template

# Flask uygulaması
script_dir = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__, template_folder=f"{script_dir}")


# Ana Sayfa URL
@app.route('/')
def index():
    # müşteriye saat bilgisi gönderimi
    # time.localtime: 01/01/1970'den bu yana geçen milisaniye sayısı
    # time.strftime, saat ve tarihi biçimlendirmeye olanak tanır
    # 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
    # M: dakikalar
    # S: saniye

    # o anki tarih / saat
    time_of_day = time.strftime('%d/%m/%y %H:%M:%S', time.localtime())
    # müşteriye gönderilecek belge oluşturulur
    page = {"date_heure": time_of_day}
    document = render_template("date_time_server.html", page=page)
    print("document", type(document), document)
    # müşteriye HTTP yanıtı
    response = make_response(document)
    print("response", type(response), response)
    return response


# sadece ana işlem
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 13. satır: Web uygulaması yalnızca URL / parametresini kullanır;
  • 15-24. satırlar: tarih ve saati nasıl elde edileceğini ve nasıl görüntüleneceğini açıklamaktadır;
  • 27. satır: o anki tarih ve saati temsil eden karakter dizisi;
  • 28-30. satırlar: 29. satırdaki [page] sözlüğünü geçirerek [date_time_server.html] dinamik belgesini oluşturuyoruz;
  • satır 31: [document]'in türünü ve belgenin kendisini görüntüleriz. Bunun bir karakter dizisi olduğunu göstermek istiyoruz;
  • 33. satır: Müşteriye gönderilecek olan HTTP yanıtı oluşturulur (henüz gönderilmemiştir);
  • satır 34: türünü ve değerini görüntüler;
  • 35. satır: HTTP yanıtı müşteriye gönderilir;

Komut dosyasının çalıştırılması, tarayıcıda aşağıdaki sonucu verir:

Image

Konsoldaki günlük kayıtları şöyledir:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\flask\02\date_time_server.py
 * Serving Flask app "date_time_server" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Çıkmak için CTRL+C tuşuna basın)
127.0.0.1 - - [10/Jul/2020 09:32:09] "GET / HTTP/1.1" 200 -
document <class 'str'> <!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Date et heure du moment</title>
</head>
<body>
    <b>Date et heure du moment : 10/07/20 09:42:33</b>
</body>
</html>
response <class 'flask.wrappers.Response'> <Response 195 bytes [200 OK]>
  • 10. satır: [render_template] tarafından döndürülen değerin türünün [str] olduğu görülmektedir. Bu karakter dizisi, yorumlandıktan sonra (10-19. satırlar) [date_time_server.html] belgesinden başka bir şey değildir;
  • 20. satır: [make_response] tarafından döndürülen değerin türünün [flask.wrappers.Response] olduğu görülmektedir. [Response.__str__] işlevi, [Response] nesnesini görüntülemek için örtük olarak çağrılmıştır. Bu işlev tarafından döndürülen dize, oluşturulacak HTTP yanıtı hakkında iki bilgi vermektedir:
    • gönderilen belge 195 bayt büyüklüğündedir;
    • HTTP yanıtının durumu [200 OK]'tir. Daha sonra bu durum koduna erişebileceğimizi göreceğiz;

22.4. [flask/03] komut dosyaları: düz metin üreten web hizmetleri

Önceki bir örnekte, web hizmetinin aşağıdaki belgeyi sağladığını görmüştük:


<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Date et heure du moment</title>
</head>
<body>
    <b>Date et heure du moment : {{page.date_heure}}</b>
</body>
</html>

Bir web istemcisi, 8. satırdaki [page.date_heure] bilgisiyle ilgilenebilir, ancak etrafındaki HTML biçimlendirmeyle ilgilenmeyebilir. Web hizmeti bu bilgiyi basit bir karakter dizisi olarak sunabilir. Burada bu tür web hizmetlerine ilişkin örnekler sunacağız.

22.4.1. [main_01] komut dosyası

Image

  • [main_01] web hizmetidir;
  • [config], web uygulamasının yapılandırma betiğidir;
  • web hizmeti, [2]'te tanımlanan bazı varlıkları kullanır;

[config] betiği şöyledir:


def configure():
    # yapılandırmadaki göreceli yolların mutlak yolu
    rootDir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # uygulamanın bağımlılıkları
    absolute_dependencies = [
        # Kişi, Yardımcı Programlar, MyException
        f"{rootDir}/classes/02/entities",

    ]
    # syspath ayarlanıyor
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # yapılandırma oluşturuluyor
    return {}

Bu yapılandırmanın temel amacı, web hizmetinin Python yolunu tanımlamaktır. [2] varlıklarının (8. satır) bulunabilmesi gerekir.

[main_01] web komut dosyası şöyledir:


# uygulamayı yapılandırıyoruz
import config
config=config.configure()

# ithalatlar
from flask import Flask, make_response
from flask_api import status

# bağımlılıklar
from Personne import Personne

# Flask uygulaması (burada statik belgeler yok)
app = Flask(__name__)


# Ana Sayfa URL
@app.route('/')
def index():
    # bir kişi
    personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
    # yanıt HTTP
    response = make_response(str(personne))
    # başlıklar HTTP
    response.headers.set("Content-type", "application/json; charser=utf8")
    # yanıt gönderiliyor HTTP
    return response, status.HTTP_200_OK


# sadece ana işlev
if __name__ == '__main__':
    # sunucuyu başlatıyoruz
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 1-3. satırlar: Uygulamanın Python Yolu belirlenir;
  • 5-10. satırlar: betiğin ihtiyaç duyduğu öğeler içe aktarılır;
  • 17. satır: web hizmeti yalnızca URL / dosyasını sunar;
  • 20. satır: bir [Personne] nesnesi oluşturulur;
  • 22. satır: Kişiyi temsil eden karakter dizisiyle bir HTTP yanıtı oluşturulur. [Personne.__str__] işlevi çağrılacaktır. Bu işlev, kişinin [asdict] sözlüğünden jSON dizesini döndürür (bkz. |classe BaseEntity|). [make_response] işlevinin parametresi, müşteriye gönderilen metin belgesidir; dolayısıyla burada bir kişiye ait jSON karakter dizisidir;
  • 24. satır: yanıtın HTTP başlıklarına, müşteriye ne tür bir belge alacağını belirten bir [Content-type] başlığı eklenir; burada, UTF-8 ile kodlanmış bir jSON belgesi söz konusudur;
  • 26. satır: İki öğeden oluşan bir tuple döndürülür:
    • müşteriye verilen yanıt, HTTP başlıkları ve belge;
    • yanıtın durum kodu. Burada [200 OK] durum kodunu döndürmek istiyoruz. Farklı durum kodları, 7. satırda içe aktarılan [flask_api] modülündeki sabitlerle tanımlanmıştır;

[flask_api] modülü varsayılan olarak mevcut değildir. Bu modülün yüklenmesi gerekir. Bunu bir PyCharm terminalinde şu şekilde yapıyoruz:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\inet\utilitaires>pip install flask_api
Collecting flask_api
  Downloading Flask_API-2.0-py3-none-any.whl (119 kB)
     || 119 kB 544 kB/s
Requirement already satisfied: Flask>=1.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from flask_api) (1.1.2)
Requirement already satisfied: Jinja2>=2.10.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (2.11.2)
Requirement already satisfied: Werkzeug>=0.15 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (1.0.1)
Requirement already satisfied: click>=5.1 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (7.1.2)
Requirement already satisfied: itsdangerous>=0.24 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Flask>=1.1->flask_api) (1.1.0)
Requirement already satisfied: MarkupSafe>=0.23 in c:\data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages (from Jinja2>=2.10.1->Flask>=1.1->flask_api) (1.1.1
)
Installing collected packages: flask-api
Successfully installed flask-api-2.0

[main_01] web betiği çalıştırıldığında, tarayıcıda şu sonuçlar elde edilir:

Image

  • [2]'te, alınan jSON dizesi;
  • [3-4]'te, alınan belgenin içeriği görüntülenir. HTML biçimlendirmesinin olmadığı, sadece jSON dizisinin bulunduğu görülür;

Şimdi web hizmeti tarafından müşteriye gönderilen [Content-Type] başlığının rolünü inceleyelim. Tarayıcıyı geliştirici moduna geçiriyoruz (genellikle F12) ve aynı URL'i yeniden istiyoruz. Aşağıda bir Chrome tarayıcısının ekran görüntüsü yer almaktadır:

Image

  • [1]'te, [Network] sekmesini seçin;
  • [2, 4]'te: tarayıcı tarafından istenen URL;
  • [3]'te, [Headers] sekmesini seçin (HTTP başlıkları);
  • [5]’te, alınan HTTP yanıtının durum kodu;
  • [6]'te, müşteriye bir metin alacağını belirten başlık jSON. Bu, müşterinin yanıtı hazırlamasına olanak tanır. Bu nedenle, Chrome’un bir jSON yanıtını veya temel bir metin yanıtını görüntülemek için kullandığı yazı tipi aynı değildir;

Image

  • [8]'te, web hizmeti tarafından gönderilen belgeye erişmek için [Response] sekmesini seçiyoruz; burada basit bir jSON dizisi var;

22.4.2. Postman

[Postman], bir web uygulamasının farklı URL'lerini sorgulamamızı sağlayacak araçtır. Bu araç bize şunları sağlar:

  • herhangi bir URL'i kullanabiliriz: bunlar elle oluşturulur;
  • bir GET, POST, PUT, OPTIONS… ile web sunucusuna sorgu göndermek;
  • GET veya POST parametrelerini belirtmek;
  • isteğin HTTP başlıklarını belirlemek;
  • jSON, XML, HTML biçiminde bir yanıt almak,
  • yanıtın HTTP başlıklarına erişmek. Böylece sunucudan gelen tam HTTP yanıtına erişim sağlanır;

[Postman], HTTP protokolünün istemci/sunucu iletişimini anlamak için mükemmel bir eğitim aracıdır.

[Postman], URL [https://www.getpostman.com/downloads/] adresinde mevcuttur. Kendi [Postman] sürümünüzü yükleyin. Kurulum sırasında bir hesap oluşturmanız istenecektir: bu hesap burada gerekli değildir. [Postman] hesabı, farklı cihazları senkronize etmek ve bir cihazdaki ayarların diğerine aktarılmasını sağlamak için kullanılır. Bunların hiçbiri burada gerekli değildir.

Kurulum tamamlandığında, [Postman] şu arayüzü gösterir:

Image

  • [2-3]'te ürün ayarlarına erişebilirsiniz;

Image

  • [6]'te, bu belgede kullanılan sürüm;

Burada, önceki jSON web hizmetini test etmek için [Postman]'i kullanacağız:

  • [flask/03/main_01] komut dosyasını çalıştırıyoruz;
  • ardından Postman ile URL [http://localhost:5000/]'i çağırıyoruz; Image
  • [1]'te bir istek oluşturuyoruz;
  • [2]'te, bu bir HTTP GET isteği olacaktır;
  • [3]'te, sorgulanan web hizmetinin URL'i;
  • [4]'te, istek web hizmetine gönderilir; Image
  • [5]'te, alınan belgeyi görüntüleyen [Body] sekmesi seçilir;
  • [6]'te, alınan belgeyi uygun bir biçimlendirmeyle görüntüleyen [Pretty] sekmesi seçilir; burada, jSON dizisine uygun bir biçimlendirme kullanılır;
  • [7]'te, alınan jSON belgesi;
  • [8-9]'te, biçimlendirilmemiş olarak alınan belge; Image
  • [10]'te, Postman tarafından alınan HTTP başlıkları görüntülenir;
  • [11]'te, alınan yanıtın HTTP durumu;
  • [12]'te, alınan HTTP başlıkları;
  • [13]'te, Postman'ın jSON dizesini alacağını bilmesini sağlayan [Content-type] başlığı. Postman bu bilgiyi, alınan belgeyi belirli bir şekilde biçimlendirmek için kullandı;

Postman'ı kullanmanın başka bir yolu daha vardır. Bu, Postman konsolunu (Ctrl-Alt-C) kullanmaktır. Bu konsol, istemci/sunucu diyaloğunu görmenizi sağlar. Ctrl-Alt-C tuş kombinasyonunun yanı sıra, Postman konsoluna Postman ana penceresinin sol alt köşesindeki bir simge aracılığıyla da erişilebilir:

Image

Postman konsolu, bir Postman isteği yürütüldüğünde gerçekleşen istemci-sunucu diyaloglarını kaydeder:

Image

  • [3] dosyasında, Postman'ın başlatılmasından bu yana yaptığı isteklerin listesi yer almaktadır. En son istekler listenin en altında yer almaktadır;
  • [4] dosyasında, Postman tarafından yapılan HTTP isteği;
  • [5-6] dosyasında, web sunucusu tarafından verilen HTTP yanıtı;
  • [7]'te, [raw] modunda, yani herhangi bir sunum düzenlemesi yapılmamış haliyle günlükleri görebilirsiniz;

[raw] modunda konsol penceresi şu şekilde görünür:

Image

  • [8]'te, Postman tarafından web sunucusuna gönderilen HTTP isteği;
  • [9] modunda, web sunucusu tarafından verilen HTTP yanıtı;
  • [10]'te, [pretty logs] moduna geri dönülebilir;

Açıklamaları kolaylaştırmak amacıyla, Postman konsolundan elde edilen satırları numaralandıracağız.

İstemci için:

1
2
3
4
5
6
7
8
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 70e2acaa-b3e5-46f6-8375-989e6b94e694
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Sunucu için:

1
2
3
4
5
6
HTTP/1.0 200 OK
Content-type: application/json; charser=utf8
Content-Length: 56
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:19:56 GMT
{"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87}

Bundan sonra, web istemcisi olarak esas olarak şunu kullanacağız:

  • [Postman]'i web istemcisi olarak;
  • [Postman] konsolunu, istemci/sunucu diyaloğunu açıklamak için [raw mode] olarak;

22.4.3. [main_02] komut dosyası

Image

[main_02] web komut dosyası şu şekildedir:


# uygulamayı yapılandırıyoruz
import config
config=config.configure()

# ithalatlar
from flask import Flask, make_response
from flask_api import status

# bağımlılıklar
from Personne import Personne

# Flask uygulaması
app = Flask(__name__)


# Ana Sayfa URL
@app.route('/')
def index():
    # bir kişi
    personne = Personne().fromdict({"prénom": "Aglaë", "nom": "de la Hûche", "âge": 87})
    # içerik
    response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
    # başlıklar HTTP
    response.headers.set("Content-Type", "text/plain; charset=utf8")
    # yanıt HTTP
    return response, status.HTTP_200_OK


# sadece ana
if __name__ == '__main__':
    # sunucu başlatılıyor
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • [main_02] betiği, [main_01] betiğine benzerdir. İki noktada farklılık gösterir:
    • 22. satır: İstemciye gönderilen belge, jSON dizesi değil, ham bir karakter dizisidir;
    • 24. satır: Bu durum, belgenin türünü [text/plain] olarak belirten HTTP [Content-Type] başlığında yansıtılır;

[main_02] web betiğini çalıştırıyoruz ve ardından [Postman]'i kullanarak sorguluyoruz:

Image

  • [1-3] ile web hizmetine sorgu gönderilir;
  • [5]'te, yanıtın OK durumu;
  • [4, 6]'te, yanıtın HTTP başlıkları;
  • [7]'te, [Content-Type] başlığı;
  • [8-10]'e, web hizmeti tarafından gönderilen belge, bir karakter dizisi;

Postman konsolu aşağıdaki günlükleri gösterir:

İstemciden gelen istek:

1
2
3
4
5
6
7
8
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 7c7fc9f3-8df8-49ae-9dc8-53c2d87d111a
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Sunucu yanıtı:


HTTP/1.0 200 OK
Content-Type: text/plain; charset=utf8
Content-Length: 34
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:34:22 GMT

personne[Aglaë, de la Hûche, 87]

22.4.4. [main_03] komut dosyası

Image

[main_03] web komut dosyası şöyledir:


# uygulama yapılandırılıyor
import config
config = config.configure()

# ithalatlar
from flask import Flask, make_response
from flask_api import status

# bağımlılıklar
from MyException import MyException
from Personne import Personne

# Flask uygulaması
app = Flask(__name__)


# Ana Sayfa URL
@app.route('/')
def index():
    # yanlış bir kişi
    msg_erreur = None
    try:
        personne = Personne().fromdict({"prénom": "", "nom": "", "âge": 87})
    except MyException as erreur:
        msg_erreur = f"{erreur}"
    # hata mı?
    if msg_erreur:
        response = make_response(msg_erreur)
        status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
    else:
        response = make_response(f"personne[{personne.prénom}, {personne.nom}, {personne.âge}]")
        status_code = status.HTTP_200_OK
    # başlıklar HTTP
    response.headers.set("Content-Type", "text/plain; charset=utf8")
    # yanıt HTTP
    return response, status_code


# sadece ana
if __name__ == '__main__':
    # sunucu başlatılıyor
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 23. satır: yanlış bir kişi örneği oluşturularak hata meydana geliyor;
  • 27-29. satırlar: Hata nedeniyle:
    • 28. satır: hata mesajını içeren bir HTTP yanıtı hazırlanıyor;
    • 29. satır: HTTP durum koduna [500 Internal Server Error] hata değeri atanır;
  • satır 34: müşteriye düz metin gönderileceği bildirilir;
  • satır 36: müşteriye HTTP yanıtı gönderilir;

[main_03] web hizmetini başlatıyoruz ve Postman'ı kullanarak bu hizmete sorgu gönderiyoruz:

Image

  • [1-3] adresine isteği gönderiyoruz;
  • [4] adresinde, [500 INTERNAL SERVER ERROR] durum kodunu içeren bir yanıt alıyoruz;
  • [5-7]'te: yanıt, meydana gelen hatayı açıklayan bir metindir;

Image

  • [8-10]'te, web hizmetinin yanıtındaki HTTP başlıkları;

Postman konsolunda, [raw] modundaki sonuçlar şunlardır:

İstemciden gelen istek:

1
2
3
4
5
6
7
8
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 925ff036-a360-47af-adf6-78173c01a247
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Sunucu yanıtı:


HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Type: text/plain; charset=utf8
Content-Length: 74
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Mon, 13 Jul 2020 17:39:24 GMT

MyException[11, Le prénom doit être une chaîne de caractères non vide]

22.5. [flask/04] komut dosyaları: isteğe kapsüllenmiş bilgiler

Image

[request_parameters.py] komut dosyası, web hizmetinin bir web istemcisinin isteğinde yer alan çeşitli bilgilere erişebildiğini göstermeyi amaçlamaktadır. Kod şu şekildedir:


# ithalat
from flask import Flask, make_response, request
from flask_api import status
# Flask uygulaması
app = Flask(__name__)


# Ana Sayfa URL
@app.route('/', methods=['GET', 'POST'])
def index():
    # istek parametreleri
    request_data = {}
    request_data["environ"] = f"{request.environ}"
    request_data["path"] = request.path
    request_data["full_path"] = request.full_path
    request_data["script_root"] = request.script_root
    request_data["url"] = request.url
    request_data["base_url"] = request.base_url
    request_data["url_root"] = request.url_root
    request_data["accept_charsets"] = request.accept_charsets
    request_data["accept_encodings"] = request.accept_encodings
    request_data["accept_languages"] = request.accept_languages
    request_data["accept_mimetypes"] = request.accept_mimetypes
    request_data["args"] = request.args
    request_data["content_encoding"] = request.content_encoding
    request_data["content_length"] = request.content_length
    request_data["content_type"] = request.content_type
    request_data["endpoint"] = request.endpoint
    request_data["files"] = request.files
    request_data["form"] = request.form
    request_data["host"] = request.host
    request_data["method"] = request.method
    request_data["query_string"] = request.query_string.decode()
    request_data["referrer"] = request.referrer
    request_data["remote_addr"] = request.remote_addr
    request_data["remote_user"] = request.remote_user
    request_data["scheme"] = request.scheme
    request_data["script_root"] = request.script_root
    request_data["user_agent"] = f"{request.user_agent}"
    request_data["values"] = request.values
    # yanıt HTTP
    response = make_response(request_data)
    # başlıklar HTTP
    response.headers["Content-Type"] = "application/json; charset=utf-8"
    # yanıtın gönderilmesi HTTP
    return response, status.HTTP_200_OK


# ana
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 9. satır: Bir değişiklik yapıyoruz. İstemcinin isteğinde hangi fiillerin kullanılabileceğini belirliyoruz. Postman bu fiillerin listesini vermektedir:

Image

İlk iki [GET, POST] en çok kullanılanlardır ve bu belgede de sadece bunlar kullanılacaktır. Kodun 9. satırına geri dönersek, [methods] parametresi, URL tarafından izin verilen yukarıdaki listede yer alan yöntemlerin listesini içerir. Bu parametre yoksa, yalnızca [GET] yöntemi izin verilir. Şimdiye kadar olan durum buydu;

  • 12. satır: [request_data] sözlüğünü oluşturacağız;
  • 13. satır: İstemcinin isteği, 2. satırda içe aktarılan, [werkzeug.local.LocalProxy] türünde önceden tanımlanmış bir [request] nesnesinde mevcuttur. Takip eden satırlar bu nesnenin çeşitli özniteliklerini alır;
  • [request] nesnesinin her bir özniteliğini ayrıntılı olarak açıklamaktansa, bu kodu çalıştırıp sonuçlara bakacağız. Böylece görüntülenen farklı özniteliklerin anlamını daha iyi anlayacağız;
  • 42. satır: [request_data] sözlüğü, HTTP yanıtının içeriği olacaktır. Bunun metin olması gerektiğini hatırlayalım. Flask, sözlükleri otomatik olarak jSON dizelerine dönüştürür;
  • 44. satır: İstemciye, jSON alacağı bildirilir;
  • 46. satır: yanıt müşteriye gönderilir;

Postman istemcisi ile, önceki web hizmetine şu isteği gönderiyoruz:

Image

  • [1-2] biçiminde gönderilen istek;
  • [2]'te istek parametreleri ayarlanır. Parametreler, URL'e [ ?param1=valeur1&param2=valeur2] biçiminde eklenir. Bu parametreleri Postman'da girmek için iki yol vardır:
    • URL dosyasına doğrudan yazmak;
    • [3-4] dosyasına yazmak;

Her iki yöntem de aynı sonucu verir;

İsteğe başka parametreler de ekliyoruz:

Image

  • [5-7]'te, isteğin gövdesine (=body) parametreler ekliyoruz. URL'teki parametreler bir web tarayıcısı kullanıcısı tarafından görülebilirken, isteğin gövdesinde yer alan parametreler görünmez. Tarayıcı (veya buradaki Postman), bunları HTTP başlıklarından sonra sunucuya gönderir. Böylece web istemcisinin isteği, web sunucusunun yanıtıyla aynı yapıya sahip olur: HTTP başlıklarının ardından bir belge gelir. Bu, istemcinin isteğinde iki yeni HTTP başlığının görünmesine neden olur:
    • [Content-Type]: İstemci, sunucuya gönderdiği belgenin türünü bildirir;
    • [Content-Length]: belgenin boyutunu bayt cinsinden belirtir;
  • [6]'te, [7]'te bildirilen parametreler için kullanılacak kodlama belirtilir. Bu parametreler çeşitli şekillerde kodlanabilir. [x-www-form-urlencoded], tarayıcılar tarafından sıklıkla kullanılan bir yöntemdir;

Oluşturulacak istek şu şekildedir:

Image

Bu isteğe verilen yanıt şöyledir:

Image

  • [1-5]'te, jSON [3] dizesi alındı;
  • web servisini genellikle ilgilendiren, URL ve [ ?param1=valeur1&param2=valeur2] parametreleri ile isteğin gövdesinde (belge) iletilen parametrelerdir. Müşteri genellikle bu şekilde bilgi iletir. [5]'te, URL'in parametrelerinin [request.args]'te mevcut olduğu görülmektedir;

Yanıtın geri kalanı şöyledir:

Image

  • [9]'te, istek gövdesine eklenen parametrelerin öznitelikleri:
    • [content_type], isteğe eşlik eden belgenin türüdür. Bu belgenin, [param=valeur] türünde ve [x-www-form-urlencoded] biçiminde kodlanmış bilgiler içerdiğini gördük. Dolayısıyla Postman, belgenin niteliğini belirten bir HTTP [Content-Type] başlığı oluşturmuştur;
    • [content_length], bu belgenin bayt cinsinden boyutudur;
  • [10]'te yer alan [request.environ] özniteliği, istemcinin isteğinin işlendiği ortam hakkında birçok bilgi içerir. Bu bilgilerin çoğu, [request] nesnesinin diğer özniteliklerinde bulunur;
  • [11]'te, isteğin gövdesinde bulunan parametreler [request.form] özniteliğinde mevcuttur;
  • [12]'te, isteği göndermek için kullanılan yöntem (burada [GET] yöntemi);
  • [13]'te, [request.values] özniteliği, URL'teki ve belge gövdesindeki tüm parametrelerin sözlüğüdür. İsteğin parametrelerini elde etmek için şu özniteliği kullanacağız:
    • [request.args], URL'te bulunan parametreleri almak için;
    • [request.form], belge gövdesinde bulunan parametreleri almak için;

Postman konsolunda günlükler şu şekildedir:

İstemciden gelen istek:

GET /?param1=valeur1&param2=valeur2 HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: cbfac6aa-71a0-4076-a0c3-91d36d74a4c0
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 60

nom=s%C3%A9l%C3%A9n%C3%A9&pr%C3%A9nom=agla%C3%AB&%C3%A2ge=77
  • 9. satır: sunucuya gönderilen belgenin türü 12. satır;
  • 11. satır: İsteğin HTTP başlıkları, gönderilen belgeden bir boş satırla ayrılmıştır. Sunucu, istemcinin HTTP başlıklarının sonunu bu şekilde tespit eder;
  • 12. satır: ‘url-kodlanmış’ belge. Aksanlı tüm karakterler kodlanmıştır;

İstemcinin yanıtı şöyledir:


HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 2433
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:09:09 GMT

{
  "accept_charsets": [], 
  "accept_encodings": [
    [
      "gzip", 
      1
    ], 
    [
      "deflate", 
      1
    ], 
    [
      "br", 
      1
    ]
  ], 
  "accept_languages": [], 
  "accept_mimetypes": [
    [
      "*/*", 
      1
    ]
  ], 
  "args": {
    "param1": "valeur1", 
    "param2": "valeur2"
  }, 
  "base_url": "http://localhost:5000/", 
  "content_encoding": null, 
  "content_length": 60, 
  "content_type": "application/x-www-form-urlencoded", 
  "endpoint": "index", 
  "environ": "{'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=908>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.server.shutdown': <function WSGIRequestHandler.make_environ.<locals>.shutdown_server at 0x00000173CA6E5160>, 'SERVER_SOFTWARE': 'Werkzeug/1.0.1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'PATH_INFO': '/', 'QUERY_STRING': 'param1=valeur1&param2=valeur2', 'REQUEST_URI': '/?param1=valeur1&param2=valeur2', 'RAW_URI': '/?param1=valeur1&param2=valeur2', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': 50592, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_USER_AGENT': 'PostmanRuntime/7.26.1', 'HTTP_ACCEPT': '*/*', 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_POSTMAN_TOKEN': 'cbfac6aa-71a0-4076-a0c3-91d36d74a4c0', 'HTTP_HOST': 'localhost:5000', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_CONNECTION': 'keep-alive', 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 'CONTENT_LENGTH': '60', 'werkzeug.request': <Request 'http://localhost:5000/?param1=valeur1&param2=valeur2' [GET]>}", 
  "files": {}, 
  "form": {
    "nom": "s\u00e9l\u00e9n\u00e9", 
    "pr\u00e9nom": "agla\u00eb", 
    "\u00e2ge": "77"
  }, 
  "full_path": "/?param1=valeur1&param2=valeur2", 
  "host": "localhost:5000", 
  "method": "GET", 
  "path": "/", 
  "query_string": "param1=valeur1&param2=valeur2", 
  "referrer": null, 
  "remote_addr": "127.0.0.1", 
  "remote_user": null, 
  "scheme": "http", 
  "script_root": "", 
  "url": "http://localhost:5000/?param1=valeur1&param2=valeur2", 
  "url_root": "http://localhost:5000/", 
  "user_agent": "PostmanRuntime/7.26.1", 
  "values": {
    "nom": "s\u00e9l\u00e9n\u00e9", 
    "param1": "valeur1", 
    "param2": "valeur2", 
    "pr\u00e9nom": "agla\u00eb", 
    "\u00e2ge": "77"
  }
}
  • 1-5. satırlar: yanıtın HTTP başlıkları, boş bir satırla sona erer;
  • satır 41-45: aksanlı karakterler UTF-8 kodlamasına tabi tutulmuştur;

Şimdi aynı parametrelerle aynı isteği göndermek için [POST] yöntemini kullanırsak, aynı yanıtı alırız; ancak [12] yönteminde [‘method’ : ‘POST’] elde ederiz.

Peki, GET ve POST yöntemleri arasındaki fark nedir? Aradaki fark çok küçük olup, tarihsel olarak tarayıcıların bu yöntemleri kullanma biçiminden kaynaklanmaktadır:

  • URL'teki parametreler kullanışlıdır, çünkü bu şekilde ayarlanmış bir URL, bir HTML belgesinde bağlantı olarak kullanılabilir. Kullanıcı, sunucudan farklı yanıtlar almak için parametreleri kendisi de değiştirebilir. Bu durumda, tarayıcılar genellikle [GET] yöntemini kullanır ve web sunucusuna gönderilen istekte gövde (content_length=0) bulunmaz (gizli parametre yoktur);
  • bazen parametrelerin URL'te görüntülenmesini istemeyebiliriz. Sunucuya gönderilen şifreler bu duruma örnektir. Ayrıca, URL'teki parametrelerin kapladığı boyut sınırlıdır (bir URL belirli bir boyutu aşamaz). İstek gövdesindeki parametrelerde ise bu sınırlama yoktur. Ayrıca, URL'te çok fazla parametre olması, onu okunaksız hale getirir. Bir web sitesine kayıt formunu yaygın bir örnek olarak ele alalım. Eskiden, HTML sayfalarında henüz JavaScript bulunmadığında, tarayıcılar girilen bilgileri bir POST aracılığıyla gönderirdi. O zamanlar buna "gönderilen değerler" denirdi;

Dolayısıyla web programlamasının başlangıcında:

  • GET yöntemleri daha çok bir web sunucusu tarafından sağlanan bilgilerin talep edilmesiyle ilişkilendirilirdi;
  • POST yöntemleri ise daha çok tarayıcıdan sunucuya bilgi göndermeyle ilişkilendirilirdi. Sunucu bu bilgilerle “zenginleştirilirdi”;

O zamandan beri JavaScript devreye girdi. Önceki örneklerde geliştiricinin kontrolü yoktu (bir bağlantıya tıklamak mutlaka bir GET'i tetikliyordu, bir formu göndermek mutlaka bir POST'i gerektiriyordu), ancak JavaScript kontrolü tekrar geliştiricilere verdi. Bu modelde, HTML sayfası, tarayıcıyı atlayabilen bir JavaScript koduyla ilişkilendirilmiştir. Böylece, bir bağlantıya yapılan tıklama JavaScript kodu tarafından yakalanabilir ve bu kod daha sonra sunucuya bir istek gönderen bir kodu çalıştırabilir. Bu istek, kullanıcı için görünmez olacaktır. Kullanıcı bunu görmeyecektir. Bu kod bir web istemcisidir ve Postman’da yaptığımız gibi, geliştirici istediği isteği oluşturabilir. Bir bağlantıya tıklama işlemine geri dönersek, geliştirici POST işlemini gerçekleştirebilir; oysa varsayılan olarak tarayıcı GET işlemini gerçekleştirirdi. Bu gelişmeler, GET ile POST arasındaki farkları daha az önemli hale getirmiştir.

Bununla birlikte, geliştiriciler genellikle şu kuralları benimser:

  • bir GET, sunucunun durumunu değiştirmemelidir. URL'te aynı parametrelerle gerçekleştirilen ardışık GET'ler aynı belgeyi döndürmelidir. Ayrıca, GET'in çoğu zaman gövdesi yoktur (ilişkili bir belge yoktur), sadece URL'te parametreler bulunur;
  • POST, sunucunun durumunu değiştirebilir. Parametreler genellikle isteğin gövdesinde gönderilir. Buna “POST değerleri” denir. Form örneği bunu en iyi şekilde ortaya koymaktadır: kullanıcı tarafından girilen değerler POST isteğinin gövdesine eklenecek ve sunucu bunları genellikle bir veritabanında olmak üzere bir yere kaydedecektir;

Belgenin devamında, herhangi bir özel kurala uymak zorunda değiliz.

22.6. [flask-05] komut dosyaları: kullanıcı belleği yönetimi

22.6.1. Giriş

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

  • istemci, web hizmeti sunucusunun 80 numaralı bağlantı noktasına bir bağlantı açar;
  • ş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 çeşitli yöntemler kullanabilir:

  1. Bir müşteriden gelen ilk istek sırasında, yanıtına bir kimlik numarası ekler ve müşteri daha sonra her yeni istekte bu kimlik numarasını sunucuya geri göndermelidir. Her müşteri için farklı olan bu tanımlayıcı sayesinde sunucu, bir müşteriyi tanıyabilir. Böylece, bu müşteri için, müşterinin tanımlayıcısına benzersiz bir şekilde eşleştirilmiş bir bellek yönetebilir. Örneğin, PHP hizmetleri bu şekilde çalışır;
  2. bir müşteriden gelen ilk istekte, yanıtına bir tanımlayıcı değil, doğrudan kullanıcının belleğini ekler. Sunucu tarafında hiçbir şey saklamaz. Belleğini korumak için web istemcisinin her yeni istekte bu belleği geri göndermesi gerekir. Bu bellek her yeni istekte değiştirilir (ya da değiştirilmez) ve istemciye geri gönderilir (ya da gönderilmez). Flask çerçeveli tarafından kullanılan yöntem budur;

İki yöntem arasındaki farklar şunlardır:

  • 1. yöntem bant genişliği açısından daha az kaynak tüketir. İstemci ile sunucu arasında yalnızca bir tanımlayıcı alışverişi yapılır. Kullanıcının belleği büyüdüğünde, bu durum aynı kalan tanımlayıcı üzerinde hiçbir etki yaratmaz. 2. yöntemde ise durum farklıdır; burada kullanıcının belleği her istekle birlikte aktarılır ve istekler ilerledikçe büyüyebilir;
  • Yöntem 1, daha fazla bellek alanı gerektirir. Zira sunucu, kullanıcının belleğini kendi dosya sistemlerinde depolar. Bir milyon kullanıcı varsa, bu durum bir sorun oluşturabilir. Yöntem 2 ise sunucuda hiçbir şey depolamaz;

Teknik olarak her iki yöntemde de süreç şu şekilde işler:

  • yeni bir istemciye verilen yanıtta, sunucu HTTP, [Set-Cookie : MotClé=Identifiant] veya [Set-Cookie : mémoire] başlığını ekler. Yöntem 1'de, bunu yalnızca ilk istekte yapar. Yöntem 2'de ise, kullanıcının belleği her değiştiğinde bunu yapar;
  • istemlerinde istemci, aldığı şeyi, yani bir tanımlayıcıyı veya bir belleği sistematik olarak geri gönderir. Bunu HTTP veya [Cookie : MotClé=Valeur] başlığı aracılığıyla yapar;

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 bulunan HTTP Cookie başlığının varlığı 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.

Sunucu başka türde bellekleri de tutabilir:

Image

  • [1]'te istek belleği özel bir durumdur. Bu bellek, web istemcisinin isteği tek bir hizmet (veya uygulama) tarafından değil, birden fazla hizmet tarafından işlendiğinde kullanılır. Bilgileri i+1 hizmetine aktarmak için, i hizmeti işlenen isteği (request) bu bilgilerle zenginleştirebilir. Buna istek düzeyinde bellek denir. Bu belgede bu tür bir bellek kullanmayacağız;
  • [2, 4]'te ise az önce tanımladığımız kullanıcı belleği yer alır. Bu bellek yerel olarak [2]'te uygulanabilir veya istemci [4] yardımıyla yönetilebilir;
  • [3]'te, "uygulama" düzeyindeki bellek genellikle salt okunur bir bellektir. Bu bellek tüm kullanıcılar tarafından paylaşılır. Burada genellikle web uygulamasının yapılandırma öğeleri bulunur; bu yapılandırma, uygulamanın tüm kullanıcıları tarafından paylaşılır. Bu tür belleklerle dikkatli olunmalıdır: Belleğe yazma işlemi, kullanıcıların henüz istek göndermediği bir zamanda, genellikle uygulamanın başlatılması sırasında gerçekleştirilmelidir. Daha sonra, istekler gelmeye başladığında bu belleğe yazmak zorlaşır. Web sunucusu aynı anda birden fazla kullanıcıya hizmet verirken, bu kullanıcılardan ikisi ‘uygulama’ düzeyindeki belleğe yazmak isterse, bu belleğin bozulma riski ortaya çıkar. Nitekim, kullanıcı 1 ‘uygulama’ düzeyindeki belleğe yazmaya başlamış olsa bile, yazma işlemi tamamlanmadan kesintiye uğrayabilir. Böylece, eksik bir uygulama belleği ortaya çıkar. Bu bellek paylaşıldığı için, kullanıcı 2 onu okuyabilir ve hatalı bir durumla karşılaşabilir;

22.6.2. [session_scope_01] komut dosyası

Image

[session_scope_xx] komut dosyaları, kullanıcı belleklerinin yönetimini göstermektedir.

[session_scope_01] komut dosyası şöyledir:


# uygulama yapılandırılıyor
import config
config = config.configure()

# bağımlılıklar
import json
from flask import Flask, make_response, session
from flask_api import status

# Flask uygulaması
app = Flask(__name__)

# oturumun gizli anahtarı
app.secret_key = config["SECRET_KEY"]


@app.route('/set-session', methods=['GET'])
def set_session():
    # oturuma bir şey ekleniyor
    session['nom'] = 'séléné'
    # boş bir yanıt gönderiliyor
    response = make_response()
    response.headers['Content-Length'] = 0
    return response, status.HTTP_200_OK


@app.route('/get-session', methods=['GET'])
def get_session():
    # oturumu alıyoruz ve yanıtı gönderiyoruz
    response = make_response(json.dumps({"nom": session['nom']}, ensure_ascii=False))
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# sadece ana program
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 11. satır: Bir Flask uygulaması başlatılır;
  • 14. satır: Bu uygulamanın [secret_key] özniteliğine, 1-3. satırlarda kullanılan yapılandırma dosyasından alınan bir değer atanır. Bir Flask oturumu ancak bu öznitelik başlatıldığında mümkündür. İçine herhangi bir şey yazılabilir. Bu öznitelik, istemciye gönderilecek "kullanıcı belleği"nin bir kısmını şifrelemek için kullanılır. Genellikle tahmin edilmesi zor bir değer girilir. [config] dosyasında gizli anahtar şu şekilde tanımlanmıştır:

    # yapılandırmayı geri veriyoruz
    config = {
        # Flask yapılandırması
        "SECRET_KEY": "vibnFfrdWYUp?*LQ"
    }
  • İlk kez, URL /
    • 17. satır: URL [/set-session], kullanıcının oturumunu başlatmak için kullanılır;
    • 27. satır: URL [/get-session], kullanıcının belleğini (veya oturumunu) geri getirmek için kullanılır;
  • 20. satır: Kullanıcının belleğine (= oturumuna) bir şey eklenir; burada bir isim eklenmiştir. Oturum, bir sözlük gibi yönetilir. Oturuma herhangi bir şey eklenemez. Oturuma eklenen değerlerin jSON biçimine dönüştürülebilmesi gerekir. Python’un önceden tanımlanmış türleri için bu, geliştiricinin müdahalesi olmadan gerçekleşir. Python’un tanımadığı özel nesneler için ise jSON dönüşümünü kendiniz yapmanız gerekir;
  • 22. satır: İçeriği olmayan bir HTTP yanıtı oluşturulur (make_response’e parametre verilmez);
  • 23. satır: İstemciye boş bir belge (0 bayt boyutunda) alacağı bildirilir;
  • 24. satır: HTTP yanıtı müşteriye gönderilir. Dolayısıyla URL ve [/set-session], bir kullanıcı oturumunu başlatmaktan başka bir işlev görmez;
  • 27. satır: URL ve [/get-session], kullanıcının oturumunda neler olduğunu bilmesini sağlar;
  • 30. satır: Kullanıcının oturumundaki jSON dizesini içeren bir HTTP yanıtı oluşturulur. Burada, Flask'ın bu dizeyi oluşturmasına izin vermek yerine jSON dizesini kendimiz oluşturduk. Zira, aksanlı karakterlerin kaçırılmasını istemiyoruz (ensure_ascii=False);
  • 31. satır: İstemciye jSON gönderildiğini bildiriyoruz;
  • 32. satır: HTTP yanıtını istemciye gönderiyoruz;

Bu komut dosyasının amacı, kullanıcı oturumunun, kullanıcının ardışık istekleri arasında bağlantı kurmayı sağladığını göstermektir:

  • 1. istek URL [/set-session]'i isteyecektir;
  • 2. istek, URL ve [/get-session]'i talep eder ve 1. isteğin başlattığı adı alır;

[flask/05] klasöründeki komut dosyalarını yapılandıran [config] komut dosyası şöyledir:


def configure():
    # yapılandırmadaki göreceli yolların referans aldığı mutlak yol
    root_dir = "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020"

    # uygulamanın bağımlılıkları
    absolute_dependencies = [
        # Kişi, Yardımcı Programlar, MyException
        f"{root_dir}/classes/02/entities",
    ]
    # syspath ayarlanıyor
    from myutils import set_syspath
    set_syspath(absolute_dependencies)

    # yapılandırmayı geri yükleme
    config = {
        # Flask yapılandırması
        "SECRET_KEY": "vibnFfrdWYUp?*LQ"
    }

    return config

[session_scope_01] komut dosyasını çalıştırıyoruz, ardından Postman ile URL ve [/set-session]'e istek göndereceğiz. Öncesinde, yapılacak isteğin bazı unsurlarını kontrol edeceğiz:

Image

  • [1]'te Postman'ın çerezlerine erişin; Image
  • [2-4] ile Postman'ın bilinen çerezlerini kontrol edip hepsini silelim: [4-5];

Şimdi oluşturulacak HTTP isteğini kontrol edelim:

Image

  • [9]'te: Postman'ın, bu istek için yaptığımız yapılandırmaya göre istek içine ekleyeceği başlıkların bir kısmı HTTP'te yer alır. Bu kontrol, herhangi bir parametreyi atlamadığınızı veya tam tersine gereksiz parametreleri bırakmadığınızı doğrulamanızı sağlar;

Bu işlem tamamlandıktan sonra isteği çalıştırabiliriz:

Image

Sonucu kontrol etmenin çeşitli yolları vardır. Öncelikle ana pencereye bakabiliriz:

Image

  • [1-2]'te, web hizmetine yapılan istek;
  • [3-6]'te, yanıtın başlıkları (HTTP);
  • [4]'te, kodda yanıt türü belirtilmediğinden Flask varsayılan olarak [text/html] türünü kullanmıştır;
  • [5]'te, istemci yanıtta belge olmadığını bilir;
  • 6. satır: [Set-Cookie] başlığı Flask sunucusu tarafından gönderilmiştir. Bu değer, oturum çerezi olarak adlandırılır. Üç unsurdan oluşur:
    • [session=valeur]: Bu değer, kullanıcının verilerini şifrelenmiş bir biçimde temsil eder. Bu veriler deşifre edilebilir (bkz. |https://blog.miguelgrinberg.com/post/how-secure-is-the-flask-user-session|). Ancak sunucu tarafından kullanılan gizli anahtar nedeniyle, kullanıcı aldığı veriyi değiştirip sunucuya geri gönderemez. Sunucu bir oturum aldığında, bozulmamış bir oturum aldığı garanti edilir;
    • [HttpOnly]: Bu öğenin varlığı, onu alan tarayıcıya, görüntülediği sayfada bulunabilecek JavaScript'in çerezlere erişmemesi gerektiğini belirtir;
    • [Path=/], oturum çerezinin geri gönderilmesi gereken yoldur; dolayısıyla burada web uygulamasının herhangi bir yolu kastedilmektedir. Kullanıcı klavye aracılığıyla bu etki alanından bir URL'i açıkça (URL yazarak) veya dolaylı olarak (bir bağlantıya tıklayarak) her talep ettiğinde, tarayıcı aldığı oturum çerezini otomatik olarak geri gönderecektir;

Ana pencerenin dezavantajı, bu yanıtı tetikleyen tam isteğe erişemememizdir. Bu pencerede gösterilenler kafa karıştırıcıdır:

Image

  • HTTP ve [3-4] başlıklarında, [5] olarak bir oturum çerezi gösterilmektedir. Bu durumda Postman’ın isteğe bir oturum çerezi eklediği düşünülebilir, ancak durum böyle değildir. [3] başlıkları, aslında şu anda yapılandırıldığı şekliyle bir sonraki istek sırasında gönderilecek olan HTTP başlıklarını temsil eder. Postman az önce bir oturum çerezi aldı ve bunu bir sonraki istek sırasında geri gönderecek. Bu nedenle [5] değerini görüyoruz;

Ctrl-Alt-C tuşlarıyla açılan Postman konsolunda istemci/sunucu diyaloğuna erişebiliriz:


GET /set-session HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 3673b73f-7600-4df4-8c4b-c37973e50df8
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Vary: Cookie
Set-Cookie: session=eyJub20iOiJzXHUwMGU5bFx1MDBlOW5cdTAwZTkifQ.Xw6jGQ.y5Icu70wTIN-B0o_hwx0xDH247I; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:32:57 GMT
  • 14. satır: sunucu tarafından gönderilen oturum çerezi;

Şimdi URL ve [/get-session]'i isteyelim:

GET /get-session HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: ce991398-2d9a-46d0-9ccd-c7ff3c7f4d6d
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=eyJub20iOiJzXHUwMGU5bFx1MDBlOW5cdTAwZTkifQ.Xw6jGQ.y5Icu70wTIN-B0o_hwx0xDH247I

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 20
Vary: Cookie
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:36:52 GMT

{"nom": "séléné"}
  • 9. satır: Postman istemcisi, aldığı oturum çerezini sunucuya geri gönderdi;
  • 18. satır: sunucu tarafından gönderilen jSON dizesi;

Bu örnek bize çeşitli noktaları göstermektedir:

  • Postman istemcisi, Flask sunucusundan aldığı oturum çerezini geri gönderir. Web tarayıcıları her zaman bu şekilde çalışır;
  • 2 numaralı istek olan [/get-session]'in, 1 numaralı istek olan [/set-session] sırasında oluşturulan bir bilgiyi geri almayı sağladığını görüyoruz. Dolayısıyla burada bir kullanıcı belleği söz konusudur;
  • 11-16. satırlar: Flask sunucusu oturum çerezi geri göndermedi. Bu her zaman böyle olmaz. Flask sunucusu, yalnızca son istek kullanıcı belleğini değiştirmişse oturum çerezini geri gönderir;

22.6.3. [session_scope_02] komut dosyası

Image

[session_02] komut dosyası şöyledir:


# bağımlılıklar
import os

from flask import Flask, make_response, session
from flask_api import status

# Flask uygulaması
app = Flask(__name__)

# oturumun gizli anahtarı
app.secret_key = os.urandom(12).hex()


# Ana Sayfa URL
@app.route('/', methods=['GET'])
def index():
    # üç sayaç yönetiliyor
    if session.get('n1') is None:
        session['n1'] = 0
    else:
        session['n1'] = session['n1'] + 1
    if session.get('n2') is None:
        session['n2'] = 10
    else:
        session['n2'] = session['n2'] + 1
    if session.get('n3') is None:
        session['n3'] = 100
    else:
        session['n3'] = session['n3'] + 1
    # sayaç sözlüğü
    compteurs = {"n1": session['n1'], "n2": session['n2'], "n3": session['n3']}
    # yanıt gönderiliyor
    response = make_response(compteurs)
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# ana sayfa
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 11. satır: Burada gizli anahtar bir işlev yardımıyla oluşturulur. Bu işlevin avantajı, rastgele karmaşık bir karakter dizisi oluşturmasıdır. [app] değişkeninin, 8. satırda oluşturulan Flask sınıfının örneği olduğunu hatırlatırız;
  • 15. satır: Bu sefer tek bir rota olacak, o da / rotası;
  • 17-29. satırlar: Üç sayacı içeren bir oturum yönetilir: [n1, n2, n3]. Kullanıcının ilk çağrısında [n1, n2, n3]=[0, 10, 100] olur ve sonraki her çağrıda bu sayaclar 1 artırılır;
  • satır 18: ilk istekte, uygulamanın oturumu boştur. [session.get(‘clé’)] ifadesi, [None] değerini döndürür. Sonraki isteklerde bu ifade, anahtara atanan değeri döndürecektir;
  • 31. satır: Bu sayaçlar bir sözlüğe eklenir;
  • satır 33: Bu sözlük, HTTP yanıt belgesidir. Hatırlatmak gerekirse, Flask sözlükleri otomatik olarak jSON dizesine dönüştürür;
  • satır 34: web istemcisine jSON alacağı bildirilir;
  • 35. satır: HTTP yanıtı istemciye gönderilir;

Bu komut dosyasını çalıştıralım ve Postman istemcisindeki tüm çerezleri sildikten sonra, Postman ile oluşturulan web uygulamasını sorgulayalım: [1-3]:

Image

Postman konsolunda, istemci/sunucu arasındaki iletişim şu şekildedir:


GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: c7db536d-9352-4aa6-9877-04560e03d935
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 41
Vary: Cookie
Set-Cookie: session=eyJuMSI6MCwibjIiOjEwLCJuMyI6MTAwfQ.Xw6nLg.v49CeDWwqP-6Dp9Qt330GAe-dNA; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:50:22 GMT

{
"n1": 0, 
"n2": 10, 
"n3": 100
}
  • [14], sunucu tarafından gönderilen oturum çerezi;
  • [18-22]'e, jSON dizesi biçiminde sunucudan gelen yanıt;

Aynı isteği ikinci kez tekrar edelim. Günlükler şu şekilde değişir:


GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 8205ad85-37b3-41f2-a171-70dd3b3a1679
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=eyJuMSI6MCwibjIiOjEwLCJuMyI6MTAwfQ.Xw6nLg.v49CeDWwqP-6Dp9Qt330GAe-dNA

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 41
Vary: Cookie
Set-Cookie: session=eyJuMSI6MSwibjIiOjExLCJuMyI6MTAxfQ.Xw6nsw.OuxIQnGhmhSsan5Qu_FL3Iyu-9k; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 06:52:35 GMT

{
"n1": 1, 
"n2": 11, 
"n3": 101
}
  • 9. satır: Postman istemcisi, aldığı oturum çerezini geri gönderir;
  • 15. satır: Sunucu, yanıtında yeni bir oturum çerezi gönderir; bunun nedeni, istemcinin isteğinin kullanıcı belleğini (= oturumu) değiştirmiş olmasıdır;
  • satır 19-23: sayaçların yeni değerleri;

22.6.4. [session_scope_03] komut dosyası

Bu yeni komut dosyası, bir oturuma farklı Python türlerini (liste, sözlük, nesne) ekleyebileceğimizi göstermeyi amaçlamaktadır. Tek kısıtlama, oturuma eklenen nesnelerin jSON'te serileştirilebilir olmasıdır. Varsayılan olarak serileştirilemiyorsa (listeler, sözlükler), jSON'te dönüştürmeyi kendiniz yapmanız gerekir.


# uygulama yapılandırılıyor
import config
config = config.configure()

# bağımlılıklar
import json
import os

from flask import Flask, make_response, session
from flask_api import status
from Personne import Personne

# Flask uygulaması
app = Flask(__name__)

# oturum gizli anahtarı
app.secret_key = os.urandom(12).hex()


# Ana Sayfa URL
@app.route('/', methods=['GET'])
def index():
    # liste yönetimi
    liste = session.get('liste')
    if liste is None:
        # 1. istek
        liste = [0, 10, 100]
    else:
        # sonraki istekler
        for i in range(len(liste)):
            liste[i] += 1
    # liste oturuma geri eklenir
    session['liste'] = liste

    # sözlük yönetimi
    dico = session.get('dico')
    if not dico:
        # 1. istek
        dico = {"un": 0, "deux": 10, "trois": 100}
    else:
        # sonraki istekler
        dico = session['dico']
        for key in dico.keys():
            dico[key] += 1
    # sözlüğü oturuma geri yükleniyor
    session['dico'] = dico

    # bir kişinin yönetimi
    personne_json = session.get('personne')
    if personne_json is None:
        # 1. istek
        personne = Personne().fromdict({"prénom": "aglaë", "nom": "séléné", "âge": 70})
    else:
        # sonraki istekler
        personne = Personne().fromjson(personne_json)
        personne.âge += 1
    # kişiyi oturuma geri ekleme
    session['personne'] = personne.asjson()

    # sonuç sözlüğü
    résultats = {"liste": liste, "dict": dico, "personne": personne.asdict()}

    # bir yanıt gönderilir jSON
    response = make_response(json.dumps(résultats, ensure_ascii=False))
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# ana sayfa
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 1-3. satırlar: web uygulaması yapılandırılır;
  • satır 5-11: bağımlılıklar içe aktarılır;
  • 14. satır: Flask uygulaması başlatılır;
  • 17. satır: [secret_key] özniteliği başlatılır. Bu, oturumların kullanılmasını sağlar;
  • 21. satır: Uygulamanın tek rotası;
  • 23-33. satırlar: oturumda bir listenin yönetimi. Bu listeye, varsayılan olarak jSON ile serileştirilebilir öğeler eklenmiştir;
  • satır 35-46: oturumda bir sözlüğün yönetimi. Buna, varsayılan olarak jSON'te serileştirilebilir öğeler eklenmiştir;
  • satır 48-58: bir kişinin yönetimi. [Personne] nesnesi, varsayılan olarak jSON'e serileştirilemez. Bu nedenle gerekli önlemlerin alınması gerekir;
  • 58. satır: Kişinin jSON dizesini oturuma kaydetmek için [BaseEntity.asjson] yöntemi kullanılır. [personne.asdict]'i de kullanabilirdik, çünkü [personne.asdict], varsayılan olarak jSON'te serileştirilebilir değerler içeren bir sözlüktür;
  • 55. satır: Oturuma jSON dizesini kaydettiğimiz için, [BaseEntity.fromjson] yöntemini kullanarak oturumdan kişiyi geri alıyoruz;
  • 61. satır: İstemciye yanıt olarak gönderilecek olan [résultats] sözlüğü oluşturulur. Bu durumda Flask’ın sözlükten jSON dizesini göndereceğini biliyoruz. Dolayısıyla sözlük, varsayılan olarak jSON ile serileştirilebilen değerler içermelidir;
  • 64. satır: [résultats] sözlüğündeki jSON dizesini HTTP yanıtına açıkça ekliyoruz. Flask bunu varsayılan olarak yapardı. Ancak, yine varsayılan olarak [ensure_ascii=True] parametresini kullanıyor ve bu bizim için uygun değildi;
  • 65. satır: İstemciye jSON alacağı bildirilir;
  • 66. satır: yanıt gönderilir;

Web uygulamasını başlatıyoruz. Postman istemcisindeki tüm çerezleri siliyoruz. Ardından istemci, URL [http://localhost:5000]'i istiyor. Postman konsolundaki istemci/sunucu diyaloğu şu şekildedir:


GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 5f8b7c63-aa8a-4429-a2fa-62141423d933
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 135
Vary: Cookie
Set-Cookie: session=.eJw9isEKwyAQRH-lzHkPm15K91dqD2mzBMFq0AgF8d-jsRQG9u3MK1jsO0AKFs1fyMSEPQabOjbOHsKV4GzaFfJgmnr4Sdg0puB9a1EMtmgys959-BjIxWBe3XxWLwNq_39IQ3Q_f5zhnHxdtYs3rqgH4gQvMg.Xw6yGw.Bwpt3q-sH03gFLmg2FIPXV_ZNt8; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 07:36:59 GMT

{"liste": [0, 10, 100], "dict": {"un": 0, "deux": 10, "trois": 100}, "personne": {"prénom": "aglaë", "nom": "séléné", "âge": 70}}

İsteği ikinci kez gönderiyoruz:


GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 40fd00ea-d45c-46b7-a51e-d4d433a37b5c
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session=.eJw9isEKwyAQRH-lzHkPm15K91dqD2mzBMFq0AgF8d-jsRQG9u3MK1jsO0AKFs1fyMSEPQabOjbOHsKV4GzaFfJgmnr4Sdg0puB9a1EMtmgys959-BjIxWBe3XxWLwNq_39IQ3Q_f5zhnHxdtYs3rqgH4gQvMg.Xw6yGw.Bwpt3q-sH03gFLmg2FIPXV_ZNt8

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 135
Vary: Cookie
Set-Cookie: session=.eJw9isEKwyAQRH-lzHkP2kupv9LtIW2WIBgNGqEg_nu3seQ0b2Zew-zfCa5hlvqBs5aw5-SLolGuUaETgi-7wD0sqaHPk7BJLilGXdEYW-ZqjNxjWhnuwpiWMB3Ti0Haz6MMMfz9EcM5-LrIT7zZjv4F5NYvOQ.Xw6ydQ.PMWRCqKx9HNnb_DyK-ha-9pCF7M; HttpOnly; Path=/
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 07:38:29 GMT

{"liste": [1, 11, 101], "dict": {"deux": 11, "trois": 101, "un": 1}, "personne": {"prénom": "aglaë", "nom": "séléné", "âge": 71}}
  • 9. satır: İstemci, aldığı oturum çerezini geri gönderir;
  • 15. satır: oturum içeriği değiştiği için (19. satır) sunucu, istemciye başka bir çerez gönderir. Bu içeriğin, oturum çerezinde şifrelenmiş olarak bulunduğunu hatırlatırız;

22.7. [flask/06] komut dosyaları: tüm kullanıcılar tarafından paylaşılan bilgiler

22.7.1. Giriş

Bu bölüm, uygulama kapsamındaki, yani tüm kullanıcılar tarafından paylaşılan bilgilerin nasıl yönetileceğini göstermeyi amaçlamaktadır. Bu bilgiler genellikle uygulama yapılandırma bilgileridir. Bir web uygulamasının farklı bellek türlerini tutabileceğini görmüştük:

Image

Burada [3] uygulamasının belleği ile ilgileniyoruz.

22.7.2. [application_scope_01] betiği

Image

[application_scope_01] komut dosyası, ‘uygulama’ kapsamındaki verileri yönetmenin bir yolunu göstermektedir:


# uygulama yapılandırılıyor
import config
config = config.configure()

# bağımlılıklar
from flask import Flask, make_response
from flask_api import status

# Flask uygulaması
app = Flask(__name__)


# Ana Sayfa URL
@app.route('/', methods=['GET'])
def index():
    # uygulamanın farklı istemcilerin istekleri arasında bellekte kaldığını göstermeyi amaçlıyoruz
    # her istemci aynı uygulamayla etkileşim halindedir

    # app_infos, oturum düzeyinde değil, uygulama düzeyinde bilgileri temsil eder
    # yani, bu bilgi belirli bir kullanıcıya değil, tüm kullanıcılara aittir
    # bu bilgi burada [config] içinde depolanır (zorunlu değildir)

    # sonuç sözlüğü
    résultats = {"config": config}

    # yanıt gönderilir
    response = make_response(résultats)
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# ana iş parçacığı
if __name__ == '__main__':
    # bu kodun birden fazla kez çalıştırılıp çalıştırılmadığını kontrol ediyoruz
    print("application app lancée")
    # web uygulamasını başlatıyoruz
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 1-3. satırlar: Yapılandırma sözlüğü alınır. Yönlendirme işlevlerinin dışında yer alan kodun yalnızca bir kez yürütüldüğünü göstereceğiz. Flask uygulaması bellekte kalır. Yönlendirmelerin dışında başlatılan tüm bilgiler, yönlendirmeler için küreseldir ve dolayısıyla yönlendirmeler tarafından bilinir. Böylece, 3. satırdaki [config] sözlüğü, / rotası (24. satır) tarafından döndürülecektir. Tüm web istemcilerinin aynı sözlüğü alacağını ve dolayısıyla bu sözlüğün tüm istemciler tarafından paylaşıldığını göstereceğiz. Dolayısıyla bu, ‘uygulama’ kapsamındaki bir bilgidir;
  • 35. satır: Yönlendirme işlevi dışındaki satırların (1-10, 32-38) kodunun birden fazla kez yürütülüp yürütülmediğini görmek için bir günlük kaydı ekliyoruz;

[config] yapılandırması şu şekildedir:


def configure():
    # yapılandırma sağlanıyor
    config = {
        # Flask yapılandırması
        "SECRET_KEY""vibnFfrdWYUp?*LQ"
    }

    return config

Bu uygulamayı çalıştırıyoruz. PyCharm konsolundaki günlük kayıtları şu şekildedir:

Image

  • [1]'te, uygulamanın ilk başlatılması;
  • [2]'te, [Debug] modu talep edildiği için uygulama [Debug] modunda yeniden başlatılır;

Şimdi bir tarayıcıyla (aşağıda Chrome), URL [http://127.0.0.1:5000/] modunu talep ediyoruz:

Image

Şimdi bir Firefox tarayıcısıyla:

Image

Şimdi Postman istemcisiyle:

GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 51e75099-8ecb-4f27-ae3b-9386e982ede4
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 39
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 15 Jul 2020 10:34:26 GMT

{
"SECRET_KEY": "vibnFfrdWYUp?*LQ"
}

Şimdi Pycharm'ın konsoluna geri dönelim: [Run]:

Image

  • İki günlük kaydı [1, 2] hala orada, ancak web sunucusu tarafından alınan üç isteği görmemize rağmen başka bir günlük kaydı yok;

Uygulamanın her yeni istekle yeniden yüklenmediğinden tamamen emin olmak için, yapılandırmaya bir sayaç ekleyip her yeni istekle bu sayacı artırabiliriz. Böylece, her istemcinin sayacı bir önceki istemcinin bıraktığı durumda gördüğünü göreceğiz. Ancak, istemcilerin uygulama kapsamındaki verileri değiştirmemeleri gerektiğini hatırlatmak isteriz; çünkü bu veriler tüm istemciler arasında paylaşılır ve sunucunun aynı anda birden fazla istemciye hizmet verdiği, bir istemcinin isteğinin kesintiye uğramadan tamamen yürütüleceğinin garanti edilemediği bir bağlamda, sonuna kadar tamamlanamadan kesintiye uğrayan bir istek 1 gönderen istemci 1, paylaşılan verileri sonraki istemciler için bozuk bir durumda bırakabilir.

22.7.3. script [application_scope_02]

Image

[application_scope_02] komut dosyası, yapılmaması gereken bir şeyi gerçekleştirecek: müşterilerin diğer kullanıcılarla paylaşılan bilgileri değiştirmesine izin verecek. Kullanıcılar arasında bir sayaç paylaşacağız ve kullanıcılar bu sayacı artıracak. Her kullanıcının, diğer kullanıcıların sayaca yaptığı değişiklikleri görebildiğini göreceğiz.

Komut dosyası şu şekildedir:


# bağımlılıklar

from flask import Flask, make_response
from flask_api import status

# Flask uygulaması
app = Flask(__name__)

# uygulama kapsamındaki veriler
config = {
    "counter": 0
}


# Ana Sayfa URL
@app.route('/', methods=['GET'])
def index():
    # [config] sözlüğünün tüm istemciler arasında paylaşıldığını göstermeyi amaçlıyoruz
    # web uygulamasının tüm müşterileri arasında paylaşıldığını göstermeyi amaçlıyoruz

    # sayaç artırılıyor
    config["counter"] += 1
    # yanıt gönderiliyor
    response = make_response(config)
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# ana iş parçacığı
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 10-12. satırlar: kullanıcılar arasında paylaşılan [config] sözlüğü. İçinde bir sayaç bulunur;
  • 22. satır: Bir kullanıcı her URL / isteğinde bulunduğunda, yapılandırmadaki sayaç artırılır;
  • satır 23-26: sözlükteki jSON dizesi her istemciye gönderilir;

Bu komut dosyasını çalıştırıyoruz. Ardından ilk tarayıcıyla URL [http://127.0.0.1:5000/] adresini çağırıyoruz:

Image

Ardından ikinci bir tarayıcıyla da aynı işlemi gerçekleştiriyoruz:

Image

Ardından Postman ile üçüncü kez aynı işlem yapılır:

Image

Görüldüğü gibi, her istemci sayacı bir önceki istemcinin bıraktığı durumda alıyor. Dolayısıyla hepsi aynı bilgiye erişebiliyor.

22.7.4. [application_scope_03] komut dosyası

[application_scope_03] komut dosyası, kullanıcılar arasında paylaşılan bilginin neden salt okunur olması gerektiğini göstermektedir.

Image

Komut dosyası şöyledir:


# bağımlılıklar
import threading
from time import sleep

from flask import Flask, make_response
from flask_api import status

# Flask uygulaması
app = Flask(__name__)

# uygulama kapsamı verileri
config = {
    "counter": 0
}


# Ana Sayfa URL
@app.route('/', methods=['GET'])
def index():
    # [config] sözlüğünün tüm istemciler arasında paylaşıldığını göstermeyi amaçlıyoruz
    # web uygulamasının tüm müşterileri arasında paylaşıldığını ve söz konusu sözlüğün salt okunur olması gerektiğini göstermeyi amaçlamaktadır

    # iş parçacığının adı
    thread_name = threading.current_thread().name
    # sayaç okunur
    counter = config["counter"]
    print(f"compteur lu : {counter}, par le thread {thread_name}")
    # 5 saniye durulur - böylece diğer istemcilere hizmet verilir
    sleep(5)
    # yapılandırma sayacını artırıyoruz
    config["counter"] = counter + 1
    # günlük
    print(f"compteur écrit : {config['counter']}, par le thread {thread_name}")
    # yanıt gönderiliyor
    response = make_response(config)
    response.headers['Content-Type'] = 'application/json; charset=utf-8'
    return response, status.HTTP_200_OK


# ana
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run(threaded=True)
  • 43. satır: Web uygulamasının çalışma modu değiştirilmiştir. Uygulamanın kullanıcıları eşzamanlı olarak hizmet vermesi gerektiğini belirtmek için [threaded=True] yazılmıştır. Bu, yürütme iş parçacıkları aracılığıyla gerçekleştirilir:
    • aynı anda birden fazla yürütme iş parçacığı olabilir ve her biri bir kullanıcıya hizmet eder;
    • makinenin işlemcisi bu iş parçacıkları arasında paylaşılır;
    • bir iş parçacığı, işini tamamlamadan önce kesilebilir. Daha sonra devam ettirilir;
  • 19. satır: [index] işlevi, birden fazla iş parçacığı tarafından eşzamanlı olarak yürütülebilir;
  • 24. satır: [index] işlevini çalıştıran iş parçacığının adı alınır;
  • 26. satır: Sayaç değeri okunur. Açıklamamızı kolaylaştırmak amacıyla, sayacın artırılmasını şu şekilde ayrıntılandırıyoruz:
    • 1. adım: İş parçacığı 1 tarafından sayacın okunması (örneğin 1);
    • 2. adım: İş parçacığı 1, 5 saniye boyunca duraklatılır (29. satır). İş parçacığı 1 bir duraklama talebinde bulunduğundan, işlemci başka bir iş parçacığına, yani iş parçacığı 2’ye verilir. Amaç, bu yeni iş parçacığının sayacın aynı değerini (=1) okumasını sağlamaktır. Ardından o da 5 saniye duraklama yapar ve işlemciyi kaybeder;
    • 3. adım: 1. adımda okunan değerden (=1) başlayarak sayacın artırılması, 31. satır. Bunu ilk yapan 1. iş parçacığıdır: sayacı 2’ye getirir ve ardından [index] işlevinin yürütülmesini tamamlar. Ardından sıra iş parçacığı 2’ye gelir; o da 1. adımda okunan değerden (=1) yola çıkarak sayacı 2’ye getirir. Sonuç olarak, her iki iş parçacığının da çalışmasından sonra sayaç 2’de kalır, oysa 3 olması gerekirdi;
  • 33. satır: Doğrulama amacıyla sayacın değeri görüntülenir;

Komut dosyasını çalıştırdıktan sonra, iki farklı tarayıcıyla ve ardından Postman ile [http://loaclhost :5000/] URL'sini çağırıyoruz. PyCharm konsolundaki günlükler şu şekildedir:


C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/flask/06/application_scope_03.py
 * Serving Flask app "application_scope_03" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 334-263-283
* Running on http://127.0.0.1:5000/ (Çıkmak için CTRL+C tuşlarına basın)
compteur lu : 0, par le thread Thread-2
compteur lu : 0, par le thread Thread-4
compteur écrit : 1, par le thread Thread-2
127.0.0.1 - - [16/Jul/2020 08:55:37] "GET / HTTP/1.1" 200 -
compteur écrit : 1, par le thread Thread-4
127.0.0.1 - - [16/Jul/2020 08:55:40] "GET / HTTP/1.1" 200 -
compteur lu : 1, par le thread Thread-5
compteur écrit : 2, par le thread Thread-5
127.0.0.1 - - [16/Jul/2020 08:55:46] "GET / HTTP/1.1" 200 -
  • 9-10. satırlar: İlk iki iş parçacığı (2 ve 4), sayaçtan aynı 0 değerini okur;
  • 11. satır: 2 numaralı iş parçacığı sayacı 1'e ayarlar;
  • 13. satır: 4 numaralı iş parçacığı sayacı 1'e ayarlar. Bu andan itibaren sayacın değeri hatalıdır;
  • 15-16. satırlar: 5 numaralı iş parçacığı kesintiye uğramaz ve sayaç değerini doğru şekilde işler;

Bu örnekten çıkarılacak ders, bir web uygulamasının kodunun kullanıcılar tarafından paylaşılan bilgilerin değerini değiştirmemesi gerektiğidir.

22.8. [flask/07] komut dosyaları: rota yönetimi

Image

Burada, bir uygulamanın rotalarının yönetimi, yani web uygulaması tarafından sunulan URL'lerle ilgileniyoruz.

22.8.1. [main_01] komut dosyası: parametreli rotalar

[main_01] komut dosyası, rotaları yapılandırma olanağını sunar:


from flask import Flask, make_response
from flask_api import status

# Flask uygulaması
app = Flask(__name__)


# yanıtın gönderilmesi
def send_plain_response(réponse: str):
    # yanıt gönderiliyor
    response = make_response(réponse)
    response.headers['Content-Type'] = 'text/plain; charset=utf-8'
    return response, status.HTTP_200_OK


# /soyadı/adı
@app.route('/<string:nom>/<string:prenom>', methods=['GET'])
def index(nom, prenom):
    # yanıt
    return send_plain_response(f"{prenom} {nom}")


# oturum başlatma
@app.route('/init-session/<string:type>', methods=['GET'])
def init_session(type: str):
    # yanıt
    return send_plain_response(f"/init-session/{type}")


# kullanıcı-doğrulama
@app.route('/authentifier-utilisateur', methods=['POST'])
def authentifier_utilisateur():
    # yanıt
    return send_plain_response("/authentifier-utilisateur")


# vergi-hesapla
@app.route('/calculer-impot', methods=['POST'])
def calculer_impot():
    # yanıt
    return send_plain_response("/calculer-impot")


# simülasyonları listele
@app.route('/lister-simulations', methods=['GET'])
def lister_simulations():
    # yanıt
    return send_plain_response("/lister-simulations")


# simülasyonu-sil
@app.route('/supprimer-simulation/<int:numero>', methods=['GET'])
def supprimer_simulation(numero: int):
    # yanıt
    return send_plain_response(f"/supprimer-simulation/{numero}")


# oturumu-sonlandır
@app.route('/fin-session', methods=['GET'])
def fin_session():
    # yanıt
    return send_plain_response(f"/fin-session")


# ana
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 17. satır: URL parametrelerinin türünü belirtir. Bu, Flask’ın doğrulamalar yapmasını sağlar. Parametre beklenen türde değilse, istemcinin isteği reddedilir (400 Bad Request hatası). Dolayısıyla Flask, bizim yapmamız gereken işin bir kısmını üstlenir;
  • 18. satır: Parametreler için, 17. satırdaki parametrelerin tam adlarını kullanmalıyız, ancak sıralamaları aynı olmak zorunda değildir;
  • 20. satır: Web istemcisine yanıtı göndermek için [send_plain_response] işlevini kullanıyoruz;
  • 9. satır: [send_plain_response] işlevi, müşteriye gönderilecek karakter dizisini alır;
  • satır 11: HTTP yanıtının gövdesi oluşturulur;
  • 12. satır: İstemciye düz metin gönderileceği bildirilir;
  • 13. satır: HTTP yanıtı gönderilir;
  • 23-62. satırlar: daha sonra bir uygulama alıştırmasında kullanılacak, parametreleri ayarlanmış diğer rotalar;

Komut dosyasını çalıştırıyoruz ve Postman istemcisiyle sorguluyoruz:

Image

22.8.2. [main_02] komut dosyası: yolların dışa aktarılması

Önceki [main_01] komut dosyasında, çok sayıda yol varsa kod uzun olabilir. [main_02] komut dosyası, yolların nasıl dışa aktarılacağını gösterir.

Image

[routes_02] komut dosyası, önceki komut dosyasındaki yollarla ilgili işlevleri bir araya getirir:


from flask import make_response
from flask_api import status


def send_response(réponse: str):
    # yanıt gönderiliyor
    response = make_response(réponse)
    response.headers['Content-Type'] = 'text/plain; charset=utf-8'
    return response, status.HTTP_200_OK


# Ana Sayfa URL
def index(nom, prenom):
    # yanıt
    return send_response(f"{prenom} {nom}")


# oturum başlatma
def init_session(type: str):
    # yanıt
    return send_response(f"/init-session/{type}")


# kullanıcı-doğrulama
def authentifier_utilisateur():
    # yanıt
    return send_response("/authentifier-utilisateur")


# vergi hesapla
def calculer_impot():
    # yanıt
    return send_response("/calculer-impot")


# simülasyonları listele
def lister_simulations():
    # yanıt
    return send_response("/lister-simulations")


# simülasyonu sil
def supprimer_simulation(numero: int):
    # yanıt
    return send_response(f"/supprimer-simulation/{numero}")


# oturumu-sonlandır
def fin_session():
    # yanıt
    return send_response(f"/fin-session")

[routes_02] komut dosyasının bir rota komut dosyası olmadığına dikkat edilmelidir. Bu, bir işlev listesidir. Rotalar ile işlevler arasındaki bağlantıyı kuran, ana komut dosyası olan [main_02]'tir:


from flask import Flask

# yol işlevlerini kendi komut dosyalarına taşıyoruz
import routes_02

# Flask uygulaması
app = Flask(__name__)

# yollar ve işlevler arasındaki ilişkilendirmeler
app.add_url_rule('/<string:nom>/<string:prenom>', methods=['GET'], view_func=routes_02.index)
app.add_url_rule('/init-session/<string:type>', methods=['GET'], view_func=routes_02.init_session)
app.add_url_rule('/authentifier-utilisateur', methods=['POST'], view_func=routes_02.authentifier_utilisateur)
app.add_url_rule('/calculer-impot', methods=['POST'], view_func=routes_02.calculer_impot)
app.add_url_rule('/lister-simulations', methods=['GET'], view_func=routes_02.lister_simulations)
app.add_url_rule('/supprimer-simulation/<int:numero>', methods=['GET'], view_func=routes_02.supprimer_simulation)
app.add_url_rule('/fin-session', methods=['GET'], view_func=routes_02.fin_session)

# main
if __name__ == '__main__':
    app.config.update(ENV="development", DEBUG=True)
    app.run()
  • 4. satır: rotalarla ilişkili işlevlerin komut dosyası içe aktarılır;
  • 9-16. satırlar: rotalar ve fonksiyonlar arasında ilişkilendirme yapılır;

Bu yöntemle, bir rotaya atanan her işlev, gerekirse ayrı bir komut dosyasına konu edilebilir.

Sonuçlar, önceki [main_01] komut dosyasıyla elde edilenlerle aynıdır.