الاستجابة

الاستجابة في الواقع هي كائن support\Response، ولتسهيل إنشاء هذا الكائن، توفر Webman بعض الوظائف المساعدة.

إرجاع استجابة عشوائية

مثال

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response('hello webman');
    }
}

تنفيذ دالة response كما يلي:

function response($body = '', $status = 200, $headers = array())
{
    return new Response($status, $headers, $body);
}

يمكنك أيضًا إنشاء كائن response فارغ أولاً، ثم استخدام $response->cookie() و $response->header() و $response->withHeaders() و $response->withBody() لضبط محتوى الاستجابة.

public function hello(Request $request)
{
    // إنشاء كائن
    $response = response();

    // .... منطق العمل تم حذفه

    // ضبط الكوكي
    $response->cookie('foo', 'value');

    // .... منطق العمل تم حذفه

    // ضبط رأس http
    $response->header('Content-Type', 'application/json');
    $response->withHeaders([
                'X-Header-One' => 'Header Value 1',
                'X-Header-Tow' => 'Header Value 2',
            ]);

    // .... منطق العمل تم حذفه

    // ضبط البيانات المراد إرجاعها
    $response->withBody('返回的数据');
    return $response;
}

إرجاع json

مثال

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return json(['code' => 0, 'msg' => 'ok']);
    }
}

تنفيذ دالة json كما يلي:

function json($data, $options = JSON_UNESCAPED_UNICODE)
{
    return new Response(200, ['Content-Type' => 'application/json'], json_encode($data, $options));
}

إرجاع xml

مثال

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        $xml = <<<XML
               <?xml version='1.0' standalone='yes'?>
               <values>
                   <truevalue>1</truevalue>
                   <falsevalue>0</falsevalue>
               </values>
               XML;
        return xml($xml);
    }
}

تنفيذ دالة xml كما يلي:

function xml($xml)
{
    if ($xml instanceof SimpleXMLElement) {
        $xml = $xml->asXML();
    }
    return new Response(200, ['Content-Type' => 'text/xml'], $xml);
}

إرجاع عرض

أنشئ ملف app/controller/FooController.php كما يلي

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return view('foo/hello', ['name' => 'webman']);
    }
}

أنشئ ملف app/view/foo/hello.html كما يلي

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>webman</title>
</head>
<body>
hello <?=htmlspecialchars($name)?>
</body>
</html>

إعادة توجيه

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return redirect('/user');
    }
}

تنفيذ دالة redirect كما يلي:

function redirect($location, $status = 302, $headers = [])
{
    $response = new Response($status, ['Location' => $location]);
    if (!empty($headers)) {
        $response->withHeaders($headers);
    }
    return $response;
}

إعداد الرأس

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response('hello webman', 200, [
            'Content-Type' => 'application/json',
            'X-Header-One' => 'Header Value' 
        ]);
    }
}

يمكنك أيضًا استخدام header و withHeaders لضبط الرأس بشكل فردي أو دفعة واحدة.

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response('hello webman')
        ->header('Content-Type', 'application/json')
        ->withHeaders([
            'X-Header-One' => 'Header Value 1',
            'X-Header-Tow' => 'Header Value 2',
        ]);
    }
}

يمكنك أيضًا إعداد الرأس مسبقًا، ثم إعداد البيانات التي سيتم إرجاعها في النهاية.

public function hello(Request $request)
{
    // إنشاء كائن
    $response = response();

    // .... منطق العمل تم حذفه

    // ضبط رأس http
    $response->header('Content-Type', 'application/json');
    $response->withHeaders([
                'X-Header-One' => 'Header Value 1',
                'X-Header-Tow' => 'Header Value 2',
            ]);

    // .... منطق العمل تم حذفه

    // ضبط البيانات المراد إرجاعها
    $response->withBody('返回的数据');
    return $response;
}

إعداد الكوكي

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response('hello webman')
        ->cookie('foo', 'value');
    }
}

يمكنك أيضًا إعداد الكوكي مسبقًا، ثم إعداد البيانات التي سيتم إرجاعها في النهاية.

public function hello(Request $request)
{
    // إنشاء كائن
    $response = response();

    // .... منطق العمل تم حذفه

    // ضبط الكوكي
    $response->cookie('foo', 'value');

    // .... منطق العمل تم حذفه

    // ضبط البيانات المراد إرجاعها
    $response->withBody('返回的数据');
    return $response;
}

المعلمات الكاملة لطريقة cookie هي كما يلي:

cookie($name, $value = '', $max_age = 0, $path = '', $domain = '', $secure = false, $http_only = false)

