Search code examples
javascriptnode.jsexpressprogress-bartesseract.js

ERR_HTTP_HEADERS_SENT when sending progressbar update (node.js/express)


I'm trying to make a progress bar to monitor a Tesseract.recognize() call but when I try sending an update to change the progress bar I get this error:

[ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

This is what's running in the routes.js file:

Tesseract.recognize(
    '/app/'+statistic.path_out_new
    ,'eng'
    , { logger: m => {
        console.log(m);
         res.render('pages/uploadPicture.ejs', {
              user : req.user
              ,progress : m
         });   
    } }
)

.then(({ data: { text } }) => {
    res.render('pages/uploadPage.ejs', {
        user : req.user
        ,progress : text
    });  
});

Here is the relevant part of the HTML from the EJS template, uploadPage.ejs:

<section><div></div><progress value="<%=progress.progress%>" text="<%=progress.status%>" /></section>

I have read this question and I think the problem is caused by sending multiple headers every time there is an update to the progress bar, but how do I send information about how the function is progressing to the user (so that the progress bar updates) without sending a header, too? I tried res.write() as suggested here but that let to the progress bar page timing out, not updating.


Solution

  • You can't only through res: res.render purpose is to send all the result at once and then close the HTTP request (connection could be kept open for keep alive). The best choice to show the progress of a job is a WebSocket.

    A little improved version of your code, without using WebSockets could be:

    var status = {}
    
    app.get("/update/:id", (req, res) => res.json(status[req.params.id]));
    
    app.get("/tesseract", (req, res) => {
      const id = ... ; // here generate a unique job id
    
      Tesseract.recognize('/app/'+statistic.path_out_new, 'eng', { logger: m => {
        // here update status[id] reflecting the progress
      }}).then(data => {
        // here update status[id] reflecting the end of the job
      });
    
      res.render("pages/uploadPage.ejs", { id, user: req.user });
    });
    

    The page rendered should call (under a setTimeout) /update/${id} in ajax to get the updated status util it represents a job completed status.

    Hope this helps.