การตอบสนอง

การตอบสนองจริงๆ แล้วเป็นอ็อบเจ็กต์ 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();

    // .... ข้ามตรรกะทางธุรกิจ

    // ตั้งค่า cookie
    $response->cookie('foo', 'value');

    // .... ข้ามตรรกะทางธุรกิจ

    // ตั้งค่า http header
    $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;
}

การตั้งค่า header

<?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 เพื่อกำหนด header ทีละอันหรือกลุ่มได้

<?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',
        ]);
    }
}

คุณยังสามารถตั้งค่า header ล่วงหน้าและตั้งค่าสิ่งที่จะส่งคืนได้ในตอนท้าย

public function hello(Request $request)
{
    // สร้างอ็อบเจ็กต์
    $response = response();

    // .... ข้ามตรรกะทางธุรกิจ

    // ตั้งค่า http header
    $response->header('Content-Type', 'application/json');
    $response->withHeaders([
                'X-Header-One' => 'Header Value 1',
                'X-Header-Tow' => 'Header Value 2',
            ]);

    // .... ข้ามตรรกะทางธุรกิจ

    // ตั้งค่าข้อมูลที่จะส่งคืน
    $response->withBody('返回的数据');
    return $response;
}

การตั้งค่า cookie

<?php
namespace app\controller;

use support\Request;

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

คุณยังสามารถตั้งค่า cookie ล่วงหน้าและตั้งค่าสิ่งที่จะส่งคืนได้ในตอนท้าย

public function hello(Request $request)
{
    // สร้างอ็อบเจ็กต์
    $response = response();

    // .... ข้ามตรรกะทางธุรกิจ

    // ตั้งค่า cookie
    $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 รองรับการส่งไฟล์ขนาดใหญ่
  • สำหรับไฟล์ขนาดใหญ่ (มากกว่า 2M) webman จะไม่อ่านไฟล์ทั้งหมดในหน่วยความจำในครั้งเดียว แต่จะอ่านไฟล์เป็นส่วนๆ ในเวลาที่เหมาะสมและส่งออก
  • webman จะปรับความเร็วในการอ่านไฟล์ให้เหมาะสมกับความเร็วในการรับของไคลเอนต์ เพื่อให้การส่งไฟล์เร็วที่สุดในขณะที่ลดการใช้งานหน่วยความจำให้น้อยที่สุด
  • การส่งข้อมูลเป็นแบบไม่บล็อกจะไม่ส่งผลต่อการประมวลผลคำขออื่นๆ
  • วิธี file จะเพิ่ม if-modified-since header โดยอัตโนมัติและตรวจสอบ if-modified-since header ในคำขอถัดไป หากไฟล์ไม่มีการเปลี่ยนแปลง จะตอบกลับ 304 โดยตรงเพื่อประหยัดแบนด์วิธ
  • ไฟล์ที่ส่งออกจะถูกส่งไปยังเบราว์เซอร์โดยใช้ Content-Type header ที่เหมาะสมโดยอัตโนมัติ
  • หากไฟล์ไม่อยู่ จะถูกแปลงเป็นการตอบกลับ 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 header

รับข้อมูลที่ส่งออก

บางไลบรารีจะพิมพ์เนื้อหาของไฟล์ไปยังการส่งออกมาตรฐาน หมายถึงข้อมูลจะถูกพิมพ์ในเทอร์มินัลเท่านั้น ไม่ได้ส่งไปยังเบราว์เซอร์ ในกรณีนี้เราจำเป็นต้องใช้ 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 body
        $timer = Timer::add(1, function () use ($connection, &$timer) {
            static $i = 0;
            if ($i++ < 10) {
                // ส่ง http body
                $connection->send(new Chunk($i));
            } else {
                // ลบตัวจับเวลาไม่จำเป็นออก เพื่อลดการเกิด memory leak
                Timer::del($timer);
                // ส่ง Chunk ที่ว่างเปล่าเพื่อแจ้งให้ไคลเอนต์ทราบว่าการตอบสนองเสร็จสิ้น
                $connection->send(new Chunk(''));
            }
        });
        // ส่ง http header ที่มี Transfer-Encoding: chunked ก่อน และส่ง http body แบบอะซิงโครนัส
        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) {
                // ส่งต่อข้อมูลที่มีอยู่ไปยังเบราว์เซอร์เมื่อ API ของ 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 header ก่อน และข้อมูลในภายหลังแบบอะซิงโครนัส
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }
}