إرجاع تدفق ملف

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response()->file(public_path() . '/favicon.ico');
    }
}
  • يدعم Webman إرسال ملفات ضخمة
  • بالنسبة للملفات الكبيرة (أكثر من 2 ميغابايت)، لن يقوم Webman بقراءة الملف بالكامل دفعة واحدة في الذاكرة، بل سيقوم بقراءة الملف وإرساله بشكل مجزأ في الوقت المناسب
  • سيقوم Webman بتحسين سرعة قراءة وإرسال الملفات بناءً على سرعة استلام العميل، لضمان أسرع إرسال للملفات مع تقليل استخدام الذاكرة إلى أدنى حد
  • إرسال البيانات هو غير متزامن، ولن يؤثر على معالجة الطلبات الأخرى
  • ستقوم طريقة file بإضافة رأس if-modified-since تلقائيًا وستتحقق من رأس if-modified-since في الطلب التالي، إذا لم يتغير الملف، ستقوم بإرجاع 304 مباشرة لتوفير النطاق الترددي
  • سيتم إرسال الملفات المرسلة تلقائيًا باستخدام رأس Content-Type المناسب إلى المتصفح
  • إذا كان الملف غير موجود، فسيتم تحويله تلقائيًا إلى استجابة 404

تنزيل ملف

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response()->download(public_path() . '/favicon.ico', '文件名.ico');
    }
}

تشبه دالة download دالة file الأساسية، والاختلاف هو:

  1. عند إعداد اسم الملف للتنزيل، سيتم تنزيله بدلاً من عرضه في المتصفح
  2. لن تتحقق دالة download من رأس if-modified-since

الحصول على المخرجات

بعض المكتبات تطبع محتوى الملف مباشرة إلى الإخراج القياسي، مما يعني أن البيانات ستتم طباعتها في طرفية سطر الأوامر، ولن تُرسل إلى المتصفح، في هذه الحالة نحتاج إلى التقاط البيانات إلى متغير باستخدام ob_start(); و ob_get_clean(); ثم إرسال البيانات إلى المتصفح، على سبيل المثال:

<?php

namespace app\controller;

use support\Request;

class ImageController
{
    public function get(Request $request)
    {
        // إنشاء الصورة
        $im = imagecreatetruecolor(120, 20);
        $text_color = imagecolorallocate($im, 233, 14, 91);
        imagestring($im, 1, 5, 5,  'A Simple Text String', $text_color);

        // بدء التقاط الإخراج
        ob_start();
        // إخراج الصورة
        imagejpeg($im);
        // الحصول على محتوى الصورة
        $image = ob_get_clean();

        // إرسال الصورة
        return response($image)->header('Content-Type', 'image/jpeg');
    }
}

استجابة مجزأة

في بعض الأحيان نريد إرسال الاستجابة بشكل مجزأ، يمكن أن يكون المثال أدناه مرجعًا.

<?php

namespace app\controller;

use support\Request;
use support\Response;
use Workerman\Protocols\Http\Chunk;
use Workerman\Timer;

class IndexController
{
    public function index(Request $request): Response
    {
        // الحصول على الاتصال
        $connection = $request->connection;
        // إرسال حزمة http كل فترة
        $timer = Timer::add(1, function () use ($connection, &$timer) {
            static $i = 0;
            if ($i++ < 10) {
                // إرسال حزمة http
                $connection->send(new Chunk($i));
            } else {
                // حذف المؤقت غير المستخدم، لتجنب تسرب الذاكرة مع زيادة عدد المؤقتات
                Timer::del($timer);
                // إرسال حزمة Chunk فارغة لإعلام العميل بانتهاء الاستجابة
                $connection->send(new Chunk(''));
            }
        });
        // أولاً إرسال رأس http مع Transfer-Encoding: chunked، سيتم إرسال جسم http بشكل غير متزامن
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }

}

إذا كنت تتعامل مع نماذج كبيرة، راجع المثال أدناه.

composer require webman/openai
<?php
namespace app\controller;
use support\Request;

use Webman\Openai\Chat;
use Workerman\Protocols\Http\Chunk;

class ChatController
{
    public function completions(Request $request)
    {
        $connection = $request->connection;
        // https://api.openai.com إذا لم يكن بالإمكان الوصول من داخل الدولة، يمكنك استخدام العنوان https://api.openai-proxy.com كبديل
        $chat = new Chat(['apikey' => 'sk-xx', 'api' => 'https://api.openai.com']);
        $chat->completions(
            [
                'model' => 'gpt-3.5-turbo',
                'stream' => true,
                'messages' => [['role' => 'user', 'content' => 'hello']],
            ], [
            'stream' => function($data) use ($connection) {
                // عند عودة البيانات من واجهة openai، سيقوم بإرسالها إلى المتصفح
                $connection->send(new Chunk(json_encode($data, JSON_UNESCAPED_UNICODE) . "\n"));
            },
            'complete' => function($result, $response) use ($connection) {
                // عند انتهاء الاستجابة، تحقق مما إذا كان هناك خطأ
                if (isset($result['error'])) {
                    $connection->send(new Chunk(json_encode($result, JSON_UNESCAPED_UNICODE) . "\n"));
                }
                // إرسال Chunk فارغ للدلالة على انتهاء الاستجابة
                $connection->send(new Chunk(''));
            },
        ]);
        // أولاً إرجاع رأس http، ستعود البيانات لاحقًا بشكل غير متزامن
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }
}