Skip to content

17. خدمات وب

توجه: اصطلاح «وب سرویس» در اینجا به هر برنامه وبی اطلاق می‌شود که داده‌های خام را به یک کلاینت ارائه می‌دهد – در مثال‌های بعدی، این یک اسکریپت کنسول خواهد بود. ما به هیچ فناوری خاصی، مانند REST (انتقال وضعیت REpresentational) یا SOAP (پروتکل دسترسی ساده به شیء)، که داده‌هایی را به صورت کمتر یا بیشتر خام در قالبی کاملاً مشخص بازمی‌گردانند، نمی‌پردازیم. REST مقدار jSON را برمی‌گرداند، در حالی که SOAP مقدار XML را برمی‌گرداند. هر یک از این فناوری‌ها دقیقاً توصیف می‌کند که کلاینت چگونه باید از سرور پرس‌وجو کند و پاسخ سرور باید چه قالبی داشته باشد. در این دوره، ما در مورد ماهیت درخواست کلاینت و پاسخ سرور انعطاف‌پذیرتر خواهیم بود. با این حال، اسکریپت‌های نوشته شده و ابزارهای مورد استفاده مشابه فناوری REST هستند.

17.1. Introduction

از آنجایی که برنامه‌های PHP می‌توانند توسط یک سرور WEB اجرا شوند، چنین برنامه‌ای به یک برنامه سرور تبدیل می‌شود که قادر به خدمت‌رسانی به چندین کلاینت است. از دیدگاه کلاینت، فراخوانی یک سرویس وب به منزله درخواست URL آن سرویس است. کلاینت می‌تواند به هر زبانی، از جمله PHP نوشته شود. در مورد دوم، ما از توابع شبکه‌ای که به تازگی بررسی کردیم استفاده می‌کنیم. ما همچنین باید بدانیم چگونه با یک سرویس وب «ارتباط» برقرار کنیم، یعنی پروتکل ارتباطی http بین یک سرور WEB و کلاینت‌های آن را درک کنیم. این هدف پاراگراف پیوندی بود.

کلاینت وب توصیف‌شده در بخش «لینک» به ما امکان داد تا بخشی از پروتکل HTTP را کشف کنیم.

Image

در ساده‌ترین شکل، تبادله‌های کلاینت/سرور به شرح زیر است:

  • کلاینت یک اتصال با پورت ۸۰ روی سرور وب برقرار می‌کند؛
  • درخواست یک سند را ارسال می‌کند؛
  • سرور وب سند درخواستی را ارسال می‌کند و اتصال را قطع می‌کند؛
  • سپس کلاینت اتصال را قطع می‌کند؛

سند می‌تواند از انواع مختلف باشد: متن در قالب HTML، یک تصویر، یک ویدیو… ممکن است یک سند موجود (سند ایستا) یا سندی باشد که به‌صورت پویا توسط یک اسکریپت تولید می‌شود (سند پویا). در مورد دوم، به این امر برنامه‌نویسی وب گفته می‌شود. اسکریپت تولید پویا اسناد را می‌توان به زبان‌های مختلفی نوشت: PHP، پایتون، پرل، جاوا، روبی، سی‌شارپ، VB.net

در ادامه، از اسکریپت‌های PHP برای تولید پویا اسناد متنی استفاده خواهیم کرد.

Image

  • در [1]، کلاینت یک اتصال با سرور برقرار می‌کند، یک اسکریپت PHP را درخواست می‌کند و ممکن است پارامترها را برای آن اسکریپت ارسال کند یا نکند؛
  • در [2]، سرور وب اسکریپت PHP را توسط تفسیرگر PHP اجرا می‌کند. این اسکریپت یک سند تولید می‌کند که به کلاینت [3] ارسال می‌شود؛
  • سرور اتصال را قطع می‌کند. کلاینت نیز همین کار را انجام می‌دهد؛

سرور وب می‌تواند همزمان چندین مشتری را مدیریت کند.

