赞
踩
在hyperf中 设置响应头让后端直接给前端返回 event-stream 流的需求,发现hyperf 中在重写 SwooleConnection 时,设置的属性 $response 为 protected ,不能直接调用此属性。
为了让我能设置此响应头,我 继承了 Hyperf\HttpServer\Server 类 并重写了 onRequest 方法,
具体的做法如下:
首先创建StreamServer 类, 并重写了onRequest 方法,如果只是想某几个路由 重写,需要自己写一下配置
- <?php
-
-
- namespace App\Service\Server;
-
-
- use Hyperf\Dispatcher\HttpDispatcher;
- use Hyperf\ExceptionHandler\ExceptionHandlerDispatcher;
- use Hyperf\HttpServer\ResponseEmitter;
- use Hyperf\HttpServer\Server;
- use Psr\Container\ContainerInterface;
- use Swoole\Http\Request;
- use Swoole\Http\Response;
-
- class StreamServer extends Server
- {
- const STREAM_URL = [
- '/chat/chat/test'
- ];
- public function __construct(ContainerInterface $container, HttpDispatcher $dispatcher, ExceptionHandlerDispatcher $exceptionHandlerDispatcher, ResponseEmitter $responseEmitter)
- {
- parent::__construct($container, $dispatcher, $exceptionHandlerDispatcher, $responseEmitter);
- }
-
- /**
- * @param Request $request
- * @param Response $response
- */
- public function onRequest($request, $response): void
- {
- $pathInfo = $request->server['path_info'];
- if (in_array($pathInfo, self::STREAM_URL)) {
- $response->header('Content-Type', 'text/event-stream');
- $response->header('Cache-Control', 'no-cache');
- $response->header('Connection', 'keep-alive');
- }
- parent::onRequest($request, $response);
- }
- }
然后再将 config/autoload/server.php 中的 http 配置 修改,如下图
这样,我们在控制器里面就可以实现 response 的header 更改为 text/event-stream。
- <?php
-
- use Hyperf\HttpServer\Contract\ResponseInterface;
-
- $wrResponse = ApplicationContext::getContainer()->get(ResponseInterface::class);
-
- //此处可以将返回的数据 直接打印字屏幕上
- $wrResponse->write("data: " . $res . "\n\n");
也就实现了 chatgpt 的打印流的关键步骤,下面贴出部分代码,使用guzzlehttp 处理chatgpt 返回的流式数据
-
- use GuzzleHttp\Client;
- use GuzzleHttp\Psr7\Utils;
- use Hyperf\Guzzle\ClientFactory;
- use Hyperf\HttpServer\Contract\ResponseInterface;
- use Hyperf\Utils\ApplicationContext;
- use Hyperf\Context\Context;
-
- class HttpRequestService
- {
-
-
-
- private $clientFactory;
-
- //此处可以模拟类属性,获取协程中的属性
- public function __get($key)
- {
- $className = static::class;
- $key = env('APP_NAME').$className . $key;
- return Context::get($key);
- }
-
- //此处可以模拟类属性,将属性存在协程中
- public function __set($key, $value)
- {
- $className = static::class;
- $key = env('APP_NAME').$className . $key;
- Context::set($key, $value);
- }
-
- public function __construct()
- {
- $this->clientFactory = make(ClientFactory::class);
- }
- /**
- * 是否使用流式获取
- * @param bool $stream
- * @return $this
- */
- private function setStream($stream = false)
- {
- $this->stream = $stream;
- return $this;
- }
-
-
- /**
- * 初始化
- * @param string $domainUrl
- * @param array $header
- * @param int $timeout
- */
- private function init($domainUrl = '', $header = [], $timeout = 120)
- {
- $config = ['timeout' => $timeout];
-
- if (!empty($domainUrl)) {
- $config['base_uri'] = $domainUrl;
- }
-
- if ($this->stream) {
- $config['stream'] = true;
- }
-
- if (!empty($header) && is_array($header)) {
- $config = array_merge($config, ['headers' => $header]);
- }
- /** @var Client $client */
- $this->client = $this->clientFactory->create($config);
- }
-
- public function postStream($domain, $uri, $params, $header = [])
- {
- $this->setStream(true);
- $this->init($domain, $header);
- $type = 'json';
- $requestParmas = [$type => $params];
-
- $response = $this->client->post($uri, $requestParmas);
- $responseData = $response->getBody(); //获取流数据
- $parseData = [];
- $wrResponse = ApplicationContext::getContainer()->get(ResponseInterface::class);
- while (!$responseData->eof()) {
- $res = trim(Utils::readLine($responseData)); //一行一行读取
- $res = str_replace("data: ", "", $res);
- if (empty($res)) {
- continue;
- }
- $wrResponse->write("data: " . $res . "\n\n");
- if ($res == '[DONE]') {
- break;
- }
- //休眠时间,用于前端展示
- usleep(100000);
- $res = json_decode($res, true);
- if (!empty($res)) {
- $parseData[] = $res['choices'][0]['delta']['content'] ?? ($res['choices'][0]['delta']['role'] ?? '');
- }
- }
-
- return $parseData;
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。