Phản hồi
Phản hồi thực ra là một đối tượng support\Response
, để tiện cho việc tạo đối tượng này, webman cung cấp một số hàm trợ giúp.
Trả về một phản hồi tùy ý
Ví dụ
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return response('hello webman');
}
}
Hàm response được triển khai như sau:
function response($body = '', $status = 200, $headers = array())
{
return new Response($status, $headers, $body);
}
Bạn cũng có thể tạo một đối tượng response
rỗng trước, sau đó sử dụng $response->cookie()
$response->header()
$response->withHeaders()
$response->withBody()
để thiết lập nội dung trả về tại các vị trí thích hợp.
public function hello(Request $request)
{
// Tạo một đối tượng
$response = response();
// .... Logic kinh doanh được bỏ qua
// Thiết lập cookie
$response->cookie('foo', 'value');
// .... Logic kinh doanh được bỏ qua
// Thiết lập header http
$response->header('Content-Type', 'application/json');
$response->withHeaders([
'X-Header-One' => 'Header Value 1',
'X-Header-Tow' => 'Header Value 2',
]);
// .... Logic kinh doanh được bỏ qua
// Thiết lập dữ liệu cần trả về
$response->withBody('Dữ liệu trả về');
return $response;
}
Trả về json
Ví dụ
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return json(['code' => 0, 'msg' => 'ok']);
}
}
Hàm json được triển khai như sau
function json($data, $options = JSON_UNESCAPED_UNICODE)
{
return new Response(200, ['Content-Type' => 'application/json'], json_encode($data, $options));
}
Trả về xml
Ví dụ
<?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);
}
}
Hàm xml được triển khai như sau:
function xml($xml)
{
if ($xml instanceof SimpleXMLElement) {
$xml = $xml->asXML();
}
return new Response(200, ['Content-Type' => 'text/xml'], $xml);
}
Trả về view
Tạo file app/controller/FooController.php
như sau
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return view('foo/hello', ['name' => 'webman']);
}
}
Tạo file app/view/foo/hello.html
như sau
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>webman</title>
</head>
<body>
hello <?=htmlspecialchars($name)?>
</body>
</html>
Chuyển hướng
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return redirect('/user');
}
}
Hàm redirect được triển khai như sau:
function redirect($location, $status = 302, $headers = [])
{
$response = new Response($status, ['Location' => $location]);
if (!empty($headers)) {
$response->withHeaders($headers);
}
return $response;
}
Thiết lập 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'
]);
}
}
Bạn cũng có thể sử dụng phương thức header
và withHeaders
để thiết lập header một cách đơn lẻ hoặc theo lô.
<?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',
]);
}
}
Bạn cũng có thể thiết lập header trước, và sau đó thiết lập dữ liệu sẽ trả về.
public function hello(Request $request)
{
// Tạo một đối tượng
$response = response();
// .... Logic kinh doanh được bỏ qua
// Thiết lập header http
$response->header('Content-Type', 'application/json');
$response->withHeaders([
'X-Header-One' => 'Header Value 1',
'X-Header-Tow' => 'Header Value 2',
]);
// .... Logic kinh doanh được bỏ qua
// Thiết lập dữ liệu cần trả về
$response->withBody('Dữ liệu trả về');
return $response;
}
Thiết lập cookie
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return response('hello webman')
->cookie('foo', 'value');
}
}
Bạn cũng có thể thiết lập cookie trước, và sau đó thiết lập dữ liệu sẽ trả về.
public function hello(Request $request)
{
// Tạo một đối tượng
$response = response();
// .... Logic kinh doanh được bỏ qua
// Thiết lập cookie
$response->cookie('foo', 'value');
// .... Logic kinh doanh được bỏ qua
// Thiết lập dữ liệu cần trả về
$response->withBody('Dữ liệu trả về');
return $response;
}
Tham số đầy đủ của phương thức cookie như sau:
cookie($name, $value = '', $max_age = 0, $path = '', $domain = '', $secure = false, $http_only = false)
Trả về luồng tệp
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return response()->file(public_path() . '/favicon.ico');
}
}
- webman hỗ trợ gửi tệp lớn
- Đối với các tệp lớn (trên 2M), webman sẽ không đọc toàn bộ tệp vào bộ nhớ một lần, mà sẽ đọc tệp theo từng đoạn tại thời điểm thích hợp và gửi đi
- webman sẽ tối ưu hóa tốc độ đọc và gửi tệp dựa trên tốc độ nhận của khách hàng, đảm bảo gửi tệp nhanh nhất trong khi giảm mức sử dụng bộ nhớ xuống thấp nhất
- Dữ liệu gửi đi là không chặn, không ảnh hưởng đến việc xử lý các yêu cầu khác
- Phương thức file sẽ tự động bổ sung header
if-modified-since
và kiểm tra headerif-modified-since
trong lần yêu cầu tiếp theo, nếu tệp không được chỉnh sửa thì sẽ trả về 304 để tiết kiệm băng thông - Tệp gửi đi sẽ tự động sử dụng header
Content-Type
thích hợp để gửi đến trình duyệt - Nếu tệp không tồn tại, sẽ tự động chuyển thành phản hồi 404
Tải tệp
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return response()->download(public_path() . '/favicon.ico', 'tên tệp.ico');
}
}
Phương thức download có sự tương đồng với phương thức file, sự khác biệt là
- Khi thiết lập tên tệp tải về, tệp sẽ được tải xuống, thay vì hiển thị trong trình duyệt
- Phương thức download sẽ không kiểm tra header
if-modified-since
Lấy đầu ra
Một số thư viện in nội dung tệp trực tiếp vào đầu ra chuẩn, nghĩa là dữ liệu sẽ được in ra trong terminal và không được gửi đến trình duyệt, lúc này chúng ta cần sử dụng ob_start();
ob_get_clean();
để bắt dữ liệu vào một biến, sau đó gửi dữ liệu đến trình duyệt, ví dụ:
<?php
namespace app\controller;
use support\Request;
class ImageController
{
public function get(Request $request)
{
// Tạo hình ảnh
$im = imagecreatetruecolor(120, 20);
$text_color = imagecolorallocate($im, 233, 14, 91);
imagestring($im, 1, 5, 5, 'Một chuỗi văn bản đơn giản', $text_color);
// Bắt đầu lấy đầu ra
ob_start();
// Xuất hình ảnh
imagejpeg($im);
// Lấy nội dung hình ảnh
$image = ob_get_clean();
// Gửi hình ảnh
return response($image)->header('Content-Type', 'image/jpeg');
}
}
Phản hồi theo từng đoạn
Đôi khi chúng ta muốn gửi phản hồi theo từng đoạn, có thể tham khảo ví dụ dưới đây.
<?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
{
// Lấy kết nối
$connection = $request->connection;
// Gửi gói http theo định kỳ
$timer = Timer::add(1, function () use ($connection, &$timer) {
static $i = 0;
if ($i++ < 10) {
// Gửi gói http
$connection->send(new Chunk($i));
} else {
// Xóa bộ đếm không cần thiết để tránh rò rỉ bộ nhớ
Timer::del($timer);
// Xuất gói Chunk rỗng để thông báo cho khách hàng rằng phản hồi đã kết thúc
$connection->send(new Chunk(''));
}
});
// Đầu tiên xuất http header với Transfer-Encoding: chunked, gói http sẽ được gửi đồng bộ
return response()->withHeaders([
"Transfer-Encoding" => "chunked",
]);
}
}
Nếu bạn đang gọi một mô hình lớn, hãy tham khảo ví dụ dưới đây.
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 có thể không thể truy cập trong nước, bạn có thể thay thế bằng địa chỉ 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) {
// Khi API openai trả về dữ liệu thì chuyển tiếp cho trình duyệt
$connection->send(new Chunk(json_encode($data, JSON_UNESCAPED_UNICODE) . "\n"));
},
'complete' => function($result, $response) use ($connection) {
// Khi phản hồi kết thúc, kiểm tra xem có lỗi hay không
if (isset($result['error'])) {
$connection->send(new Chunk(json_encode($result, JSON_UNESCAPED_UNICODE) . "\n"));
}
// Trả về chunk rỗng đại diện cho việc phản hồi đã kết thúc
$connection->send(new Chunk(''));
},
]);
// Trước tiên trả về một header http, sau đó dữ liệu sẽ được trả về đồng bộ
return response()->withHeaders([
"Transfer-Encoding" => "chunked",
]);
}
}