با بسته نرم‌افزاری [Laragon]، وب‌سرور یک سرور آپاچی است، یک سرور متن‌باز از بنیاد آپاچی (http://www.apache.org/). در برنامه‌های کاربردی زیر، [Laragon] باید راه‌اندازی شود:

Image

این کار سرور وب آپاچی و همچنین SGBD و MySQL را راه‌اندازی می‌کند.

اسکریپت‌هایی که توسط وب‌سرور اجرا می‌شوند با استفاده از ابزار NetBeans نوشته خواهند شد. تا کنون، ما اسکریپت‌های PHP را نوشته‌ایم که در یک محیط کنسول اجرا می‌شوند:

Image

کاربر از کنسول برای درخواست اجرای اسکریپت PHP و دریافت نتایج استفاده می‌کند.

در برنامه‌های کاربردی کلاینت/سرور زیر:

  • اسکریپت سمت کلاینت در یک محیط کنسول اجرا می‌شود؛
  • اسکریپت سرور در یک زمینه وب اجرا می‌شود؛

Image

اسکریپت سرور PHP نمی‌تواند در هر نقطه‌ای از سیستم فایل قرار گیرد. این به این دلیل است که وب‌سرور برای اسناد ایستا و پویایی که درخواست ارائه آن‌ها را دارد، در مکان‌هایی که توسط پیکربندی مشخص شده‌اند جستجو می‌کند. پیکربندی پیش‌فرض Laragon به این معناست که اسناد در پوشه <Laragon>/www جستجو می‌شوند، جایی که <Laragon> پوشه نصب Laragon است. بنابراین، اگر یک کلاینت وب، سند D را که در مسیر URL [http://localhost/D] قرار دارد درخواست کند، وب سرور سند D را که در مسیر [<Laragon>/www/D] قرار دارد، ارائه خواهد داد.

در مثال‌های زیر، اسکریپت‌های سرور را در پوشه [www/php7/scripts-web] قرار می‌دهیم. اگر یک اسکریپت سرور با نام S.php فراخوانی شود، از طریق URL و [http://localhost/php7/scripts-web/S.php] از وب سرور درخواست خواهد شد. سپس سند [<Laragon>/www/php7/scripts-web/S.php] ارائه خواهد شد.

Image

  • به [1]، فایل [<laragon>/www]؛
  • در [2]، پوشه [php7/scripts-web]؛

برای ایجاد اسکریپت‌های سمت سرور با NetBeans، به شرح زیر عمل می‌کنیم:

Image

  • در [1-2]، یک پروژه جدید ایجاد می‌کنیم
  • در [3-4]، ما دسته‌بندی [PHP] و پروژه [PHP Application] را انتخاب می‌کنیم

Image

  • در [5]، نام پروژه؛
  • در [6]، پوشه پروژه در سیستم فایل. توجه داشته باشید که این در پوشه [<laragon>/www] قرار دارد، جایی که باید باشد؛
  • در [7-8]، مقادیر پیش‌فرض ارائه‌شده را بپذیرید؛
  • در [9-10]، مقادیر پیش‌فرض ارائه‌شده را بپذیرید. در [10]، توجه داشته باشید که اسکریپت‌های URL که در این پروژه قرار می‌دهیم با مسیر [http://localhost/php7/scripts-web/] شروع خواهند شد؛

Image

  • در [11]، فریم‌ورک‌های وب نوشته شده در PHP در دسترس هستند. این فریم‌ورک‌ها به محض اینکه وب‌اپلیکیشن از نظر مقیاس رشد کند، ضروری می‌شوند؛
  • در [12]، می‌توانید با استفاده از ابزار [Composer]، کتابخانه‌های PHP را اضافه کنید. ما از این ابزار دو بار در یک پنجره [Terminal] در Laragon استفاده کردیم:
    • برای نصب کتابخانه [SwiftMailerکه ارسال ایمیل‌ها را ممکن می‌سازد؛
    • برای نصب کتابخانه [php-mime-mail-parser]، که امکان خواندن ایمیل‌ها را فراهم می‌کند؛
  • در [13]، پس از تأیید جادوگر ایجاد پروژه، پروژه در [13] در زبانه پروژه‌ها ظاهر می‌شود؛

17.2. ایجاد یک صفحهٔ ایستا

توجه: برای ادامه این راهنما، باید [Laragon] در حال اجرا باشد.

ما نشان خواهیم داد چگونه یک صفحهٔ ایستا HTML (زبان نشانه‌گذاری HyperText) را با استفاده از NetBeans ایجاد کنیم:

Image

  • در [1-5]، یک پوشه با نام [01] ایجاد می‌کنیم؛

Image

Image

  • برای [6-12]، ما یک فایل به نام HTML [exemple-01.html] ایجاد می‌کنیم؛

فایل [exemple-01.html] به صورت از پیش پرشده به شرح زیر تولید می‌شود (مه ۲۰۱۹):


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

بیایید محتوای آن را به شرح زیر به‌روزرسانی کنیم:


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

ما عنوان صفحه (خط ۴) و محتوای آن (خط ۹) را تغییر داده‌ایم.

اکنون بیایید سرور لارگون آپاچی را وادار کنیم این صفحه، HTML را نمایش دهد:

Image

  • در [1-2]، ما صفحه نمایش‌داده‌شده توسط سرور لارگون آپاچی را داریم؛
  • در [3]، URL صفحهٔ نمایش‌داده‌شده؛
  • در [4]، عنوانی که ما اصلاح کرده‌ایم؛
  • در [5]، محتوایی که ما تغییر داده‌ایم؛

صفحه نمایش داده شده یک صفحه ایستا است: می‌توان آن را هر تعداد بار که بخواهید در مرورگر (F5) بارگذاری کرد و همیشه همان محتوا نمایش داده می‌شود.

اکثر مرورگرها دسترسی به داده‌های مبادله شده بین کلاینت و سرور را، همانطور که در بخش «لینک» توضیح داده شده است، فراهم می‌کنند. برای دسترسی به این داده‌ها در مرورگر فایرفاکس (مه ۲۰۱۹)، باید F12 را وارد کنید:

Image

همان‌طور که در [1] نشان داده شده است، بیایید صفحه را دوباره بارگذاری کنیم (F5):

Image

  • در [2]، سند بارگذاری‌شده توسط مرورگر: ما آن را انتخاب می‌کنیم؛

Image

  • در [5]، سند مورد تجزیه و تحلیل انتخاب می‌شود؛
  • در [3-4]، درخواست مشاهدهٔ مبادلات کلاینت/سرور را داریم؛
  • در [6]، این مبادلات؛

Image

  • در [7]، زبانهٔ سربرگ‌ها انتخاب شده است؛
  • در [8]، URL که توسط مرورگر درخواست شده است؛
  • در [9]، فرمان ارسال‌شده به سرور [GET http://localhost/php7/scripts-web/01/exemple-01.html HTTP/1.1] است؛
  • در [10]، سربرگ‌های HTTP که بعداً توسط مرورگر (کلاینت) ارسال می‌شوند؛
  • در [11]، سربرگ‌های HTTP از پاسخ سرور؛

Image

  • در [12-14]، پاسخ سرور که پس از هدرهای HTTP ارسال شده است؛
  • در [14]، می‌توانیم ببینیم که مرورگر کلاینت صفحه HTML را که ما ساختیم دریافت کرده است. سپس این کد را تفسیر کرد تا موارد زیر را نمایش دهد:

Image

17.3. ایجاد یک صفحه پویا در PHP

اکنون یک صفحه پویا در PHP می‌نویسیم:

Image

Image

  • در [1-8]، ما یک صفحه [exemple-01.php] ایجاد می‌کنیم؛

فایل [exemple-01.php] به صورت از پیش پرشده به شرح زیر تولید می‌شود (مه ۲۰۱۹):


<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <?php
        // کد خود را اینجا قرار دهید
        ?>
    </body>
</html>

ما کد بالا را به شرح زیر اصلاح می‌کنیم:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemple de page dynamique</title>
    </head>
    <body>
        <?php
        //زمان: تعداد میلی‌ثانیه‌ها بین لحظهٔ حاضر و ۱ ژانویهٔ ۱۹۷۰
        // قالب نمایش تاریخ و زمان
        // d: روز (۲ رقم)
        // m: ماه دو رقمی
        // y: سال دو رقمی
        // H: ساعت 0.23
        // I: دقیقه
        // s: ثانیه‌ها
        print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
        ?>
    </body>
</html>

توضیحات

  • خط ۵: عنوان صفحه را تغییر داده‌ایم؛
  • خط ۱۷: تاریخ و زمان فعلی را چاپ می‌کند؛

در اصل، اسکریپت PHP بالا زمان فعلی را به کنسول می‌نویسد. با این حال، هنگامی که توسط یک وب‌سرور اجرا می‌شود، خروجی دستور [print] – که معمولاً به کنسول اجرای اسکریپت هدایت می‌شود – به اینجا، یعنی به ارتباط بین سرور و کلاینت آن، هدایت می‌شود. بنابراین، در یک محیط وب، اسکریپت بالا زمان فعلی را به صورت متن برای کلاینت، در این مورد یک مرورگر، ارسال می‌کند.

بیایید اسکریپت [exemple-01.php] را اجرا کنیم:

Image

  • به عنوان [3]، URL که از سرور وب آپاچی درخواست شده است؛
  • به [4]، عنوان صفحه‌ای که آن را تغییر داده‌ایم؛
  • در [5]، محتوایی که توسط دستور [print] تولید شده است؛

این یک صفحه پویا است زیرا اگر صفحه را چندین بار در مرورگر (F5) بارگذاری مجدد کنید، محتوای آن تغییر می‌کند (زمان تغییر می‌کند).

مرورگر یک فید HTML دریافت کرده است. برای مشاهده آن، باید کد منبع صفحه را در مرورگر نمایش دهید:

Image

  • برای مشاهده منوی [1]، در مرورگر روی صفحه کلیک راست کنید؛
  • در [2]، URL صفحه [exemple-01.php] اما با پیشوند [view-source :] [3]؛
  • در [4]، محتوای HTML که مرورگر نمایش داد؛

بنابراین مهم است به یاد داشته باشیم که یک اسکریپت PHP که قرار است توسط یک سرور وب اجرا شود، باید یک جریان HTML تولید کند.

حال بیایید به (F12) سربرگ‌های HTTP ارسال‌شده توسط سرور به مرورگر مشتری نگاهی بیندازیم:

Image

  • در [3]، یک هدر HTTP وجود دارد که هنگام درخواست صفحهٔ ایستا، حضور نداشت. این هدر نشان می‌دهد که پاسخ سرور توسط یک اسکریپت PHP تولید شده است؛

ما مشاهده کرده‌ایم که پاسخ سرور (جریان HTML در اینجا) می‌تواند توسط یک اسکریپت PHP تولید شود. این اسکریپت همچنین می‌تواند سربرگ‌های HTTP و تقریباً تمام عناصر پاسخ سرور را تولید کند.

17.4. مبانی زبان HTML

این فصل به برنامه‌نویسی WEB در PHP نمی‌پردازد. یک برنامه وب MVC در بخش مرتبط توسعه داده شده است. در عوض، این فصل بر سرویس‌های وب تمرکز دارد: صفحات PHP که داده‌ها را از طریق یک سرور وب به سایر کلاینت‌های PHP ارائه می‌دهند. با این حال، ما احساس کردیم که ارائه برخی مبانی HTML به خواننده مفید خواهد بود.

یک مرورگر وب می‌تواند اسناد مختلفی را نمایش دهد که رایج‌ترین آن‌ها سند HTML (زبان نشانه‌گذاری HyperText) است. این متن با برچسب‌هایی به شکل <balise>texte</balise> قالب‌بندی شده است. برای مثال، متن <b>important</b> متن important را به صورت پررنگ نمایش می‌دهد. تگ‌های مستقل نیز وجود دارند، مانند تگ <hr/> که یک خط افقی نمایش می‌دهد. ما به بررسی تگ‌هایی که در یک متن HTML یافت می‌شوند، نمی‌پردازیم. نرم‌افزارهای WYSIWYG متعددی وجود دارند که به شما امکان می‌دهند یک صفحه WEB را بدون نوشتن حتی یک خط کد HTML بسازید. این ابزارها به‌طور خودکار کد HTML را برای طرح‌بندی ایجادشده با استفاده از ماوس و کنترل‌های از پیش تعریف‌شده تولید می‌کنند. بنابراین شما می‌توانید (با استفاده از ماوس) یک جدول را در صفحه درج کرده و سپس کد HTML تولید شده توسط نرم‌افزار را مشاهده کنید تا بفهمید برای تعریف یک جدول در یک صفحه WEB باید از کدام تگ‌ها استفاده کنید. به همین سادگی. علاوه بر این، آشنایی با زبان HTML ضروری است، زیرا برنامه‌های وب پویا باید کد HTML را خودشان تولید کنند تا به کلاینت‌های WEB ارسال کنند. این کد توسط یک برنامه تولید می‌شود و بدیهی است که شما باید بدانید چه چیزی را تولید کنید تا کلاینت صفحه وب مورد نظر خود را دریافت کند.

خلاصه اینکه برای شروع برنامه‌نویسی وب نیازی به دانستن کل زبان HTML نیست. با این حال، این دانش ضروری است و می‌توان آن را از طریق استفاده از نرم‌افزارهای سازنده صفحه وب WYSIWYG مانند WEB و ده‌ها نرم‌افزار دیگر به دست آورد. راه دیگری برای کشف پیچیدگی‌های زبان HTML، گشت‌وگذار در وب و مشاهده کد منبع صفحاتی است که ویژگی‌های جالبی دارند که شما هنوز با آن‌ها آشنا نیستید.

مثال زیر را در نظر بگیرید که برخی از عناصری را که می‌توان در یک سند WEB یافت، برجسته می‌کند، مانند:

  • یک جدول؛
  • یک تصویر؛
  • یک لینک.

Image

یک سند HTML معمولاً شکل زیر را دارد:

<html> <head> <title>یک عنوان</title> ... </head> <body attributes> ... </body></html>

کل سند در بین تگ‌های <html>…</html> قرار گرفته است. این سند از دو بخش تشکیل شده است:

  1. <head>…</head>: این بخش غیرقابل نمایش سند است. این بخش اطلاعاتی را در اختیار مرورگری که سند را نمایش می‌دهد قرار می‌دهد. این بخش اغلب شامل تگ <title>…</title> است که متنی را برای نمایش در نوار عنوان مرورگر تعیین می‌کند. همچنین ممکن است شامل تگ‌های دیگری باشد، به‌ویژه آن‌هایی که کلمات کلیدی سند را تعریف می‌کنند و بعداً توسط موتورهای جستجو استفاده می‌شوند. این بخش همچنین ممکن است حاوی اسکریپت‌ها باشد، که معمولاً به زبان جاوااسکریپت یا وی‌بی‌اسکریپت نوشته می‌شوند و توسط مرورگر اجرا خواهند شد.
  1. <body attributes>…</body>: این بخشی است که توسط مرورگر نمایش داده می‌شود. تگ‌های موجود در این بخش، چیدمان بصری «دلخواه» سند را به مرورگر می‌گویند. هر مرورگر این تگ‌ها را به روش خود تفسیر خواهد کرد. بنابراین ممکن است دو مرورگر یک سند وب را به شکل متفاوتی نمایش دهند. این معمولاً یکی از سردردهای طراحان وب است.

کد HTML برای سند مثال ما به شرح زیر است:


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

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

        <table border="1">
            <thead>
                <tr>
                    <th>Colonne 1</th>
                    <th>Colonne 2</th>
                    <th>Colonne 3</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>cellule(1,1)</td>
                    <td style="text-align: center;">cellule(1,2)</td>
                    <td>cellule(1,3)</td>
                </tr>
                <tr>
                    <td>cellule(2,1)</td>
                    <td>cellule(2,2)</td>
                    <td>cellule(2,3</td>
                </tr>
            </tbody>
        </table>
        <br/><br/>
        <table border="0">
            <tr>
                <td>Une image</td>
                <td>
                    <img border="0" src="images/cerisier.jpg"/></td>
            </tr>
            <tr>
                <td>Le site de Polytech'Angers</td>
                <td><a href="http://www.polytech-angers.fr/fr/index.html">ici</a></td>
            </tr>
        </table>
    </body>
</html>
Elément
برچسب‌ها و مثال‌ها HTML
titre du document
<title>برخی از برچسب‌های HTML</title> (خط ۵)
متن [Quelques balises HTML] هنگام نمایش سند در نوار عنوان مرورگر ظاهر خواهد شد
barre horizontale
<hr />: یک خط افقی را نمایش می‌دهد (خط ۱۰)
tableau
<table attributes>….</table>: برای تعریف جدول (خطوط ۱۲، ۳۲)
<thead>…</thead>: برای تعریف سربرگ‌های ستون (خطوط ۱۳، ۱۹)
<tbody>…</tbody>: برای تعریف محتوای جدول (خطوط ۲۰، ۳۱)
<tr attributes>…</tr>: برای تعریف یک سطر (خطوط 21 و 25)
<td attributes>…</td>: برای تعریف یک سلول (خط ۲۲)
مثال‌ها:
<table border="1">…</table>: ویژگی border ضخامت حاشیه جدول را مشخص می‌کند
<td style="text-align: centre;">cell(1,2)</td> (خط ۲۳): یک سلول را تعریف می‌کند که محتوای آن cell(1,2) خواهد بود. این محتوا به صورت افقی مرکزدهی می‌شود (text-align: centre).
image
<img border="0" src="images/cerisier.jpg"/> (خط ۳۸): تصویری بدون حاشیه (border="0") را تعریف می‌کند که فایل منبعه آن [images/cerisier.jpg] روی سرور وب (src="images/cerisier.jpg") است. این لینک در یک سند وب تولیدشده با استفاده از URL http://localhost/php7/scripts-web/01/balises.html ظاهر می‌شود. بنابراین، مرورگر برای دریافت تصویر مرجع، درخواست http://localhost/php7/scripts-web/01/images/cerisier.jpg را به URL ارسال می‌کند.
lien
<a href="http://www.polytech-angers.fr/fr/index.html">اینجا (خط ۴۲): متن ici را به‌عنوان پیوندی به URL http://www.polytech-angers.fr/fr/index.html در نظر می‌گیرد.
fond de page
<body style="background-image: url(images/standard.jpg)"> (خط ۸): نشان می‌دهد که تصویری که باید به‌عنوان پس‌زمینه صفحه استفاده شود، در URL [images/standard.jpg] روی سرور WEB قرار دارد. در چارچوب مثال ما، مرورگر برای دریافت این تصویر پس‌زمینه، URL http://localhost/php7/scripts-web/01/images/standard.jpg را درخواست خواهد کرد.

این مثال ساده نشان می‌دهد که برای ساخت کل سند، مرورگر باید سه درخواست به سرور ارسال کند:

  1. http://localhost/php7/scripts-web/01/images/balises.html برای بازیابی منبع HTML سند
  2. http://localhost/php7/scripts-web/01/images/cerisier.jpg برای دریافت تصویر cerisier.jpg
  3. http://localhost/php7/scripts-web/01/images/standard.jpg برای دریافت تصویر پس‌زمینه standard.jpg

این موضوع از طریق ترافیک شبکه بین کلاینت و سرور نشان داده می‌شود (F12 در مرورگر):

Image

  • در [3-5]، می‌توانیم سه درخواست ارسال‌شده توسط مرورگر را مشاهده کنیم؛

17.5. ایستا کردن یک صفحهٔ پویا

بیایید ببینیم چگونه می‌توانیم صفحه HTML [exemple-01.html] را پویا کنیم. بیایید محتوا را کپی کنیم

Image

ما محتوای [exemple-01.html] را در فایل [page-01.php] کپی کرده‌ایم. اگر اسکریپت وب [2] را اجرا کنیم، در مرورگر خروجی زیر را مشاهده می‌کنیم:

Image

  • در [3]، URL درخواستی؛
  • [4]، عنوان صفحه؛
  • [5]، محتوای صفحه؛

اگر کد دریافتی توسط مرورگر را مشاهده کنید، موارد زیر را خواهید یافت:

Image

  • در [7]، کد HTML در داخل اسکریپت [exemple-01.php] قرار گرفته است

تفسیرگر PHP اسکریپت [page-01.php] را تفسیر کرد و همان جریان HTML را مانند صفحهٔ ایستا [exemple-01.html] تولید کرد. در اسکریپت [page-01.php]، هیچ PHP وجود نداشت، فقط HTML. این یک نکته را به ما می‌آموزد: وقتی تفسیرگر PHP در یک اسکریپت PHP، HTML را پیدا می‌کند، آن را دست‌نخورده باقی می‌گذارد و همان‌طور که هست برای کلاینت ارسال می‌کند.

حالا بیایید چند دستور PHP را در اسکریپت [page-01.php] قرار دهیم تا تفسیرگر PHP کاری برای انجام دادن داشته باشد:


<!DOCTYPE html>
<html>
    <head>
        <title><?php print $page->title ?></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div><b><?php print $page->contents ?></b></div>
    </body>
</html>

در خطوط ۴ و ۹، کد PHP را برای تولید پویا عنوان و محتوای صفحه اضافه کرده‌ایم. در اینجا فرض می‌کنیم متغیر [$page] یک شیء حاوی داده‌های قابل نمایش است.

اگر این کد جدید را اجرا کنیم، نتیجه زیر را در مرورگر مشاهده می‌کنیم:

Image

  • در [1]، URL درخواست‌شده؛
  • در [2]، عنوان صفحه نمایش داده نشد زیرا متغیر [$page] تعریف نشده بود؛
  • در [3]، همین موضوع در مورد محتوا نیز صدق می‌کند؛

اکنون، بیایید اسکریپت وب زیر [exemple-02.php] را بنویسیم:

Image

اسکریپت [exemple-02.php] به شرح زیر خواهد بود:


<?php

// ما عناصر صفحه را که باید نمایش داده شوند تعریف می‌کنیم
$page=new \stdclass();
$page->title="Un nouveau titre";
$page->contents="Un nouveau contenu généré dynamiquement";
// نمایش [page-01]
require_once "page-01.php";
  • خطوط ۴–۶: ما شیء [$page] را تعریف می‌کنیم؛
  • خط ۸: اسکریپت [page-01.php] وارد شده است. کد این اسکریپت سپس تفسیر خواهد شد:
    • متغیر [$page] اکنون تعریف شده است و تفسیرگر PHP از آن استفاده خواهد کرد؛
    • کد HTML از [page-01.php] بدون تغییر برای کلاینت ارسال خواهد شد؛
    • نتایج عملیات PHP و [print] در جریان متنی که به کلاینت ارسال می‌شود، گنجانده خواهند شد؛

اکنون، اگر اسکریپت وب [exemple-02.php] را اجرا کنیم، خروجی زیر را در مرورگر مشاهده می‌کنیم:

Image

اگر محتوای متنی دریافتی توسط مرورگر را مشاهده کنیم:

Image

  • کدهای PHP، که در اصل [2] و [3] بودند، با نتایج دو دستور [print] جایگزین شده‌اند؛

دو نکته را می‌توان از این مثال برداشت:

  • صفحات HTML که برای مرورگر در نظر گرفته شده‌اند، می‌توانند در اسکریپت‌های PHP ایزوله شوند که تنها شامل این کد HTML و چند بخش پویا هستند که توسط کد PHP تولید شده‌اند. در این صفحات باید تا حد امکان PHP کم باشد؛
  • تمام منطق تولیدکننده داده‌های پویا که در صفحات HTML گنجانده شده است، باید در اسکریپت‌های خالص PHP ایزوله شود، به گونه‌ای که هیچ کد نمایش صفحه‌ای در آن‌ها وجود نداشته باشد (HTML، CSS، JavaScript و غیره)؛

این امر امکان تفکیک وظایف را فراهم می‌کند:

  • وظیفه تولید صفحات وبی که باید نمایش داده شوند (HTML, CSS, JavaScript و غیره)؛
  • وظیفه پیاده‌سازی منطق برای اپلیکیشن وبی که در حال ساخت آن هستیم. این منطق می‌تواند با استفاده از معماری سه‌لایه پیاده‌سازی شود، دقیقاً همان‌طور که با اسکریپت‌های کنسول انجام دادیم؛

سپس اسکریپت‌های وب مشخصی را خواهیم ساخت؛

  • این اسکریپت‌ها فقط داده‌ها را به کلاینت ارسال می‌کنند و هیچ قالب‌بندی‌ای انجام نمی‌دهند (HTML, CSS, JavaScript). بنابراین، آن‌ها به جای صفحات وب، سرورهای داده خواهند بود؛
  • کلاینت‌های این اسکریپت‌های وب، اسکریپت‌های کنسول خواهند بود که داده‌های ارسال‌شده توسط سرور را بازیابی کرده و آن را پردازش می‌کنند؛

17.6. کاربرد تاریخ و زمان مشتری/سرور

اکنون پیکربندی زیر را در نظر می‌گیریم:

Image

ما قرار است بنویسیم:

  • یک اسکریپت وب، [1]، که تاریخ و زمان کنونی را به کلاینت خود ارسال می‌کند؛
  • یک اسکریپت کنسول [2] که به‌عنوان کلاینت برای اسکریپت وب عمل خواهد کرد: این اسکریپت تاریخ و زمان ارسال‌شده توسط اسکریپت وب را بازیابی کرده و آن‌ها را در کنسول نمایش می‌دهد؛

Image

  • در [1]، اسکریپت وب [date-time-server.php]؛
  • [2]، اسکریپت کنسول [date-time-client] که کلاینت اسکریپت وب است؛

17.6.1. اسکریپت سمت سرور

ما قبلاً یک اسکریپت وب نوشته‌ایم که تاریخ و زمان فعلی را تولید می‌کند، همان‌طور که در بخش مرتبط توضیح داده شده است. این اسکریپت زیر بود: [exemple-01.php]:


<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemple de page dynamique</title>
    </head>
    <body>
        <?php
        //زمان: تعداد میلی‌ثانیه‌ها از ۱ ژانویه ۱۹۷۰
        // قالب نمایش تاریخ و زمان
        // d: روز (۲ رقم)
        // m: ماه دو رقمی
        // y: سال (۲ رقم)
        // H: ساعت 0.23
        // i: دقیقه
        // s: ثانیه‌ها
        print "<b>Date et heure du jour : </b>" . date("d/m/y H:i:s", time());
        ?>
    </body>
</html>

ما گفتیم که قرار است سرورهای داده بنویسیم: داده‌های خام بدون قالب‌بندی (HTML). بنابراین اسکریپت سرور [date-time-server.php] به شرح زیر خواهد بود:


<?php

//سربرگ را روی HTP تنظیم کنید [Content-Type]
header('Content-Type: text/plain; charset=UTF-8');
//
// تاریخ و زمان ارسال
//زمان: تعداد میلی‌ثانیه‌ها از ۱ ژانویه ۱۹۷۰
// قالب نمایش تاریخ و زمان
// d: روز (۲ رقم)
// m: ماه دو رقمی
// y: سال (۲ رقم)
// H: ساعت 0.23
// i: دقیقه
// s: ثانیه‌ها
print date("d/m/y H:i:s", time());
  • خط ۴: ما هدر HTTP [Content-Type] را تنظیم می‌کنیم که به کلاینت ماهیت سندی را که قرار است دریافت کند، اطلاع می‌دهد. تا این نقطه، هدر به این صورت بود: [Content-Type]. در اینجا به کلاینت می‌گوییم که سند متن ساده است: HTML. این برای کلاینت کنسول ما که تلاش نمی‌کند از این هدر استفاده کند، مهم نیست. این برای مرورگرهای کلاینت که از این هدر استفاده می‌کنند، اهمیت بیشتری دارد؛

بیایید این اسکریپت سمت سرور را اجرا کنیم:

Image

اگر پاسخ سرور (F12) را در مرورگر بررسی کنیم، می‌توانیم در [5]، هدر HTTP را که اسکریپت سرور تنظیم کرده است، و در [8]، سند متنی دریافتی را مشاهده کنیم؛

Image

17.6.2. اسکریپت سمت کلاینت

در بخش «Link»، چندین کلاینت HTTP را توسعه دادیم. می‌توانستیم از آن‌ها برای بازیابی سند متنی ارسال‌شده توسط اسکریپت سرور [date-time-server.php] استفاده کنیم. ما این کار را انجام نخواهیم داد. همان‌طور که برای پروتکل‌های SMTP و IMAP انجام دادیم، از یک کتابخانه شخص ثالث، یعنی کامپوننت [HttpClient] از فریم‌ورک Symfony [https://symfony.com/doc/master/components/http_client.html] استفاده خواهیم کرد.

مانند دو کتابخانه قبلی، از ابزار [Composer] برای نصب کامپوننت Symfony [HttpClient] استفاده می‌کنیم. در یک پنجره Laragon [Terminal] (به بخش «link» مراجعه کنید)، دستور زیر را وارد کنید:

Image

  • در [3]، بررسی کنید که در پوشه [<laragon>/www/] هستید، که در آن <laragon> پوشه نصب Laragon است؛
  • به [4]، دستور [composer] را اجرا کنید که کتابخانه Symfony را نصب می‌کند؛
  • در [5]، هیچ چیزی نصب نمی‌شود زیرا کتابخانه [HttpClient] قبلاً روی این ماشین نصب شده بود؛
  • در [6-7]، پوشه‌های جدیدی در [<laragon>/www/vendor/symfony] ظاهر می‌شوند؛

به جای [5]، باید چیزی شبیه به موارد زیر داشته باشید:


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

اطمینان حاصل کنید که پوشه [<laragon>/www/vendor] بخشی از شاخه [Include Path] پروژه شما باشد (لینک موجود در پاراگراف را ببینید):

Image

پس از انجام این کار، می‌توانیم اسکریپت کنسول [date-time-client.php] را بنویسیم:

Image

اسکریپت کنسول [date-time-client.php] فایل زیر را پردازش خواهد کرد: jSON [config-date-time-client.json]:

1
2
3
{
    "url": "http://localhost/php7/scripts-web/02/date-time-server.php"
}
  • خط ۲: URL از اسکریپت سرور؛

اسکریپت کلاینت [date-time-client.php] به شرح زیر خواهد بود:


<?php

// سرویس تاریخ و زمان کلاینت
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-date-time-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

//ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();

try {
  //ارسال درخواست
  $response = $httpClient->request('GET', $config['url']);
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // بازیابی سربرگ‌ها
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // بدنه پاسخ بازیابی می‌شود
  $content = $response->getContent();
  // نمایش آن
  print "---Réponse du serveur : [$content]\n";
} catch (TypeError | RuntimeException $ex) {
  //خطا را نمایش می‌دهد
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
  exit;
}

توضیحات

  • خط ۱۰: همان‌طور که برای کتابخانه‌های قبلی انجام دادیم، فایل [<laragon>/www/vendor/autoload.php] را بارگذاری می‌کنیم؛
  • خط ۱۱: ما کلاس [HttpClient] را که از آن استفاده خواهیم کرد، اعلام می‌کنیم؛
  • خطوط ۱۳–۲۴: پیکربندی اسکریپت را از فرهنگ لغت [$config] بازیابی می‌کنیم؛
  • خط ۲۷: ما یک شیء از نوع [HttpClient] ایجاد می‌کنیم؛
  • خط ۳۱: ما با استفاده از فرمان GET، URL را از اسکریپت سرور درخواست می‌کنیم: [GET URL HTTTP/1.1]. این عملیات غیرهمزمان است. اجرا در خط ۳۳ بدون انتظار برای دریافت پاسخ ادامه می‌یابد؛
  • خط ۳۳: وضعیت پاسخ درخواست می‌شود. این وضعیت در اولین هدر HTTP که توسط سرور بازگردانده می‌شود، یافت می‌شود. بنابراین، اگر این هدر [HTTP/1.1 200 OK] باشد، وضعیت پاسخ 200 است. این عملیات مسدودکننده است: اجرای برنامه تنها پس از دریافت کل پاسخ از سرور توسط کلاینت از سر گرفته می‌شود؛
  • خط ۳۷: هدرهای HTTP پاسخ درخواست می‌شوند؛
  • خط ۴۲: سند بازگردانده‌شده توسط سرور درخواست می‌شود؛ می‌دانیم که در این مورد این سند متنی است.
  • خطوط ۴۵–۴۹: در صورت بروز خطا، پیام خطا نمایش داده می‌شود؛

وقتی اسکریپت کلاینت اجرا می‌شود (برای دسترسی به اسکریپت سرور، لارگون باید در حال اجرا باشد)، نتیجه زیر در کنسول نمایش داده می‌شود:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Thu, 30 May 2019 14:42:03 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
content-length: 17
content-type: text/plain; charset=UTF-8
---Réponse du serveur : [30/05/19 14:42:03]

تاریخ و زمان فعلی در خط ۸ به‌درستی بازیابی شده‌اند.

ممکن است کنجکاو باشید بدانید اسکریپت کلاینت چه چیزی را به سرور ارسال کرده است. برای این کار، از سرور عمومی خود TCP (بخش «لینک» را ببینید) استفاده خواهیم کرد:

Image

  • در [1]، پوشهٔ utilities؛
  • به [2]، سرور TCP روی پورت ۱۰۰ راه‌اندازی می‌شود؛
  • در [3]، سیستم منتظر دستوری است که از طریق صفحه‌کلید وارد می‌شود؛

ما فایل پیکربندی اسکریپت [date-time-client.php] را اصلاح می‌کنیم:


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

این بار، کلاینت با سرور [localhost] در پورت ۱۰۰ تماس می‌گیرد. بنابراین، سرور عمومی TCP ما فراخوانی خواهد شد. وقتی اسکریپت کنسول [date-time-client.php] را اجرا می‌کنیم، کنسول سرور عمومی TCP به شرح زیر تغییر می‌کند:

Image

  • به [3]، دستور HTTP GET تولید شده توسط اسکریپت کلاینت؛
  • به [4]، امضای اسکریپت کنسول؛
  • در [5]، پاسخ سرور به اسکریپت کلاینت. توجه داشته باشید که این یک پاسخ معتبر HTTP نیست:
    • باید سربرگ‌های HTTP وجود داشته باشند؛
    • که با یک خط خالی دنبال می‌شود؛
    • که به دنبال آن سند متنی ارسال‌شده به کلاینت می‌آید؛
  • در [6]، ارتباط با اسکریپت کلاینت قطع می‌شود تا اسکریپت تشخیص دهد که کل پاسخ را دریافت کرده است؛

در اسکریپت سمت کلاینت، خروجی کنسول زیر نمایش داده می‌شود:

Image

  • در [7]، آنچه کلاینت Symfony دریافت کرد؛

17.6.3. اسکریپت سمت سرور – نسخهٔ ۲

به طور پیش‌فرض، توابع PHP برای نوشتن یک اسکریپت وب، شیءگرا نیستند. بنابراین، در سمت سرور، ما ملزم به ترکیب کلاس‌ها با توابع استاندارد PHP هستیم. برای دستیابی به سبکی یکپارچه‌تر، از کتابخانه [HttpFoundation] در چارچوب Symfony استفاده خواهیم کرد. این کتابخانه تمام توابع استاندارد PHP را برای یک سرویس وب در سیستمی از کلاس‌ها و رابط‌ها جای داده است. مستندات این کتابخانه در URL [https://symfony.com/doc/current/components/http_foundation.html] (مه ۲۰۱۹) موجود است.

برای نصب این کتابخانه، در ترمینال لاراکون (به بخش پیوندها مراجعه کنید) به شرح زیر عمل می‌کنیم:

Image

  • [2-3]: اطمینان حاصل کنید که در پوشه [<laragon>/www] هستید؛
  • [4]: دستور [composer] که کتابخانه [HttpFoundation] را نصب می‌کند؛
  • [5]: در این مثال، کتابخانه قبلاً نصب شده بود؛

در اولین نصب، باید خروجی‌های کنسول مشابه موارد زیر را مشاهده کنید:


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

نسخه دوم وب‌سرور [date-time-server-2.php] به شرح زیر است:


<?php

// استفاده از کتابخانه‌های Symfony

//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpFoundation\Response;

// تنظیم هدر Content-Type
$response=new Response();
$response->headers->set("content-type","text/plain");
$response->setCharset("utf-8");

// تنظیم بدنه پاسخ
//
// تاریخ و زمان ارسال
//زمان: تعداد میلی‌ثانیه‌ها از ۱ ژانویه ۱۹۷۰
// فرمت نمایش تاریخ و زمان
// d: روز (۲ رقم)
// m: ماه دو رقمی
// y: سال (۲ رقم)
// H: ساعت 0.23
// i: دقیقه
// s: ثانیه‌ها
$response->setContent(date("d/m/y H:i:s", time()));

// پاسخ ارسال می‌شود
$response->send();

نظرات

  • خط ۷: کلاس [Response] از کتابخانه Symfony [HttpFoundation] کل پاسخ به کلاینت‌های سرویس وب را مدیریت می‌کند؛
  • خط ۱۰: ایجاد یک نمونه از کلاس [Response];
  • خط ۱۱: نوع پاسخ به صورت [text/plain] مشخص شده است؛
  • خط ۱۲: پاسخ متن UTF-8 است؛
  • خط ۲۵: سند پاسخ، طبق درخواست کلاینت، تنظیم می‌شود؛
  • خط ۲۸: پاسخ برای کلاینت ارسال می‌شود؛

17.6.4. اسکریپت کلاینت – نسخهٔ ۲

اسکریپت کلاینت بدون تغییر باقی می‌ماند. تنها فایل پیکربندی آن، [config-date-time-client.json]، اصلاح شده است:

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

نتایج همانند نسخهٔ ۱ است.

17.7. یک سرور داده jSON

پاسخ یک اسکریپت وب ممکن است شامل چندین بخش داده باشد که می‌توان آن‌ها را در آرایه‌ها و اشیاء گروه‌بندی کرد. سپس اسکریپت می‌تواند این عناصر مختلف را در یک رشته jSON ارسال کند که کلاینت آن را رمزگشایی خواهد کرد.

Image

17.7.1. اسکریپت سمت سرور

اسکریپت [json-server.php] از کلاس زیر استفاده می‌کند:


<?php

namespace Modèles;

class Personne implements \JsonSerializable {
  // ویژگی‌ها
  private $nom;
  private $prénom;
  private $âge;

  //تبدیل یک آرایهٔ انجمنی به یک شی [Personne]
  public function setFromArray(array $assoc): Personne {
    // شیء فعلی با آرایهٔ asociative مقداردهی اولیه می‌شود
    foreach ($assoc as $attribute => $value) {
      $this->$attribute = $value;
    }
    // نتیجه
    return $this;
  }

  // گیرنده و تنظیم‌کننده
  public function getNom() {
    return $this->nom;
  }

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

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

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

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

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

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

  // رابط JsonSerializable را پیاده‌سازی می‌کند
  public function jsonSerialize(): array {
    //یک آرایهٔ asociative بازمی‌گرداند که ویژگی‌های شیء به‌عنوان کلیدها هستند
    // این آرایه سپس می‌تواند به‌عنوان jSON رمزگذاری شود
    return get_object_vars($this);
  }

  //تبدیل jSON به یک شیء [Personne]
  public static function jsonUnserialize(string $json): Personne {
    //یک شخص از رشته jSON ایجاد می‌شود
    return (new Personne())->setFromArray(json_decode($json, true));
  }

}

توضیحات

  • خط ۵: کلاس رابط PHP [JsonSerializable] را پیاده‌سازی می‌کند. این مستلزم پیاده‌سازی متد [jsonSerialize] در خطوط ۵۵–۵۹ است. این متد باید یک آرایهٔ asociative را بازگرداند که باید در jSON سریالیزه شود. هنگامی که عبارت [json_encode($personne)] استفاده می‌شود، تابع [json_encode] بررسی می‌کند که آیا کلاس [Personne] رابط [JsonSerializable] را پیاده‌سازی می‌کند یا خیر. در این صورت، عبارت به [json_encode($personne→serialize())] تبدیل می‌شود؛
  • خطوط ۱۲–۱۹: کلاس هیچ سازنده‌ای ندارد اما دارای یک مقداردهنده است. سپس می‌توان کلاس [Personne] را با استفاده از عبارت [(new Personne())→setFromArray($array)] نمونه زد. انواع مختلفی از مقداردهی‌های اولیه وجود دارد، در حالی که تنها یک سازنده می‌تواند وجود داشته باشد. این مقداردهی‌های اولیه امکان روش‌های مختلفی برای نمونه زدنی نوع [(new Personne())→initialiseuri(…)] را فراهم می‌کنند؛
  • خطوط ۶۲–۶۵: تابع استاتیک [jsonUnserialize] اجازه می‌دهد یک شیء [Personne] از رشتهٔ jSON آن ایجاد شود؛

اسکریپت [json-server.php] به شرح زیر خواهد بود:


<?php

// وابستگی‌ها
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;

// سربرگ Content-Type و مجموعه کاراکتر مورد استفاده تنظیم می‌شوند
$response = new Response();
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");

//یک شیء Person ایجاد می‌شود
$personne = (new Personne())->setFromArray([
  "nom" => "de la Hûche",
  "prénom" => "jean-paul",
  "âge" => 27]);
//یک آرایهٔ asociative
$assoc = ["attr1" => "value1",
  "attr2" => [
    "prenom" => "Jean-Paul",
    "nom" => "de la Hûche"
  ]
];
// محتوای پاسخ jSON است
$response->setContent(json_encode([$personne, $assoc]));

//پاسخ ارسال می‌شود
$response->send();

توضیحات

  • خطوط ۴–۵: کلاس [Personne] وارد می‌شود؛
  • خط ۱۱: ما مشخص می‌کنیم که سند از نوع [application/json] خواهد بود. با دریافت این هدر، مرورگرها رشته jSON را به صورت قالب‌بندی‌شده نمایش می‌دهند، نه به صورت متن ساده؛
  • خط ۱۲: رشته jSON شامل کاراکترهای UTF-8 خواهد بود؛
  • خطوط ۱۵–۱۸: یک شیء با نام [Personne] ایجاد می‌شود؛
  • خطوط ۲۰–۲۵: یک آرایهٔ انجمنی دو‌سطحی ایجاد می‌شود؛
  • خط ۲۷: رشته jSON از یک آرایه به کلاینت ارسال می‌شود:
    • عنصر [$personne] با استفاده از متد [jsonSerialize] به jSON سریالیزه خواهد شد؛
    • عنصر [$assoc] به‌صورت بومی به jSON سریالیزه خواهد شد؛

اگر این اسکریپت سمت سرور اجرا شود (لاگارون باید در حال اجرا باشد)، پاسخ زیر در مرورگر بازگردانده می‌شود:

Image

Image

نظرات

  • در [2]، پاسخ قالب‌بندی‌شده jSON;
  • در [4]، پاسخ خام jSON. به رمزگذاری کاراکترهای دارای نشانه توجه کنید؛
  • در [6]، این نوع محتوا [application/json] که توسط سرور ارسال شده است، باعث شده مرورگر این قالب‌بندی را اعمال کند؛

17.7.2. کلاینت

Image

کلاینت [json-client.php] توسط فایل زیر پیکربندی شده است: jSON [config-json-client.json]:

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

اسکریپت [json-client.php] به شرح زیر است:


<?php

// کلاینت یک سرویس jSON
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-json-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

//ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();

try {
  //ارسال درخواست
  $response = $httpClient->request('GET', $config['url']);
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // سربرگ‌ها را بازیابی می‌کنیم
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  //بازیابی بدنه پاسخ jSON
  list($personne, $assoc) = json_decode($response->getContent(), true);
  //یک نمونه از یک شخص از آرایهٔ ویژگی‌های او ایجاد می‌شود
  $personne = (new Personne())->setFromArray($personne);
  //نمایش پاسخ سرور
  print "---Réponse du serveur\n";
  print "$personne\n";
  print "tableau=" . json_encode($assoc, JSON_UNESCAPED_UNICODE) . "\n";
} catch (TypeError | RuntimeException $ex) {
  //خطا نمایش داده می‌شود
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}


توضیحات

  • خطوط ۱۲–۱۳: وارد کردن کلاس [Personne];
  • خط ۳۰: ایجاد کلاینت HTTP;
  • خط ۴۴: رشته jSON ارسال‌شده توسط سرور را رمزگشایی می‌کند. می‌دانیم که آنچه رمزگذاری شده، یک آرایه دو عنصری است که شامل دو آرایه asociative است؛
  • خط ۴۶: یک شیء [Personne] ایجاد می‌شود تا در خط ۴۹ نمایش داده شود؛
  • خط ۵۰: آرایه asociative دوم نمایش داده می‌شود. دستور [print] قادر به نمایش آرایه‌ها نیست. بنابراین، این مورد به رشته jSON تبدیل می‌شود. برای اطمینان از نمایش صحیح کاراکترهای دارای علامت diakritique، پارامتر دوم باید روی [JSON_UNESCAPED_UNICODE] تنظیم شود. ما دیده‌ایم که کاراکترهای دارای علامت diakritique در رشته jSON واقعاً رمزگذاری شده‌اند؛

اجرای اسکریپت سمت کلاینت نتایج زیر را تولید می‌کند:


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

در خطوط ۱۱ و ۱۲، کاراکترهای دارای علامت diakritik به‌درستی بازیابی شده‌اند.

17.8. بازیابی متغیرهای محیطی سرویس وب

یک اسکریپت سمت سرور در محیطی وب اجرا می‌شود که از آن آگاه است. این محیط در دیکشنری $_SERVER ذخیره شده است، که یک متغیر سراسری از PHP است. اگر از کتابخانه [HttpFoundation] استفاده کنیم، این محیط در فیلد [Request→server] یافت می‌شود، جایی که [Request] درخواستی است که توسط اسکریپت وب پردازش شده است.

17.8.1. اسکریپت سرور

ما در حال نوشتن یک برنامهٔ سرور هستیم که محیط اجرای خود را به کلاینت‌هایش ارسال می‌کند.

Image

اسکریپت وب [env-server.php] به شرح زیر است:


<?php

//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

// درخواست را بازیابی می‌کند
$request = Request::createFromGlobals();
//پاسخ تولید می‌شود
$response = new Response();
// محتوای پاسخ JSON در UTF-8 است
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// محتوای پاسخ روی jSON تنظیم شده است
$response->setContent(json_encode($request->server->all()));
//پاسخ ارسال می‌شود
$response->send();
  • خط ۹: ما شیئی از نوع [Request] را بازیابی می‌کنیم که شامل تمام اطلاعات موجود در مورد درخواست HTTP دریافت‌شده توسط اسکریپت وب، و همچنین محیط اجرای آن است؛
  • خطوط ۱۳–۱۴: متن ساده حاوی کاراکترهای UTF-8 به کلاینت ارسال می‌شود؛
  • خط 16: اطلاعاتی که به کلاینت ارسال می‌شود، یک رشته کاراکتری خواهد بود که با استفاده از jSON از شیء [$request→server→all()] سریالیزه شده است: [$request→server] نشان‌دهنده محیط اجرای اسکریپت وب است. این یک شی از نوع [ServerBag] است، نوعی فرهنگ لغت. از سوی دیگر، [$request→server→all()] یک فرهنگ لغت واقعی است که حاوی محتویات [ServerBag] می‌باشد؛
  • خط ۱۸: اطلاعات ارسال می‌شود؛

اگر این اسکریپت از NetBeans اجرا شود، مرورگر صفحه زیر را نمایش می‌دهد:

Image

  • در [2]، کلیدهای مختلف در فرهنگ لغت محیط؛
  • در [3]، مقادیر این کلیدها؛

17.8.2. اسکریپت کلاینت

Image

اسکریپت کلاینت [env-client.php] توسط فایل زیر پیکربندی می‌شود: jSON [config-env-client.json]:

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

اسکریپت کلاینت [env-client.php] به شرح زیر است:


<?php

// محیط اسکریپت سمت سرور
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-env-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();
try {
  //ارسال درخواست به سرور
  $response = $httpClient->request('GET', $config['url']);
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // سربرگ‌ها را بازیابی می‌کند
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // نمایش پاسخ سرور
  print "---Réponse du serveur\n";
  $env = json_decode($response->getContent());
  foreach ($env as $key => $value) {
    print "[$key]=>$value\n";
  }
} catch (TypeError | RuntimeException $ex) {
  //خطا نمایش داده می‌شود
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

نظرات

  • خط ۴۲: پاسخ jSON از سرور سریال‌برداری می‌شود. این یک آرایهٔ asociative بازمی‌گرداند؛
  • خطوط ۴۳–۴۵: تمام مقادیر این آرایهٔ asociative نمایش داده می‌شوند؛

خروجی کنسول زیر به دست می‌آید:


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

در اینجا معنای برخی از متغیرها آمده است (برای ویندوز. در لینوکس، این متغیرها متفاوت خواهند بود):

HTTP_HOST
مقدار xxx از هدر HTTP [Host: xxx] ارسال‌شده توسط کلاینت
HTTP_USER_AGENT
مقدار xxx از هدر HTTP [User_Agent: xxx] ارسال‌شده توسط کلاینت
HTTP_ACCEPT_ENCODING
مقدار xxx از هدر HTTP [Accept-Encoding: xxx] ارسال شده توسط کلاینت
PATH
مسیر فایل‌های اجرایی روی ماشینی که اسکریپت سرور روی آن در حال اجرا است
COMSPEC
مسیر تفسیرگر فرمان DOS
PATHEXT
پسوندهای فایل‌های اجرایی
WINDIR
پوشه نصب ویندوز
SERVER_SIGNATURE
امضای وب‌سرور. در اینجا چیزی نیست.
SERVER_SOFTWARE
نوع وب‌سرور
SERVER_NAME
نام اینترنتی ماشین وب‌سرور
SERVER_PORT
پورت گوش‌دادن وب‌سرور
SERVER_ADDR
آدرس IP ماشین وب سرور، در اینجا 127:0:0:1
REMOTE_ADDR
آدرس کلاینت IP. در این مورد، کلاینت روی همان دستگاهی بود که سرور روی آن قرار داشت.
REMOTE_PORT
پورت ارتباطی کلاینت
DOCUMENT_ROOT
ریشهٔ درخت دایرکتوری برای اسناد ارائه‌شده توسط وب‌سرور
REQUEST_SCHEME
پروتکل TCP درخواست از URL http://localhost/php7/
SERVER_ADMIN
آدرس ایمیل مدیر وب‌سرور
SCRIPT_FILENAME
مسیر کامل اسکریپت سرور
REMOTE_PORT
پورت مورد استفاده کلاینت برای ارسال درخواست
SERVER_PROTOCOL
نسخه پروتکل HTTP مورد استفاده توسط وب سرور
REQUEST_METHOD
فرمان HTTP که توسط کلاینت استفاده می‌شود. چهار مورد از آن‌ها وجود دارد: GET، POST، PUT، DELETE
QUERY_STRING
پارامترها ارسال شده با فرمان GET /url?parameters
REQUEST_URI
URL درخواستی توسط کلاینت. اگر مرورگر URL http://machine[:port]/uri را درخواست کند، ما REQUEST_URI=uri خواهیم داشت
SCRIPT_NAME
$_SERVER['SCRIPT_FILENAME']=$_SERVER['DOCUMENT_ROOT'].$_SERVER['SCRIPT_NAME']

17.9. بازیابی پارامترهای ارسال‌شده توسط کلاینت توسط سرور

17.9.1. مقدمه

در پروتکل HTTP، یک کلاینت دو روش برای ارسال پارامترها به سرور WEB دارد:

  • آنها سرویس URL را به صورت درخواست می‌کنند

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

که در آن مقادیر vali باید ابتدا کدگذاری شوند تا برخی کاراکترهای رزرو شده با مقادیر هگزادسیمال خود جایگزین شوند؛

  • این درخواست URL سرویس را به شکل زیر درخواست می‌کند

POST url HTTP/1.0

سپس، در میان سربرگ‌های HTTP که به سرور ارسال می‌شوند، شامل سربرگ زیر است:


Content-length=N

سربرگ‌های باقی‌مانده‌ای که توسط کلاینت ارسال می‌شوند با یک خط خالی پایان می‌یابند. سپس می‌تواند داده‌های خود را به صورت زیر ارسال کند


val1&param2=val2&param3=val3…

که مقادیر vali باید، مانند روش GET، از قبل کدگذاری شوند. تعداد کاراکترهای ارسال‌شده به سرور باید N باشد، که N مقدار اعلام‌شده در هدر است


Content-length=N

اسکریپت PHP سرویس وب، که پارامترهای قبلی parami ارسال‌شده توسط کلاینت را بازیابی می‌کند، مقادیر آن‌ها را از آرایه دریافت می‌کند:

  • $_GET["parami"] برای یک فرمان GET;
  • $_POST["parami"] برای یک فرمان POST؛

این موضوع برای توابع پایه PHP صدق می‌کند. اگر از کتابخانه [HttpFoundation] استفاده شود، این پارامترها را می‌توان در یافت:

  • [Request]->query->get('parami') برای یک فرمان GET;
  • [Request]->request->get('parami') برای یک فرمان POST؛

که در آن [Request] نمایانگر تمام اطلاعات مربوط به درخواستی است که توسط اسکریپت وب دریافت شده است؛

17.9.2. کلاینت GET – نسخه ۱

Image

اسکریپت‌های کلاینت توسط فایل زیر پیکربندی شده‌اند: jSON [config-parameters-client.json]:

1
2
3
4
{
    "url-get": "http://localhost/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost/php7/scripts-web/05/parameters-server.php"
}
  • خط ۱: URL اسکریپت وب هدف برای مشتریان GET;
  • خط ۲: اسکریپت وب URL که توسط کلاینت POST هدف‌گیری شده است؛

کلاینت‌های GET سه پارامتر [nom, prenom, age] را به سرور ارسال می‌کنند. کلاینت [parameters-get-client.php] به شرح زیر است:


<?php

// GET کلاینت یک سرور وب
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-parameters-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();

try {
  // آماده‌سازی تنظیمات
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
//رمزگذاری اطلاعات
  $parameters = "prenom=" . urlencode($prenom) .
    "&nom=" . urlencode($nom) .
    "&age=$age”;
  //درخواست ارسال می‌شود
  $response = $httpClient->request('GET', $config['url-get'] . "?$parameters");
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  //بازیابی سربرگ‌ها
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // نمایش پاسخ سرور
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  //خطا را نمایش می‌دهد
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

توضیحات

  • خطوط ۳۳–۳۵: رمزگذاری پارامترهای ارسال‌شده به سرور. پارامترهای [$prenom, $nom]، که ممکن است حاوی کاراکترهای UTF-8 باشند، با استفاده از تابع [urlencode] رمزگذاری می‌شوند. تمام کاراکترهای غیرالفبایی (طبق تعریف در عبارت‌های رابطه‌ای) با %xx جایگزین می‌شوند، که در آن xx مقدار هگزادسیمال آن کاراکتر است. فاصله‌ها با علامت + جایگزین می‌شوند؛
  • خط ۳۷: URL درخواستی، $URL?$parameters است، که در آن $parameters به شکل nom=val1&prenom=val2&age=val3 است؛
  • خط ۴۸: کلاینت به سادگی پاسخ سرور را نمایش خواهد داد؛

ممکن است کنجکاو باشید ببینید وقتی یک درخواست GET ارسال می‌شود، سرور چه چیزی دریافت می‌کند. برای این کار، سرور عمومی خود [RawTcpServer] را از یک ترمینال Laragon روی پورت ۱۰۰ ماشین محلی راه‌اندازی می‌کنیم (به بخش «لینک» مراجعه کنید):

Image

اطمینان حاصل کنید که در [4]، واقعاً در پوشه utilities هستید.

ما فایل jSON [parameters-get-client.json] را ویرایش خواهیم کرد که کلاینت‌های GET و POST را پیکربندی می‌کند:


{
    "url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost/php7/scripts-web/05/parameters-server.php"
}
  • خط ۲: ما پورت وب سرور را تغییر داده‌ایم. بنابراین با [RawTcpServer] تماس گرفته خواهد شد؛

ما کلاینت را اجرا می‌کنیم. در پنجره [RawTcpServer]، اطلاعات زیر را مشاهده می‌کنیم:

Image

  • در [1]، فرمان پیکربندی‌شده GET که توسط کلاینت ارسال شده است را می‌بینیم. می‌توانیم به‌وضوح رمزگذاری برخی از کاراکترها را ببینیم؛

17.9.3. سرور GET / POST

Image

اسکریپت سرور [parameters-server.php] به شرح زیر است:


<?php

//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;

// پرس‌وجو را بازیابی می‌کند
$request = Request::createFromGlobals();
// پارامترهای درخواست را بازیابی می‌کند
$getParameters = $request->query->all();
$bodyParameters = $request->request->all();

//پاسخ تولید می‌شود
$response = new Response();
//محتوای پاسخ متن UTF-8 است
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
//محتوای پاسخ – جدولی رمزگذاری‌شده در jSON
$response->setContent(json_encode([
  "method" => $request->getMethod(),
  "uri" => $request->getRequestUri(),
  "getParameters" => $getParameters,
  "bodyParameters" => $bodyParameters
    ], JSON_UNESCAPED_UNICODE));
//ارسال پاسخ
$response->send();

نظرات

  • خط ۹: ایجاد شی [Request] از اسکریپت وب. این شی تمام اطلاعاتی را که اسکریپت وب از کلاینت دریافت کرده است در خود جای می‌دهد؛
  • خط ۱۱: شیء [Request→query] از نوع [ParameterBag] است و شامل پارامترهای هر عملیات GET انجام‌شده توسط یک کلاینت می‌باشد. بیان [Request→query→getX»)] پارامتر با نام X را از میان پارامترهای GET و [nom=val1&prenom=val2&age=val3] بازیابی می‌کند. بیان [Request→query→all()] فرهنگ لغت پارامترها را برای GET بازیابی می‌کند؛
  • خط ۱۲: شیء [Request→request] از نوع [ParameterBag] است و شامل پارامترهایی است که به عنوان یک سند از سمت کلاینت به سرور ارسال شده‌اند. این پارامترها همچنین به دلیل اینکه بخشی از سندی را تشکیل می‌دهند که کلاینت برای سرور ارسال می‌کند، «بارگذاری شده» (uploaded) نیز نامیده می‌شوند. بیان [Request→request→getX»)] پارامتر با نام X را از میان پارامترهای ارسال‌شده [nom=val1&prenom=val2&age=val3] بازیابی می‌کند. بیان [Request→request→all()] فرهنگ لغت پارامترهای ارسال‌شده را بازیابی می‌کند؛
  • خطوط ۱۷–۱۸: به کلاینت اطلاع داده می‌شود که jSON در UTF-8 رمزگذاری شده برای او ارسال خواهد شد؛
  • خطوط ۲۰–۲۵: سرور تمام پارامترهایی را که از کلاینت دریافت کرده است، به همراه نوع عملیاتی ([GET / POST / …]) که توسط کلاینت انجام شده و URI درخواستی، به کلاینت بازمی‌گرداند. این روش از طریق عبارت [$request→getMethod()] به دست می‌آید. سند ارسالی به کلاینت، رشته jSON است که یک آرایهٔ انجمنی (associative array) است و برخی از مقادیر آن خود آرایه‌های انجمنی هستند. پارامتر [JSON_UNESCAPED_UNICODE] مشخص می‌کند که کاراکترهای یونیکد (مانند کاراکترهای دارای علامت diakritical، برای مثال) باید همان‌طور که هستند ارسال شوند و نه اینکه رمزگذاری (encoded) شوند؛
  • خط ۲۷: پاسخ به کلاینت ارسال می‌شود؛

اجرای اسکریپت سمت کلاینت نتایج زیر را تولید می‌کند:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 10:08:45 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 207
connection: close
content-type: application/json
---Réponse du serveur [{"method":"GET","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom=jean-paul&nom=de+la+h%C3%BBche&age=45","getParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"},"bodyParameters":[]}]
  • خط ۱۰:
    • [method]: متد GET است؛
    • [uri]: پارامترهای رمزگذاری‌شده با URL درخواست GET در URI درخواست‌شده قابل مشاهده هستند؛
    • [getParameters]: آرایه پارامترها از GET;
    • [bodyParameters]: آرایه پارامترهای بارگذاری‌شده: خالی است؛

17.9.4. کلاینت GET – نسخهٔ ۲

در نسخه قبلی اسکریپت کلاینت، ما برای اهداف آموزشی، پارامترهای ارسال‌شده به سرور را خودمان با URL-encode رمزگذاری می‌کردیم. شیء [HttpClient] می‌تواند این کار را خودش انجام دهد. این اسکریپت [parameters-get-client-2.php] زیر است:


<?php

// GET مشتری یک سرور وب
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-parameters-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();
try {
  // آماده‌سازی تنظیمات
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  //ارسال درخواست به سرور
  $response = $httpClient->request('GET', $config['url-get'],
    ["query" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
  ]]);
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // بازیابی سربرگ‌ها
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  //نمایش پاسخ سرور
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  //نمایش خطا
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

توضیحات

  • خطوط ۳۳–۳۷: افزودن پارامترها به درخواست GET در خط ۳۲. شیء [HttpClient] خود رمزگذاری URL را مدیریت خواهد کرد؛

17.9.5. کلاینت POST

یک کلاینت HTTP توالی متنی زیر را به سرور وب ارسال می‌کند: HTTP سرورها، خط خالی، سند. در کلاینت قبلی، این توالی به شرح زیر بود:

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

هیچ سندی وجود نداشت. روش دیگری برای ارسال پارامترها وجود دارد که به آن POST گفته می‌شود. در این حالت، توالی متنی که به سرور وب ارسال می‌شود به شرح زیر است:

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

این بار، پارامترهایی که برای کلاینت GET در هدرها (HTTP) گنجانده شده بودند، در کلاینت POST بخشی از سند ارسال‌شده پس از هدرها را تشکیل می‌دهند.

اسکریپت برای کلاینت POST [parameters-postclient.php] به شرح زیر است:


<?php

// POST کلاینت یک سرور وب
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-parameters-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

//ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();
try {
  // آماده‌سازی تنظیمات
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  //ارسال درخواست به سرور
  $response = $httpClient->request('POST', $config['url-post'],
    ["body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
  ]]);
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // بازیابی سربرگ‌ها
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  //نمایش پاسخ سرور
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  //نمایش خطا
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}
  • خط ۳۲: اکنون یک درخواست HTTP از نوع POST داریم؛
  • خطوط ۳۳–۳۷: پارامترهای POST به عنوان بدنه درخواست POST ارجاع داده می‌شوند: این سند ارسال شده توسط کلاینت به سرور است. در اینجا، سه پارامتر ارسال می‌شوند: [nom, prenom, age];
  • خط ۴۸: پاسخ سرور jSON نمایش داده می‌شود؛

نتایج اجرای اسکریپت کلاینت به شرح زیر است:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 11:43:02 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 163
connection: close
content-type: application/json
---Réponse du serveur [{"method":"POST","uri":"\/php7\/scripts-web\/05\/parameters-server.php","getParameters":[],"bodyParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"}}]
  • خط ۱۰: متد [Post] است و پارامترها از نوع [bodyParameters] هستند. هیچ پارامتر [getParameters] وجود ندارد، همانطور که توسط [uri] نشان داده شده است؛

ممکن است کنجکاو باشید ببینید سرور در طول یک درخواست POST چه چیزی دریافت می‌کند. برای این کار، سرور عمومی [RawTcpServer] خود را از طریق ترمینال Laragon روی پورت ۱۰۰ ماشین محلی راه‌اندازی می‌کنیم (به بخش «لینک» مراجعه کنید):

Image

اطمینان حاصل کنید که در [4]، واقعاً در پوشهٔ utilities هستید.

ما فایل jSON [config-parameters-client.json] را ویرایش خواهیم کرد که کلاینت POST را پیکربندی می‌کند:


{
    "url-get": "http://localhost:100/php7/scripts-web/05/parameters-server.php",
    "url-post": "http://localhost:100/php7/scripts-web/05/parameters-server.php"
}
  • خط ۳: ما پورت وب‌سرور را تغییر داده‌ایم. بنابراین با [RawTcpServer] تماس گرفته خواهد شد؛

ما کلاینت را اجرا می‌کنیم. در پنجره [RawTcpServer]، اطلاعات زیر را مشاهده می‌کنیم:

Image

  • در [6]، فرمان POST;
  • در [7]: هدر HTTP [Content-Length] تعداد بایت‌های سند را که کلاینت به سرور ارسال خواهد کرد، مشخص می‌کند. سربرگ HTTP [Content-Type] ماهیت این سند را مشخص می‌کند. نوع [application/x-www-form-urlencoded] نشان‌دهنده متن رمزگذاری‌شده با URL است؛
  • در [8]، خط خالی که پایان سربرگ‌ها HTTP و شروع سند ۴۴ بایتی را نشان می‌دهد. آنچه در اسکرین‌شات نشان داده نمی‌شود خود سند است. این رشته پارامترهای رمزگذاری‌شده با URL است: [prenom=jean-paul&nom=de+la+h%C3%BBche&age=45]. خواننده می‌تواند تأیید کند که این رشته واقعاً ۴۴ کاراکتر دارد؛

17.9.6. یک کلاینت ترکیبی POST

در یک POST، می‌توان پارامترهای رمزگذاری‌شده در URL را با پارامترهای رمزگذاری‌شده در مستندی که توسط کلاینت پس از سربرگ‌های HTTP ارسال می‌شود، ترکیب کرد. در اینجا یک مثال آمده است: [parameters-mixte-postclient.php]:


<?php

// POST  کلاینت یک سرور وب
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-parameters-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();
try {
  // آماده‌سازی تنظیمات
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  //ارسال درخواست به سرور
  $response = $httpClient->request('POST', $config['url-post'],
    [
      //پارامترهای سند (بدنه)
      "body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
      ],
      // URL پارامترهای (query)
      "query" => [
        "prenom2" => $prenom,
        "nom2" => $nom,
        "age2" => $age
  ]]);
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // بازیابی سربرگ‌ها
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  //نمایش پاسخ سرور
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  //نمایش خطا
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

نظرات

  • خط ۳۲: یک درخواست POST؛
  • خطوط ۴۰–۴۵: پارامترهای رمزگذاری‌شده با URL در URL;
  • خطوط ۳۵–۳۹: پارامترهای رمزگذاری‌شده با URL در بدنه (سند) درخواست؛

پس از اجرا، خروجی کنسول زیر تولید می‌شود:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 12:34:23 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 270
connection: close
content-type: application/json
---Réponse du serveur [{"method":"POST","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom2=jean-paul&nom2=de%20la%20h%C3%BBche&age2=45","getParameters":{"prenom2":"jean-paul","nom2":"de la hûche","age2":"45"},"bodyParameters":{"prenom":"jean-paul","nom":"de la hûche","age":"45"}}]
  • خط ۱۰: می‌بینیم که سرور توانسته است هر دو نوع پارامتر را بازیابی کند؛

17.9.7. یک کلاینت ترکیبی GET

ما سعی می‌کنیم همانند قبل با یک درخواست GET همین کار را انجام دهیم. اسکریپت [parameters-mixte-get-client.php] به شرح زیر است:


<?php

// POST کلاینت یک سرور وب
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-parameters-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

//ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();
try {
  // آماده‌سازی تنظیمات
  list($prenom, $nom, $age) = array("jean-paul", "de la hûche", 45);
  //ارسال درخواست به سرور
  $response = $httpClient->request('GET', $config['url-post'],
    [
      //پارامترهای سند (بدنه)
      "body" => [
        "prenom" => $prenom,
        "nom" => $nom,
        "age" => $age
      ],
      // URL پارامترهای (query)
      "query" => [
        "prenom2" => $prenom,
        "nom2" => $nom,
        "age2" => $age
  ]]);
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // بازیابی سربرگ‌ها
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  // نمایش پاسخ سرور
  print "---Réponse du serveur [" . $response->getContent() . "]\n";
} catch (TypeError | RuntimeException $ex) {
  //نمایش خطا
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

توضیحات

  • خط ۳۲: یک درخواست POST؛
  • خطوط ۴۰–۴۵: پارامترهای رمزگذاری‌شده با URL در URL;
  • خطوط ۳۵–۳۹: پارامترهای رمزگذاری‌شده URL در بدنه (سند) درخواست؛

پس از اجرا، خروجی کنسول زیر تولید می‌شود:

---Réponse avec statut : 200
---Entêtes de la réponse
date: Mon, 03 Jun 2019 12:41:19 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: no-cache, private
content-length: 217
connection: close
content-type: application/json
---Réponse du serveur [{"method":"GET","uri":"\/php7\/scripts-web\/05\/parameters-server.php?prenom2=jean-paul&nom2=de%20la%20h%C3%BBche&age2=45","getParameters":{"prenom2":"jean-paul","nom2":"de la hûche","age2":"45"},"bodyParameters":[]}]
  • خط ۱۰: می‌بینیم که سرور هیچ پارامتر رمزگذاری‌شده‌ی URL را در سند ارسال‌شده توسط کلاینت دریافت نکرده است. وقتی به هدرهای HTTP ارسال‌شده توسط کلاینت نگاه می‌کنیم، می‌بینیم که در واقع یک سند ۴۴ کاراکتری ارسال شده است، اما سرور آن را پردازش نکرده است؛

بنابراین، برای ارسال اطلاعات به سرور از کدام روش باید استفاده شود؟

  • روش [GET URL?param1=val1&param2=val2&…] از یک URL پیکربندی‌شده استفاده می‌کند که می‌تواند به عنوان یک لینک عمل کند. این مزیت اصلی آن است: کاربر می‌تواند چنین لینک‌هایی را در نشانک‌های خود ذخیره کند؛
  • در سایر برنامه‌ها، ممکن است نخواهید پارامترهای ارسال‌شده به سرور را در یک URL نمایش دهید. به عنوان مثال، به دلایل امنیتی. در این صورت، از یک روش [POST] استفاده کرده و پارامترهای رمزگذاری‌شده با URL را در مستندی که به سرور ارسال می‌شود، درج می‌کنید؛

17.10. مدیریت جلسه وب

در مثال‌های قبلی مشتری/سرور، فرآیند به این صورت بود:

  • کلاینت یک اتصال به پورت ۸۰ روی ماشین سرویس وب برقرار می‌کند؛
  • آن رشته متنی را ارسال می‌کند: سربرگ‌های HTTP، خط خالی، [document
  • در پاسخ، سرور توالی‌ای از همان نوع را ارسال می‌کند؛
  • سرور اتصال را با کلاینت قطع می‌کند؛
  • کلاینت اتصال به سرور را قطع می‌کند؛

اگر همان کلاینت اندکی بعد درخواست جدیدی به وب سرور ارسال کند، یک اتصال جدید بین کلاینت و سرور برقرار می‌شود. سرور نمی‌تواند تشخیص دهد که آیا کلاینت متصل‌شونده قبلاً بازدید کرده است یا اینکه این اولین درخواست اوست. بین اتصالات، سرور کلاینت خود را «فراموش می‌کند». به همین دلیل، پروتکل HTTP یک پروتکل بدون حالت (stateless) نامیده می‌شود. با این حال، به یاد داشتن مشتریان برای سرور مفید است. به عنوان مثال، اگر یک برنامه امن باشد، مشتری برای احراز هویت خود، نام کاربری و رمز عبور را برای سرور ارسال می‌کند. اگر سرور بین اتصالات، مشتری خود را «فراموش» کند، مشتری مجبور است در هر اتصال جدید، خود را احراز هویت کند که این امر امکان‌پذیر نیست.

برای ردیابی یک کلاینت، سرور به شرح زیر عمل می‌کند: هنگامی که کلاینت یک درخواست اولیه ارسال می‌کند، سرور یک شناسه را در پاسخ خود قرار می‌دهد که کلاینت باید آن را در هر درخواست بعدی بازگرداند. به لطف این شناسه که برای هر کلاینت منحصربه‌فرد است، سرور می‌تواند کلاینت را تشخیص دهد. سپس می‌تواند یک رکورد برای آن کلاینت به شکل یک ورودی حافظه که به طور منحصربه‌فرد با شناسه کلاینت مرتبط است، حفظ کند.

از نظر فنی، این به شرح زیر عمل می‌کند:

  • در پاسخ خود به یک مشتری جدید، سرور هدر HTTP را با مقدار Set-Cookie: MotClé=Identifier درج می‌کند. این کار را فقط در اولین درخواست انجام می‌دهد؛
  • در درخواست‌های بعدی، کلاینت شناسه‌ی خود را از طریق هدر HTTP Cookie: MotClé=Identifier ارسال می‌کند تا سرور بتواند آن را تشخیص دهد؛

ممکن است این سؤال پیش بیاید که سرور چگونه می‌داند در حال تعامل با یک کلاینت جدید است و نه یک کلاینت بازگشتی. حضور هدر HTTP Cookie در میان هدرهای HTTP کلاینت است که این موضوع را به سرور نشان می‌دهد. برای یک کلاینت جدید، این هدر وجود ندارد.

تمام اتصالات انجام‌شده توسط یک مشتری مشخص، یک جلسه (session) نامیده می‌شوند.

17.10.1. فایل پیکربندی [php.ini]

برای اطمینان از اینکه مدیریت جلسه با PHP به درستی کار می‌کند، باید بررسی کنید که پیکربندی آن به درستی انجام شده است. در ویندوز، فایل پیکربندی آن php.ini است. بسته به زمینهٔ اجرا (کنسول، وب)، فایل پیکربندی [php.ini] باید در پوشه‌های متفاوتی قرار گیرد. برای شناسایی این پوشه‌ها، از اسکریپت زیر استفاده کنید:

1
2
3
4
<?php

// اطلاعات PHP
phpinfo();

در خط ۴، تابع phpinfo اطلاعاتی در مورد تفسیرگر PHP که اسکریپت را اجرا می‌کند، ارائه می‌دهد. به طور خاص، این تابع مسیر فایل پیکربندی [php.ini] در حال استفاده را ارائه می‌کند.

ما قبلاً از این اسکریپت در یک محیط کنسول استفاده کرده‌ایم (به بخش «لینک» مراجعه کنید). در یک محیط وب، نتیجه زیر به دست می‌آید:

Image

  • در [1-2]، فایل [php.ini] که مفسر اسکریپت وب را پیکربندی می‌کند. این فایل شامل یک بخش «session» است:
[Session]
session.save_handler = files
session.save_path = "C:/myprograms/laragon-lite/tmp"
session.use_strict_mode = 0
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 36000
session.referer_check =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.trans_sid_tags = "a=href,area=href,frame=src,form="
session.sid_bits_per_character = 5
  • خط ۲: داده‌های جلسه کلاینت در یک فایل ذخیره می‌شود؛
  • خط ۳: پوشه‌ای که داده‌های جلسه در آن ذخیره می‌شود. اگر این پوشه وجود نداشته باشد، هیچ خطایی گزارش نمی‌شود و مدیریت جلسه کار نمی‌کند؛
  • خطوط ۴–۶: نشان می‌دهند که شناسه جلسه توسط سربرگ‌های HTTP، Set-Cookie و Cookie مدیریت می‌شود؛
  • خط ۷: هدر Set-Cookie به شکل Set-Cookie: PHPSESSID=identifiant_de_session; خواهد بود؛
  • خط ۸: یک جلسهٔ مشتری به‌طور خودکار آغاز نمی‌شود. اسکریپت سرور باید صراحتاً با استفاده از دستور session_start() آن را درخواست کند؛
  • خط ۹: کوکی جلسه تا زمانی که مرورگر مشتری بسته شود معتبر باقی می‌ماند؛
  • خط ۱۰: مسیری که کوکی جلسه باید برای آن ارسال شود. اگر [session.cookie_path = /xxx] باشد، در این صورت هر زمان که مرورگر یک URL از نوع [/xxx/yyy/zzz] را درخواست می‌کند، باید کوکی را ارسال کند. در اینجا، مسیر [/] نشان می‌دهد که کوکی باید برای هر URL در سایت ارسال شود؛
  • خط ۱۳: برخی اشیاء جلسه باید سریالیزه شوند تا در یک فایل ذخیره شوند. PHP این سریالیزاسیون و دسریالیزاسیون را با استفاده از توابع [serialize / unserialize] مدیریت می‌کند؛
  • خط ۱۶: مدت زمانی که پس از آن اشیاء جلسه ذخیره شده در فایل پشتیبان منسوخ تلقی می‌شوند؛
  • خط ۱۹: طول عمر یک جلسه. پس از پایان این دوره، یک جلسه جدید ایجاد می‌شود و اشیاء ذخیره شده در جلسه قبلی از بین می‌روند؛

17.10.2. مثال ۱

17.10.2.1. سرور

Image

مدیریت شناسه جلسه برای سرویس وب شفاف است. این شناسه توسط وب‌سرور مدیریت می‌شود. یک سرویس وب می‌تواند از طریق عبارت session_start() به جلسه مشتری دسترسی پیدا کند. از این نقطه به بعد، سرویس وب می‌تواند از طریق دیکشنری $_SESSION از جلسه مشتری بخواند و در آن بنویسد. اگر از کتابخانه [HttpFoundation] استفاده شود، جلسه از طریق عبارت [Request→getSession] در دسترس است.

کد زیر، [session-server.php مدیریت مبتنی بر جلسه سه شمارنده را نشان می‌دهد. با هر درخواست جدید، اسکریپت وب این شمارنده‌ها را افزایش داده و آن‌ها را در جلسه ذخیره می‌کند تا در درخواست بعدی قابل بازیابی باشند.


<?php
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

//
// درخواست را بازیابی می‌کند
$request = Request::createFromGlobals();
// جلسه
$session = new Session();
$session->start();
// بازیابی سه شمارنده از جلسه
if ($session->has("N1")) {
  // شمارنده N1 را افزایش دهید
  $session->set("N1", (int) $session->get("N1") + 1);
} else {
  //شمارنده N1 در جلسه وجود ندارد – آن را ایجاد کنید
  $session->set("N1", 0);
}
if ($session->has("N2")) {
  //افزایش شمارنده N2
  $session->set("N2", (int) $session->get("N2") + 1);
} else {
  // شمارنده N2 در جلسه نیست – در حال ایجاد است
  $session->set("N2", 10);
}
if ($session->has("N3")) {
  //افزایش شمارنده N3
  $session->set("N3", (int) $session->get("N3") + 1);
} else {
  // شمارنده N3 در حال حاضر فعال نیست – در حال ایجاد است
  $session->set("N3", 100);
}
//پاسخ در حال تولید است
$response = new Response();
//محتوای پاسخ متن UTF-8 است
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
//پاسخ jSON خواهد بود، آرایه‌ای شامل سه شمارنده
$response->setContent(json_encode([
  "N1" => $session->get("N1"),
  "N2" => $session->get("N2"),
  "N3" => $session->get("N3")]));

//ارسال پاسخ
$response->send();
  • خط ۱۰: شیء [$request] تمام اطلاعات مربوط به درخواست دریافت‌شده توسط اسکریپت وب را در خود جای می‌دهد؛
  • خطوط ۱۲–۱۳: یک جلسه ایجاد و فعال می‌شود. شیء [Session] داده‌های جلسه را که مربوط به کوکی جلسه ارسال‌شده توسط کلاینت است، در خود جای می‌دهد. اگر کلاینت چنین کوکی‌ای را ارسال نکرده باشد، آنگاه هیچ داده‌ای در [Session] ذخیره نمی‌شود. اسکریپت وب در اولین پاسخ خود، هدر HTTP [Set-Cookie : PHPSESSID=xxx] را درج خواهد کرد. در درخواست‌های بعدی، کلاینت هدر HTTP [Cookie : PHPSESSID=xxx] را ارسال می‌کند تا نشانه سشن مورد نظر خود را مشخص کند. سشن حافظه کلاینت است؛
  • خط ۱۵: بررسی می‌کنیم که آیا جلسه کلیدی با نام [N1] دارد. این نام اولین شمارنده ما خواهد بود. اگر اینطور نباشد (خط ۲۰)، مقدار آن را روی ۰ تنظیم کرده و در جلسه ذخیره می‌کنیم. اگر اینطور باشد (خط ۲۳)، ما:
    • آن را از جلسه بازیابی می‌کنیم؛
    • ارزش آن را ۱ افزایش می‌دهیم؛
    • آن را دوباره در جلسه قرار می‌دهیم؛
  • خطوط ۲۲–۳۵: همین کار را برای دو شمارنده دیگر، N2 و N3، انجام می‌دهیم؛
  • خطوط ۳۶–۴۰: یک پاسخ از نوع [application/json] آماده می‌کنیم؛
  • خطوط ۴۲–۴۵: پاسخ، رشته jSON از آرایه‌ای حاوی سه شمارنده خواهد بود؛
  • خط ۴۸: پاسخ به کلاینت ارسال می‌شود؛

در رابطهٔ کلاینت/سرور، مدیریت جلسهٔ کلاینت بر روی سرور به هر دو طرف، کلاینت و سرور، بستگی دارد:

  • سرور مسئول ارسال یک شناسگر به کلاینت در اولین درخواست آن است
  • کلاینت مسئول بازگرداندن این شناسه در هر درخواست جدید است. اگر این کار را انجام ندهد، سرور فرض می‌کند که کلاینت جدید است و برای یک جلسه جدید شناسه جدیدی تولید می‌کند.

نتایج

ما از یک مرورگر وب به‌عنوان کلاینت استفاده می‌کنیم. به‌طور پیش‌فرض (یا بهتر است بگوییم، بر اساس پیکربندی)، مرورگر در واقع شناسه‌های جلسه‌ای را که سرور برایش ارسال می‌کند، به سرور بازمی‌گرداند. با ارسال درخواست‌ها، مرورگر سه شمارنده ارسالی از سوی سرور را دریافت می‌کند و شاهد افزایش مقادیر آن‌ها خواهد بود.

Image

  • در [2]، اولین درخواست به سرویس وب؛
  • در [4]، چهارمین درخواست نشان می‌دهد که شمارنده‌ها واقعاً افزایش یافته‌اند. مقادیر شمارنده‌ها واقعاً در طول درخواست‌ها حفظ می‌شوند؛

بیایید از حالت توسعه‌دهنده برای مشاهده سربرگ‌های مبادله شده بین سرور و کلاینت در HTTP استفاده کنیم. فایرفاکس را می‌بندیم تا جلسهٔ جاری با سرور پایان یابد، آن را دوباره باز می‌کنیم و حالت توسعه‌دهنده را فعال می‌کنیم (F12). این کار جلسهٔ جاری مرورگر را پاک می‌کند و باعث می‌شود یک جلسهٔ جدید آغاز شود. ما درخواستی به سرویس [session-server.php] ارسال می‌کنیم:

Image

در [5]، می‌توانیم شناسه جلسه را که توسط سرور در پاسخ به اولین درخواست کلاینت ارسال شده است، مشاهده کنیم. این شناسه از هدر HTTP Set-Cookie استفاده می‌کند.

بیایید با تازه‌سازی (F5) صفحه در مرورگر وب، یک درخواست جدید ارسال کنیم:

Image

دو نکته در اینجا قابل توجه است:

  • در [11]، مرورگر وب شناسه جلسه را با هدرهای HTTP و Cookie بازمی‌گرداند.
  • در [12]، سرویس وب دیگر این شناسه را در پاسخ خود شامل نمی‌کند. اکنون مسئولیت ارسال آن با هر درخواست بر عهده کلاینت است.

17.10.2.2. کلاینت

ما اکنون یک اسکریپت سمت کلاینت را بر اساس اسکریپت سمت سرور قبلی می‌نویسیم. از نظر مدیریت جلسه، این اسکریپت باید مانند یک مرورگر وب رفتار کند:

  • در پاسخ سرور به اولین درخواستش، او باید شناسهٔ جلسه را که سرور برایش ارسال می‌کند، پیدا کند. او می‌داند که آن را در هدر HTTP Set-Cookie پیدا خواهد کرد.
  • در هر درخواست بعدی، باید شناسهٔ جلسه را که دریافت کرده است به سرور بازگرداند. او این کار را با استفاده از هدر HTTP Cookie انجام خواهد داد.

Image

کلاینت [session-client] توسط فایل زیر پیکربندی شده است: jSON [config-session-client.json]:

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

کد کلاینت برای [session-client] به شرح زیر است:


<?php

//مدیریت جلسه
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی کلاینت
const CONFIG_FILE_NAME = "config-session-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

//ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create();
try {
  // ما قصد داریم ۱۰ درخواست ارسال کنیم
  for ($i = 0; $i < 10; $i++) {
    //ارسال درخواست به سرور
    if (!isset($sessionCookie)) {
      //بدون جلسه
      $response = $httpClient->request('GET', $config['url']);
    } else {
      // با یک جلسه
      $response = $httpClient->request('GET', $config['url'],
        ["headers" => ["Cookie" => $sessionCookie]]);
    }
    //وضعیت پاسخ
    $statusCode = $response->getStatusCode();
    print "---Réponse avec statut : $statusCode\n";
    // بازیابی هدرها
    print "---Entêtes de la réponse\n";
    $headers = $response->getHeaders();
    foreach ($headers as $type => $value) {
      print "$type: " . $value[0] . "\n";
    }
    // در صورت وجود، کوکی جلسه را بازیابی کنید
    if (isset($headers["set-cookie"])) {
      // کوکی جلسه؟
      foreach ($headers["set-cookie"] as $cookie) {
        $match = [];
        $match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
        if ($match) {
          $sessionCookie = "PHPSESSID=" . $champs[1];
        }
      }
    }
  }
  // نمایش پاسخ سرور jSON
  print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
  // نمایش خطا
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}


توضیحات

  • خط ۲۷: ایجاد کلاینت HTTP;
  • خط ۳۰: همان درخواست ۱۰ بار به سرور [session-server.php] ارسال خواهد شد؛
  • خط ۳۲: متغیر [$sessionCookie] به مقدار هدر HTTP [Set-Cookie] که توسط کلاینت دریافت شده است، تنظیم خواهد شد؛
  • خطوط ۳۲–۳۴: اگر این متغیر وجود نداشته باشد، به این معنی است که جلسه هنوز شروع نشده است. فرمان [GET] بدون سربرگ [Cookie] ارسال می‌شود؛
  • خطوط ۳۵–۳۸: در غیر این صورت، جلسه آغاز شده است و فرمان [GET] با سربرگ [Cookie] ارسال می‌شود. مقدار این سربرگ [$sessionCookie] خواهد بود؛
  • خط ۵۰: اگر هدر [Set-Cookie] در میان هدرهای HTTP دریافتی باشد، آنگاه کوکی جلسه بازیابی می‌شود؛
  • خط ۵۲: وب‌سرور ممکن است چندین هدر [Set-Cookie] ارسال کند. کوکی جلسه تنها یکی از آن‌هاست. در مثال ما، این کوکی به طور خاص در قالب [PHPSESSID=xxx;] است؛
  • خطوط ۵۳–۵۷: از یک عبارت منظم برای یافتن کوکی جلسه استفاده می‌شود؛
  • خط ۶۲: پس از ارسال ۱۰ درخواست، پاسخ نهایی سرور، jSON، نمایش داده می‌شود؛

نتایج

اجرای اسکریپت سمت کلاینت باعث می‌شود موارد زیر در کنسول NetBeans نمایش داده شود:


"C:\myprograms\laragon-lite\bin\php\php-7.2.11-Win32-VC15-x64\php.exe" "C:\Data\st-2019\dev\php7\poly\scripts-console\clients web\06\session-client.php"
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
set-cookie: PHPSESSID=1cerjgsgdlc35e1mkenvtltmh8; path=/
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse avec statut : 200
…………………………………………………………
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 13:41:34 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 25
connection: close
content-type: application/json
---Réponse du serveur : {"N1":9,"N2":19,"N3":109}
  • خط ۸: در اولین پاسخ، سرور شناسه جلسه را ارسال می‌کند. در پاسخ‌های بعدی، دیگر آن را ارسال نمی‌کند؛
  • خط ۴۱: سه شمارنده [N1, N2, N3] در واقع ۹ بار افزایش یافته‌اند. در طول درخواست شماره ۱، آنها به صفر بازنشانی شدند؛

مثال زیر نشان می‌دهد که شما می‌توانید مقادیر یک آرایه یا یک شیء را نیز در جلسه ذخیره کنید.

17.10.3. مثال ۲

17.10.3.1. سرور

Image

ما قصد داریم یک شیء [Personne] را در جلسه قرار دهیم. تعریف این کلاس به شرح زیر است:


<?php

namespace Modèles;

class Personne implements \JsonSerializable {
  //ویژگی‌ها
  private $nom;
  private $prénom;
  private $âge;

  //تبدیل یک آرایهٔ انجمنی به یک شیء [Personne]
  public function setFromArray(array $assoc): Personne {
    // شیء فعلی با آرایهٔ asociative مقداردهی اولیه می‌شود
    foreach ($assoc as $attribute => $value) {
      $this->$attribute = $value;
    }
    // نتیجه
    return $this;
  }

  // گیرنده و تنظیم‌کننده
  public function getNom() {
    return $this->nom;
  }

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

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

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

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

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

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

  // رابط JsonSerializable را پیاده‌سازی می‌کند
  public function jsonSerialize(): array {
    //یک آرایهٔ انجمنی را بازمی‌گرداند که ویژگی‌های شیء به‌عنوان کلیدها هستند
    // این آرایه سپس می‌تواند به‌عنوان jSON رمزگذاری شود
    return get_object_vars($this);
  }

  //تبدیل jSON به یک شیء [Personne]
  public static function jsonUnserialize(string $json): Personne {
    //یک شخص از رشته jSON ایجاد می‌شود
    return (new Personne())->setFromArray(json_decode($json, true));
  }

}

اسکریپت سرور به شرح زیر خواهد بود:


<?php

//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
require_once __DIR__ . "/Personne.php";
use \Modèles\Personne;

//
//بازیابی پرس‌وجوی فعلی
$request = Request::createFromGlobals();

// جلسه
$session = new Session();
$session->start();

// استخراج داده‌های مختلف از جلسه
// آرایه
if ($session->has("tableau")) {
  //آرایه در جلسه قرار دارد – تمام مقادیر آن را افزایش می‌دهد
  $tableau = $session->get("tableau");
  for ($i = 0; $i < count($tableau); $i++) {
    $tableau[$i] += 1;
  }
  // آرایه دوباره در جلسه قرار داده می‌شود
  $session->set("tableau", $tableau);
} else {
  //آرایه در جلسه نیست – آن را ایجاد می‌کنیم
  $tableau = [0, 10, 100];
  // در جلسه قرار می‌گیرد
  $session->set("tableau", $tableau);
}
// فرهنگ
if ($session->has("assoc")) {
  //[assoc] در جلسه است – تمام عناصر آن افزایش می‌یابند
  $assoc = $session->get("assoc");
  foreach ($assoc as $key => $value) {
    $assoc[$key] = $value + 1;
  }
  // ما $assoc را به جلسه اضافه می‌کنیم
  $session->set("assoc", $assoc);
} else {
  //[assoc] در جلسه نیست – ایجاد می‌شود
  $assoc = ["un" => 0, "deux" => 10, "trois" => 100];
  // $assoc به جلسه اضافه می‌شود
  $session->set("assoc", $assoc);
}
// شیء Person
if ($session->has("personne")) {
  // [personne] در جلسه است – سن آن را افزایش دهید
  $personne = $session->get("personne");
  $personne->setÂge($personne->getÂge() + 1);
} else {
  // [personne] در جلسه نیست – آن را ایجاد کنید
  $personne = (new Personne())->setFromArray(
    ["prénom" => "Léonard", "nom" => "Hûche", "âge" => 0]);
  // $personne به جلسه اضافه شد
  $session->set("personne", $personne);
}
//پاسخ تولید می‌شود
$response = new Response();
//محتوای پاسخ jSON در UTF-8 است
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
$response->setContent(json_encode([
  "tableau" => $tableau,
  "assoc" => $assoc,
  "personne" => $personne], JSON_UNESCAPED_UNICODE));

//پاسخ ارسال می‌شود
$response->send();

نظرات

  • خطوط ۱۶–۱۷: جلسهٔ جاری را بازیابی کرده و آن را فعال می‌کنیم؛
  • خطوط 21–34: ما یک آرایهٔ asociative [tableau] را که در جلسه ذخیره شده است، مدیریت می‌کنیم. با هر درخواست جدید، عناصر آن ۱ واحد افزایش می‌یابند؛
  • خطوط ۳۶–۴۹: ما یک آرایهٔ asociative به نام [assoc] را که در جلسه ذخیره شده است، مدیریت می‌کنیم. با هر درخواست جدید، عناصر آن ۱ واحد افزایش می‌یابند؛
  • خطوط ۵۱–۶۱: یک شیء مبتنی بر جلسه [Personne] مدیریت می‌شود. با هر درخواست جدید، سن این شخص ۱ واحد افزایش می‌یابد؛
  • خطوط ۶۲–۷۳: یک پاسخ jSON به کلاینت ارسال می‌شود: رشته jSON از یک آرایه انجمنی؛

بیایید این اسکریپت را از NetBeans اجرا کنیم. دو درخواست اول نتایج زیر را تولید می‌کنند (F5 در مرورگر برای درخواست دوم):

Image

  • می‌توانیم ببینیم که در [6-8]، تمام شمارنده‌ها افزایش یافته‌اند؛

17.10.3.2. کلاینت

Image

مشتری همانند مثال ۱ (پاراگراف لینک) است. ما فقط فایل پیکربندی آن را تغییر می‌دهیم، [config-session-client]:


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

اجرای اسکریپت نتایج زیر را تولید می‌کند:


---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 14:25:24 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
set-cookie: PHPSESSID=qbfrj8clr20mod3eriur71mao6; path=/
content-length: 119
connection: close
content-type: application/json
---Réponse avec statut : 200
………….……………………………………………………….
---Réponse avec statut : 200
---Entêtes de la réponse
date: Tue, 04 Jun 2019 14:25:24 GMT
server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
x-powered-by: PHP/7.2.11
cache-control: max-age=0, private, must-revalidate
content-length: 119
connection: close
content-type: application/json
---Réponse du serveur : {"tableau":[9,19,109],"assoc":{"un":9,"deux":19,"trois":109},"personne":{"nom":"Hûche","prénom":"Léonard","âge":9}}
  • در خط [22]، می‌توانیم ببینیم که تمام شمارنده‌ها افزایش یافته‌اند؛

17.11. Authentification

اکنون به سرویس‌های وبی می‌پردازیم که تنها برای کاربران خاص در نظر گرفته شده‌اند. بنابراین، کلاینت باید پیش از دریافت پاسخ، خود را نزد سرویس وب احراز هویت کند.

17.11.1. کلاینت

Image

کد کلاینت [auth-client.php] به شرح زیر است:


<?php

//مدیریت جلسه
//
// مدیریت خطا
//ini_set("error_reporting", E_ALL & ~ E_WARNING & ~E_DEPRECATED & ~E_NOTICE);
//ini_set("display_errors", "off");
//
//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;

// پیکربندی مشتری
const CONFIG_FILE_NAME = "config-auth-client.json";

//بازیابی پیکربندی
if (!file_exists(CONFIG_FILE_NAME)) {
  print "Le fichier de configuration [" . CONFIG_FILE_NAME . "] n'existe pas\n";
  exit;
}
if (!$config = \json_decode(\file_get_contents(CONFIG_FILE_NAME), true)) {
  print "Erreur lors de l'exploitation du fichier de configuration jSON [" . CONFIG_FILE_NAME . "]\n";
  exit;
}

// ایجاد یک کلاینت HTTP
$httpClient = HttpClient::create([
    'auth_basic' => ['admin', 'admin'],
    // "verify_peer" => false,
    // "QZXW2HTMLCdmVyaWZ5X2hvc3QX" => false
  ]);


try {
  //درخواست به سرور ارسال می‌شود
  $response = $httpClient->request('GET', $config['url']);
  //وضعیت پاسخ
  $statusCode = $response->getStatusCode();
  print "---Réponse avec statut : $statusCode\n";
  // بازیابی سربرگ‌ها
  print "---Entêtes de la réponse\n";
  $headers = $response->getHeaders();
  foreach ($headers as $type => $value) {
    print "$type: " . $value[0] . "\n";
  }
  //پاسخ سرور jSON نمایش داده می‌شود
  print "---Réponse du serveur : {$response->getContent()}\n";
} catch (TypeError | RuntimeException $ex) {
  // نمایش خطا
  print "Erreur de communication avec le serveur : " . $ex->getMessage() . "\n";
}

نظرات

  • خطوط ۲۷–۳۱: یک پارامتر به متد استاتیک [HttpClient::create] ارسال شده است، یعنی یک آرایهٔ asociative؛
  • خط ۲۸: کلید [auth_basic] دارای مقداری از یک آرایه دو عنصری [user, password] است. کلاینت از این عناصر برای احراز هویت با سرویس وب استفاده خواهد کرد. کلید [auth_basic] به نوع احراز هویتی به نام [Autorization Basic] اشاره دارد که نام آن برگرفته از هدر HTTP است که کلاینت ارسال خواهد کرد. انواع احراز هویت دیگری نیز وجود دارند؛
  • به جز این کد، کلاینت با نمونه‌های قبلی یکسان است؛

برای مشاهده سربرگ‌های HTTP ارسال‌شده توسط کلاینت، آن را به سرور عمومی TCP [RawTcpServer] متصل خواهیم کرد، همان‌طور که قبلاً چندین بار انجام داده‌ایم:

Image

ما کلاینت را با پیکربندی زیر [config-auth-client.json] راه‌اندازی می‌کنیم:


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

سرویس‌دهنده [RawTcpServer] سپس خطوط زیر را دریافت می‌کند:

Image

  • در [5]، می‌توانیم هدر [Autorization : Basic XXX] ارسال‌شده توسط کلاینت را ببینیم. رشته XXX، رشته [user:password] رمزگذاری‌شده با Base64 است؛

برای تأیید این موضوع، می‌توانید رشته دریافتی را در وب‌سایت [https://www.base64decode.org/] رمزگشایی کنید:

Image

17.11.2. سرور

Image

سرور [auth-server.php] به شرح زیر است:


<?php

//وابستگی‌ها
require_once 'C:/myprograms/laragon-lite/www/vendor/autoload.php';
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;

// کاربران مجاز
$users = ["admin" => "admin"];
//
//درخواست فعلی را بازیابی می‌کند
$request = Request::createFromGlobals();
//احراز هویت
$requestUser = $request->headers->get('php-auth-user');
$requestPassword = $request->headers->get('php-auth-pw');
//آیا کاربر وجود دارد؟
$trouvé = array_key_exists($requestUser, $users) && $users[$requestUser] === $requestPassword;
// آماده‌سازی پاسخ
$response = new Response();
// تنظیم کد وضعیت پاسخ
if (!$trouvé) {
  // یافت نشد – کد 401
  $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
  $response->headers->add(["WWW-Authenticate"=> "Basic realm=".utf8_decode("\"PHP7 par l'exemple\"")]);
} else {
  // یافت شد – کد 200
  $response->setStatusCode(Response::HTTP_OK);
}
// پاسخ فاقد محتوا است، فقط سرورها HTTP
$response->send();

نظرات

  • خط ۹: کاربران مجاز؛ در این مورد، یک کاربر واحد با نام کاربری [admin] و رمز عبور [admin]؛
  • خط ۱۴: شناسه کاربر از هدر [PHP-AUTH-USER] بازیابی می‌شود. این هدر توسط کلاینت ارسال نشده، بلکه توسط سرور در PHP ساخته شده است؛
  • خط ۱۵: رمز عبور کاربر از هدر [PHP-AUTH-PW] بازیابی می‌شود، هدر ساخته‌شده توسط PHP;
  • خط ۱۷: کاربر در حال تلاش برای ورود در فهرست کاربران مجاز جستجو می‌شود؛
  • خطوط ۲۳–۲۴: اگر کاربر شناسایی نشده باشد، موارد زیر به کلاینت ارسال می‌شود
    • خط ۲۳: کد [401 Unauthorized];
    • خط ۲۴: یک هدر [WWW-Authenticate: Basic realm=”quelque chose”]. اکثر مرورگرها این هدر را تشخیص می‌دهند و پنجره‌ای برای احراز هویت نمایش می‌دهند که از کاربر می‌خواهد وارد شود. سربرگ‌های HTTP باید به صورت ISO 8859-1 رمزگذاری شوند. از سوی دیگر، متن NetBeans در UTF-8 رمزگذاری شده است. تابع [utf8_decode] تبدیل از UTF-8 به ISO 8859-1 را مدیریت می‌کند. در این مورد، این کار ضروری نبود زیرا کاراکترهای رشته [PHP7 par lexemple] در هر دو UTF-8 و ISO 8859-1 یکسان هستند. این تابع تنها به‌عنوان یادآوری از رمزگذاری مورد استفاده در سربرگ‌های HTTP وجود دارد؛
  • خط ۲۵: اگر کاربر شناسایی شده باشد، کد [200 OK] به کلاینت ارسال می‌شود؛

بیایید با استفاده از یک مرورگر وب، URL و [auth-server.php] را درخواست کنیم:

Image

می‌توانیم ببینیم که مرورگر یک پنجرهٔ احراز هویت نمایش می‌دهد. در [2]، می‌توانیم مقدار هدر [WWW-Authenticate] ارسال‌شده توسط سرور را ببینیم. اگر به هدرهای HTTP دریافت‌شده توسط مرورگر نگاه کنیم، موارد زیر را می‌یابیم:

1
2
3
4
5
6
7
8
9
HTTP/1.0 401 Unauthorized
Date: Fri, 07 Jun 2019 09:11:23 GMT
Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
X-Powered-By: PHP/7.2.11
Cache-Control: no-cache, private
WWW-Authenticate: Basic realm="PHP7 par l'exemple"
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
  • خط ۱: کد پاسخ [401 Unauthorized];
  • خط ۶: هدر HTTP [WWW-Authenticate];
  • خط ۷: محتوای پاسخ خالی است؛

اگر در [3-4]، دو بار [admin] را وارد کنید، پاسخ سرور به شرح زیر است:

1
2
3
4
5
6
7
8
HTTP/1.0 200 OK
Date: Fri, 07 Jun 2019 09:21:00 GMT
Server: Apache/2.4.35 (Win64) OpenSSL/1.1.0i PHP/7.2.11
X-Powered-By: PHP/7.2.11
Cache-Control: no-cache, private
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
  • خط ۱: کد وضعیت پاسخ ۲۰۰ OK;
  • خط ۶: بدنه پاسخ خالی است؛

اگر در [3-4]، اطلاعات نادرست وارد شود، مرورگر [Firefox] که برای آزمایش استفاده می‌شود، پنجره احراز هویت را به طور نامحدود نمایش می‌دهد تا اینکه اطلاعات صحیح وارد شود. هر بار که یک درخواست-پاسخ به سرور انجام می‌شود، همان پاسخ بازگردانده شده و پنجرهٔ احراز هویت مرورگر را فعال می‌کند.

بیایید کلاینت [auth-client.php] را با یک کاربر غیرمجاز اجرا کنیم. پاسخ سرور به شرح زیر است:


---Réponse avec statut : 401
---Entêtes de la réponse
Erreur de communication avec le serveur : HTTP/1.0 401 Unauthorized returned for "https://localhost/php7/scripts-web/08/auth-server.php".
  • در [1]، کلاینت واقعاً کد ۴۰۱ را دریافت کرد؛
  • در [3]، یک استثنا در کلاینت پرتاب شد. این خطا توسط کلاینت Symfony با شناسه [HttpClient] پرتاب شد: این کلاینت زمانی که کد وضعیت پاسخ HTTP نشان‌دهنده خطای سمت سرور باشد و کلاینت تلاش کند سربرگ‌ها یا محتوای پاسخ سرور را بخواند، یک استثنا پرتاب می‌کند. پیام در خط ۳ نشان می‌دهد که سرور با [HTTP/1.0 401 Unauthorized] پاسخ داده است تا نشان دهد کاربر شناسایی نشده است؛

حال بیایید کلاینت [auth-client.php] را با کاربر مجاز [‘admin’,’admin’] اجرا کنیم. پاسخ سرور به شرح زیر است:


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

  • خط ۱: سرور با [HTTP/1. 200 OK] پاسخ داد؛
  • خط ۷: پاسخ هیچ محتوایی ندارد (۰ بایت)؛

17.11.3. امن‌سازی اتصال کلاینت/سرور

ما دیدیم که برای احراز هویت با سرور، کلاینت هدر را ارسال می‌کند:

authorization: Basic YWRtaW46YWRtaW4=

اگر این خط توسط جاسوس‌افزار رهگیری شود، می‌تواند به راحتی اعتبارنامه‌های [login, mot de passe] را که در رشته [YWRtaW46YWRtaW4=] به صورت Base64 رمزگذاری شده‌اند، بازیابی کند. به همین دلیل، احراز هویت باید از طریق یک اتصال امن بین کلاینت و سرور انجام شود. اتصالات امن URL به جای پروتکل HTTP از پروتکل [HTTPS] استفاده می‌کنند. پروتکل [HTTPS]، پروتکل HTTP در یک اتصال امن کلاینت/سرور است. درخواست‌های امن URL به شکل [https://chemin_document] هستند.

همهٔ وب‌سرورها درخواست‌های URL را به این شکل نمی‌پذیرند. آن‌ها باید اصلاح شوند تا مطمئن شویم که امن هستند. سرور آپاچی لارگون یک سرور امن است، اما پروتکل HTTPS به‌صورت پیش‌فرض فعال نیست. باید در منوی لارگون فعال شود:

Image

  • به [4]؛ همچنین باید رمزگذاری SSL را روی سرور آپاچی فعال کنید؛

پس از انجام این کار، سرور آپاچی به طور خودکار مجدداً راه‌اندازی می‌شود:

Image

  • به [1]، یک قفل سبز ظاهر می‌شود: این نشان می‌دهد که پروتکل HTTPS فعال شده است؛
  • در [2]، یک پورت سرویس جدید ظاهر می‌شود – در این مورد پورت ۴۴۳. این پورت سرویس برای پروتکل امن HTTPS است؛

اکنون که یک سرور امن داریم، بیایید فایل پیکربندی کلاینت [config-auth-client.json] را به شرح زیر تغییر دهیم:


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

در [2]، پروتکل به [https] و پورت به [443] تغییر کرده است.

اکنون بیایید کلاینت [auth-client.php] را با کاربر مجاز [admin, admin] اجرا کنیم. خروجی کنسول به شرح زیر است:

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

کلاینت Symfony با شناسه [HttpClient] یک استثنا (exception) ایجاد کرد زیرا سرور گواهی مورد اعتماد را برای آن ارسال کرد که [HttpClient] آن را نپذیرفت. ارتباط از طریق SSL از گواهی‌های مورد اعتماد صادر شده توسط نهادهای رسمی استفاده می‌کند. هنگامی که پروتکل HTTPS بر روی سرور آپاچی Laragon فعال شد، یک گواهی خودامضا برای سرور آپاچی ایجاد شد. گواهی خودامضا گواهی است که توسط یک مرجع رسمی تأیید نشده است. کلاینت Symfony [HttpClient] این گواهی خودامضا را رد کرد.

امکان دستور دادن به [HttpClient] وجود دارد تا اعتبار گواهی ارسال‌شده توسط سرور را بررسی نکند. این کار با استفاده از گزینه‌ها در متد [HttpClient::create] انجام می‌شود:


//یک مشتری ایجاد می‌شود: HTTP
$httpClient = HttpClient::create([
    'auth_basic' => ['admin', 'admin'],
    "verify_peer" => false
  ]);

خط ۴ به برنامه دستور می‌دهد که گواهی سرور را تأیید نکند. ما قبلاً با این مشکل در اسکریپت [http-02.php] که در بخش مرتبط ذکر شده بود، مواجه شده بودیم. آن اسکریپت از کتابخانه [libcurl] برای اتصال به سایت‌های HTTP و HTTPS استفاده می‌کرد. سپس ما از پیکربندی زیر برای آن کتابخانه استفاده کرده بودیم:


// آغاز یک جلسه cURL
  $curl = curl_init($url);
  if ($curl === FALSE) {
    // یک خطا رخ داده است
    return "Erreur lors de l'initialisation de la session cURL pour le site [$site]";
  }
  // گزینه‌های curl
  $options = [
    // حالت تفصیلی
    CURLOPT_VERBOSE => true,
    // اتصال جدید – بدون کش
    CURLOPT_FRESH_CONNECT => true,
    // زمان‌بندی درخواست (به ثانیه)
    CURLOPT_TIMEOUT => $timeout,
    CURLOPT_CONNECTTIMEOUT => $timeout,
    // اعتبارسنجی گواهی‌ها را انجام نده SSL
    CURLOPT_SSL_VERIFYPEER => false,
    // دنبال کردن هدایت‌ها
    CURLOPT_FOLLOWLOCATION => true,
    // بازیابی سند درخواستی به صورت یک رشته
    CURLOPT_RETURNTRANSFER => true
  ];

  //پیکربندی curl
curl_setopt_array($curl, $options);

در خط ۱۷، ثابت [CURLOPT_SSL_VERIFYPEER] کنترل می‌کند که آیا گواهی ارسال‌شده توسط سرور تأیید شود یا خیر. کلاینت [HttpClient] در واقع یک کلاینت [curl] است، زمانی که افزونه [curl] در پیکربندی PHP فعال باشد، همانطور که در اینجا اینطور است. بنابراین کلاس ایجادشده توسط [HttpClient::create]، کلاس [CurlHttpClient] است. ثابت‌های [curl] در این کلاس در دسترس هستند اما با نام‌های متفاوت:


$curlopts = [
            CURLOPT_URL => $url,
            CURLOPT_USERAGENT => 'Symfony HttpClient/Curl',
            CURLOPT_TCP_NODELAY => true,
            CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
            CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 0 < $options['max_redirects'] ? $options['max_redirects'] : 0,
            CURLOPT_COOKIEFILE => '', // ردیابی کوکی‌ها در طول هدایت‌ها
            CURLOPT_CONNECTTIMEOUT_MS => 1000 * $options['timeout'],
            CURLOPT_PROXY => $options['proxy'],
            CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '',
            CURLOPT_SSL_VERIFYPEER => $options['verify_peer'],
            CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0,
            CURLOPT_CAINFO => $options['cafile'],
            CURLOPT_CAPATH => $options['capath'],
            CURLOPT_SSL_CIPHER_LIST => $options['ciphers'],
            CURLOPT_SSLCERT => $options['local_cert'],
            CURLOPT_SSLKEY => $options['local_pk'],
            CURLOPT_KEYPASSWD => $options['passphrase'],
            CURLOPT_CERTINFO => $options['capture_peer_cert_chain'],
        ];

ما ثابت‌های مورد استفاده توسط [CurlHttpClient] را به رنگ زرد برجسته کرده‌ایم.

اگر اکنون کلاینت [auth-client] را با کاربر [admin, admin] اجرا کنیم، نتیجه زیر را به دست می‌آوریم:


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

کاربر به‌درستی شناسایی شده است. اگر کلاینت [auth-client] را با کاربری غیر از [admin, admin] اجرا کنیم، نتیجه زیر را دریافت می‌کنیم:

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

اکنون می‌دانیم چگونه با یک سرور امن احراز هویت کنیم.