hyperf 中 http 设置响应头为 text/event-stream

文章讲述了如何在Hyperf框架下,通过继承Server类并重写onRequest方法来设置响应头为event-stream,以便支持向前端返回流式数据。同时,文中提到了使用GuzzleHttp处理ChatGPT的流式响应,通过一行行读取并发送数据到前端,实现聊天应用的关键功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值