響應
響應實際上是一個support\Response
對象,為了方便創(chuàng)建這個對象,webman提供了一些助手函數(shù)。
返回一個任意響應
例子
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return response('hello webman');
}
}
response函數(shù)實現(xiàn)如下:
function response($body = '', $status = 200, $headers = array())
{
return new Response($status, $headers, $body);
}
你也可以先創(chuàng)建一個空的response
對象,然后在適當?shù)奈恢美?code>$response->cookie() $response->header()
$response->withHeaders()
$response->withBody()
設置返回內(nèi)容。
public function hello(Request $request)
{
// 創(chuàng)建一個對象
$response = response();
// .... 業(yè)務邏輯省略
// 設置cookie
$response->cookie('foo', 'value');
// .... 業(yè)務邏輯省略
// 設置http頭
$response->header('Content-Type', 'application/json');
$response->withHeaders([
'X-Header-One' => 'Header Value 1',
'X-Header-Tow' => 'Header Value 2',
]);
// .... 業(yè)務邏輯省略
// 設置要返回的數(shù)據(jù)
$response->withBody('返回的數(shù)據(jù)');
return $response;
}
返回json
例子
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function hello(Request $request)
{
return json(['code' => 0, 'msg' => 'ok']);
}
}
json函數(shù)實現(xiàn)如下
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函數(shù)實現(xiàn)如下:
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函數(shù)實現(xiàn)如下:
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,最后設置將要返回的數(shù)據(jù)。
public function hello(Request $request)
{
// 創(chuàng)建一個對象
$response = response();
// .... 業(yè)務邏輯省略
// 設置http頭
$response->header('Content-Type', 'application/json');
$response->withHeaders([
'X-Header-One' => 'Header Value 1',
'X-Header-Tow' => 'Header Value 2',
]);
// .... 業(yè)務邏輯省略
// 設置要返回的數(shù)據(jù)
$response->withBody('返回的數(shù)據(jù)');
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,最后設置要返回的數(shù)據(jù)。
public function hello(Request $request)
{
// 創(chuàng)建一個對象
$response = response();
// .... 業(yè)務邏輯省略
// 設置cookie
$response->cookie('foo', 'value');
// .... 業(yè)務邏輯省略
// 設置要返回的數(shù)據(jù)
$response->withBody('返回的數(shù)據(jù)');
return $response;
}
cookie方法完整參數(shù)如下:
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支持發(fā)送超大文件
- 對于大文件(超過2M),webman不會將整個文件一次性讀入內(nèi)存,而是在合適的時機分段讀取文件并發(fā)送
- webman會根據(jù)客戶端接收速度來優(yōu)化文件讀取發(fā)送速度,保證最快速發(fā)送文件的同時將內(nèi)存占用減少到最低
- 數(shù)據(jù)發(fā)送是非阻塞的,不會影響其它請求處理
- file方法會自動添加
if-modified-since
頭并在下一個請求時檢測if-modified-since
頭,如果文件未修改則直接返回304以便節(jié)省帶寬 - 發(fā)送的文件會自動使用合適的
Content-Type
頭發(fā)送給瀏覽器 - 如果文件不存在,會自動轉(zhuǎn)為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方法基本一致,的區(qū)別是
1、設置下載的文件名后文件會被下載下來,而不是顯示在瀏覽器里
2、download方法不會檢查if-modified-since
頭
獲取輸出
有些類庫是將文件內(nèi)容直接打印到標準輸出的,也就是數(shù)據(jù)會打印在命令行終端里,并不會發(fā)送給瀏覽器,這時候我們需要通過ob_start();
ob_get_clean();
將數(shù)據(jù)捕獲到一個變量中,再將數(shù)據(jù)發(fā)送給瀏覽器,例如:
<?php
namespace app\controller;
use support\Request;
class ImageController
{
public function get(Request $request)
{
// 創(chuàng)建圖像
$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);
// 獲得圖像內(nèi)容
$image = ob_get_clean();
// 發(fā)送圖像
return response($image)->header('Content-Type', 'image/jpeg');
}
}
分段響應
有時候我們想分段發(fā)送響應,可以參考下面例子。
<?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;
// 定時發(fā)送http包體
$timer = Timer::add(1, function () use ($connection, &$timer) {
static $i = 0;
if ($i++ < 10) {
// 發(fā)送http包體
$connection->send(new Chunk($i));
} else {
// 刪除不用的定時器,避免定時器越來越多內(nèi)存泄漏
Timer::del($timer);
// 輸出空的Chunk包體通知客戶端響應結(jié)束
$connection->send(new Chunk(''));
}
});
// 先輸出一個帶Transfer-Encoding: chunked的http頭,http包體異步發(fā)送
return response()->withHeaders([
"Transfer-Encoding" => "chunked",
]);
}
}
如果你是調(diào)用的大模型,參考下面例子。
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 國內(nèi)訪問不到的話可以用地址 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接口返回數(shù)據(jù)時轉(zhuǎn)發(fā)給瀏覽器
$connection->send(new Chunk(json_encode($data, JSON_UNESCAPED_UNICODE) . "\n"));
},
'complete' => function($result, $response) use ($connection) {
// 響應結(jié)束時檢查是否有錯誤
if (isset($result['error'])) {
$connection->send(new Chunk(json_encode($result, JSON_UNESCAPED_UNICODE) . "\n"));
}
// 返回空的chunk代表響應結(jié)束
$connection->send(new Chunk(''));
},
]);
// 先返回一個http頭,后面數(shù)據(jù)異步返回
return response()->withHeaders([
"Transfer-Encoding" => "chunked",
]);
}
}