Как правильно работать с SSE

@Controller
public class BankController extends BaseController {

    private Map<String, ResponseBodyEmitter> emitters = new ConcurrentHashMap<>();

    // call inside some method
    /*
        executorService.execute(() -> {
            activateEmitter(dto.getRecipient(), dto.getAmount());
        });
    */

    @GetMapping(path = "/bills/notify")
    public ResponseBodyEmitter registerEmitter(HttpServletResponse response) {
        
        String phone = authentication().getName();
        ResponseBodyEmitter emitter;
        if(emitters.containsKey(phone)) {
            emitter = emitters.get(phone);
            response.setStatus(HttpServletResponse.SC_OK);
        }
        else {
            emitter = new ResponseBodyEmitter(0L);
            emitters.putIfAbsent(phone, emitter);               
            response.setStatus(HttpServletResponse.SC_CREATED);
        }       
        emitter.onCompletion(()-> emitters.remove(phone, emitter));
        emitter.onTimeout(()-> emitters.remove(phone, emitter));
        emitter.onError((e)-> emitters.remove(phone, emitter));
        return emitter;
    }
    private void activateEmitter(int id, double amount) {

        BillResponse bill = billService.getBillDTO(id);
        String phone = bill.getOwner().getPhone();
        
        if(emitters.containsKey(phone)) {
            ResponseBodyEmitter emitter = emitters.get(phone);
            try {
                String load = mapper.writeValueAsString(new EmitterPayload(id, amount));
                emitter.send(load);
//              emitter.complete();
            }
            catch (Exception exc) {
                log.warn(exc.getMessage(), exc);
                emitter.completeWithError(exc);
            }
        }
    }
    
}
$(document).ready(function(){

    function connect(){
        let xhr = new XMLHttpRequest();
        xhr.open('GET', '/bankdemo/bills/notify');
        xhr.send();
        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
/*          
                let json = JSON.parse(xhr.responseText);
                let cell = '#balance'+json.id;
                let total = parseFloat($(cell).html(), 2) + json.income;
                $(cell).text(total.toFixed(2));
                alert('+ ' + json.income);
                connect();
*/
            }
            else {
                alert('Request failed: ' + xhr.status + ', ' + xhr.statusText);
            }
        };
        xhr.onerror = function() {
            alert('Request failed: ' + xhr.status + ', ' + xhr.statusText);
        };
        xhr.onprogress = function() {
            let json = JSON.parse(xhr.responseText);
            let cell = '#balance'+json.id;
            let total = parseFloat($(cell).html(), 2) + json.income;
            $(cell).text(total.toFixed(2));
            alert('+ ' + json.income);
            xhr.abort();
            connect();
        };
    };
    (function(){
        connect();
    })();

    $(window).on("beforeunload", function(){
        xhr.abort();
    });
    
});

Суть проблемы: после получения разовой порции данных с бэка на фронт приходится делать реконнект, причём если раскомментить xhr.onload вместо xhr.onprogress, то вдобавок ещё нужно раскомментить на бэке emitter.complete, иначе фронт вообще ничего не отображает (т.е. xhr.onload показывает инфу только после исполнения emitter.complete, на emitter.send не реагирует); но не хочется также и на бэке создавать новый эмиттер на каждый вызов, а переиспользвать ранее зарегистрированый. Подозреваю, что на фронте нужно как-то иначе принимать данные, чтобы эта конструкция рационально работала, но в ДЖС я плохо ориентируюсь.


Ответы (0 шт):