登录/注册 登录
密码登录 验证码登录 忘记密码
快捷登录/注册
Upload
提交

流式应用程序的无缓冲响应

  • PHP强行刷新浏览器缓存实现实时显示

  • 响应头“X-Accel-Buffering”传递“yes”或“no”可以动态地开启或关闭代理的缓冲功能

  • 设置此连接的代理缓存,将此设置为 no 将允许适用于 Comet 和 HTTP 流式应用程序的无缓冲响应

//后端代码

date_default_timezone_set('PRC'); //设置中国时区

header('X-Accel-Buffering: no');
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
ob_end_clean();
ob_implicit_flush(1);
//echo "retry: 10000\n";

while(1){

    $data =  [
        "id" => time(),
        "message" => '欢迎来到helloweba,现在是北京时间'.date('Y-m-d H:i:s')
    ];

    returnEventData($data);
    sleep(2);
}

function returnEventData($returnData, $event='message', $id=0, $retry=0){

    //$ResponseType = ['message','event'];

    $str = '';
    if($id>0){
        $str .= "id: {$id}".PHP_EOL;
    }
    if($event){
        $str.= "event: {$event}".PHP_EOL;
    }
    if($retry>0){
        $str .= "retry: {$retry}".PHP_EOL;
    }
    if(is_array($returnData)){
        $returnData = json_encode($returnData);
    }
    $str .= "data: {$returnData}".PHP_EOL;
    $str .= PHP_EOL;
    echo $str;
}


发起请求

//定义函数
function fetchStream(url, params) {
    /* eslint-disable */
    const {onmessage, onclose, ...otherParams} = params;
    var decoder = new TextDecoder();
    const onProgress = function (textChunk, callback) {
        var WAITING = -1;
        var CLOSED = 2;

        var AFTER_CR = -1;
        var FIELD_START = 0;
        var FIELD = 1;
        var VALUE_START = 2;
        var VALUE = 3;

        var currentState = WAITING;
        var textBuffer = "";
        var state = FIELD_START;
        var dataBuffer = "";
        var lastEventIdBuffer = "";
        var eventTypeBuffer = "";
        var lastEventId = "";
        var fieldStart = 0;
        var valueStart = 0;
        let c;
        var n = -1;
        for (var i = 0; i < textChunk.length; i += 1) {
            c = textChunk.charCodeAt(i);
            if (c === "\n".charCodeAt(0) || c === "\r".charCodeAt(0)) {
                n = i;
            }
        }
        const chunk = (n !== -1 ? textBuffer : "") + textChunk.slice(0, n + 1);

        for (let position = 0; position < chunk.length; position += 1) {
            c = chunk.charCodeAt(position);
            if (state === AFTER_CR && c === "\n".charCodeAt(0)) {
                state = FIELD_START;
            } else {
                if (state === AFTER_CR) {
                    state = FIELD_START;
                }
                if (c === "\r".charCodeAt(0) || c === "\n".charCodeAt(0)) {
                    if (state !== FIELD_START) {
                        if (state === FIELD) {
                            valueStart = position + 1;
                        }
                        var field = chunk.slice(fieldStart, valueStart - 1);
                        var value = chunk.slice(valueStart + (valueStart < position && chunk.charCodeAt(valueStart) === " ".charCodeAt(0) ? 1 : 0), position);
                        if (field === "data") {
                            dataBuffer += "\n";
                            dataBuffer += value;
                        } else if (field === "id") {
                            lastEventIdBuffer = value;
                        } else if (field === "event") {
                            eventTypeBuffer = value.toLowerCase();
                        }
                    }
                    if (state === FIELD_START) {

                        if (dataBuffer !== "") {
                            lastEventId = lastEventIdBuffer;
                            if (eventTypeBuffer === "") {
                                eventTypeBuffer = "message";
                            }
                            var event = new MessageEvent(eventTypeBuffer, {
                                data: dataBuffer.slice(1),
                                lastEventId: lastEventIdBuffer
                            });
                            callback(event)
                            /*  if (eventTypeBuffer === "open") {
                                  callback(event)
                              } else if (eventTypeBuffer === "message") {
                                  callback(event)
                              } else if (eventTypeBuffer === "error") {
                                  callback(event)
                              }*/
                            if (currentState === CLOSED) {
                                return;
                            }
                        }
                        dataBuffer = "";
                        eventTypeBuffer = "";
                    }
                    state = c === "\r".charCodeAt(0) ? AFTER_CR : FIELD_START;
                } else {
                    if (state === FIELD_START) {
                        fieldStart = position;
                        state = FIELD;
                    }
                    if (state === FIELD) {
                        if (c === ":".charCodeAt(0)) {
                            valueStart = position + 1;
                            state = VALUE_START;
                        }
                    } else if (state === VALUE_START) {
                        state = VALUE;
                    }
                }
            }
        }
    };
       const push = (controller, reader, contentType) => {
       reader.read().then((result)=>{
            if (result.done) {
                controller.close();
                onclose?.();
            } else {
                controller.enqueue(result.value);
              var  value = decoder.decode(result.value, {stream: true})
                if (value){
                    if (/^text\/event\-stream(;.*)?$/i.test(contentType)) {
                        try {
                            JSON.parse(value)
                            onmessage(new MessageEvent('error', {
                                data: value,
                                lastEventId: 0
                            }))
                        }catch (e){
                            onProgress(value, onmessage)
                        }
                    } else {
                        onmessage(new MessageEvent('error', {
                            data: value,
                            lastEventId: 0
                        }))
                    }
                }
    
                push(controller, reader,contentType);
            }
        }).catch(()=>{
           onclose?.();
       });
    
    
    };
    // 发送请求
    return fetch(url, otherParams)
        .then((response) => {
            // 以ReadableStream解析数据
            const reader = response.body.getReader();
            return new ReadableStream({
                start(controller) {
                    push(controller, reader, response.headers.get("Content-Type"));
                },
            });
        }).catch(()=>{
            onclose?.();
        });
}

//调用函数示例
let theFirstTime = true;//是否首次回调
let allContent = '';//存储全部内容
fetchStream(url, {
    method: 'post',
    body: JSON.stringify({}),
    headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'Content-Type': 'application/json',
    },
    onmessage: (event) => {
        if (!event.data){
            return
        }
        var data, text

        try {
            data = JSON.parse(event.data)
            switch (event.type) {
                case 'message':
                    text = data.text
                    break;
                case 'error':
                    if (data.ret) {
                        app.$message.error(data.msg);
                    }
                    break;
            }
        } catch (e) {
            text = event.data
        }
        if(text){
            allContent += text
            //这里可以其他回调函数
            //callback(text, theFirstTime, allContent)
            if (theFirstTime) {
                theFirstTime = false
            }
        }

    },
    onclose: () => {
       //关闭时
    }
})


感谢您的阅读,本文为正版软件资讯 | 阅木有原创内容,转载时请标注来源于正版软件资讯 | 阅木有和本文链接

评论