Node.js无法在发送标头后设置标头引入res.render()后出现错误

时间:2018-08-28 13:53:11

标签: javascript node.js header request-headers response-headers

我有以下代码段:

write.csv(mtcars, 'mtcars.csv')

create_issue_spotter_data = function(df) {
 df$new_column = 0
 df = df[df$mpg < 20, ]
 return(df)
}



ui <- fluidPage(

  fluidRow(align = "center", downloadButton("download_btn")),
  fluidRow(align = "center", plotOutput("distPlot"))

)

server <- function(input, output) {

  start <- 1

  file <- reactiveFileReader(
    intervalMillis = 1000 * 60 * 5, 
    session = NULL, 
    filePath = 'mtcars.csv', 
    readFunc = readr::read_csv
  )

  this_df = eventReactive(file(), {
    if (start != 1) {
      print(paste("Now updating the data. Date:", Sys.time()))
      saved_df <<- create_issue_spotter_data(file())
      df <- saved_df
    } else {
      start <<- start + 1
      print(paste("Everything's good at", Sys.time()))
      df <- saved_df
    }
    df
  })

  output$download_btn <- downloadHandler(
    filename = paste0(paste("Issue", "Spotter", gsub("-", "_", Sys.Date()), sep = "_"), ".pdf"),
    content = function(file) {
    pdf(file)
    p = build_cluster_viz(this_df(), download = TRUE)
    print(p)
    dev.off()
  }, contentType = "pdf")

  output$distPlot <- renderPlot({
    plot(this_df()$mpg, this_df()$disp)
  })
}

我评论为“从服务器内存中发送文件的聪明方法”的代码来自这篇文章: Node Express.js - Download file from memory - 'filename must be a string'

这是从内存中获取一个字符串,并将其作为.txt文件提供给客户端。

此代码曾经有效。

然后我决定添加 app.post('/pst', function(req, res) { var data = req.body.convo; res.render('waiting.ejs'); //ADDED THIS myFunc(data).then(result => { res.render('success.ejs'); //THEN THIS //--------------------------------- //clever way to send text file to client from the memory of the server var fileContents = Buffer.from(result, 'ascii'); var readStream = new stream.PassThrough(); readStream.end(fileContents); res.set('Content-disposition', 'attachment; filename=' + fileName); res.set('Content-Type', 'text/plain'); readStream.pipe(res); //-------------------------------------- }).catch( ..... 行,但出现此错误:

res.render('waiting.ejs');

然后我尝试在代码将.txt文件发送到客户端之前和之后添加另一个res.render()[在这种情况下为Error: Can't set headers after they are sent. ]。

错误仍然存​​在。另外,也没有重定向到success.ejs,换句话说,res.render('success.ejs');从未工作过,尽管它是否放在那段代码之后。

3 个答案:

答案 0 :(得分:1)

您将必须检查express.js源代码(here):

res.render = function render(view, options, callback) {
  var app = this.req.app;
  var done = callback;
  var opts = options || {};
  var req = this.req;
  var self = this;

  // support callback function as second arg
  if (typeof options === 'function') {
    done = options;
    opts = {};
  }

  // merge res.locals
  opts._locals = self.locals;

  // default callback to respond
  done = done || function (err, str) {
    if (err) return req.next(err);
    self.send(str);
  };

  // render
  app.render(view, opts, done);
};

您可以看到,当您使用res.render()方法时,它将完成的回调传递给app.render(...)source code),然后它将done传递给{{1 }}等

最后,如果成功,它将用tryInitView调用done回调,如果失败则用str调用回调。然后,它会在err回调中触发res.send(),这只会阻止您在此之后设置标头。

答案 1 :(得分:1)

   app.post('/pst', function(req, res) {
            var data = req.body.convo;

            myFunc(data).then(result => {


          

            //---------------------------------
            //clever way to send text file to client from the memory of the server
            var fileContents = Buffer.from(result, 'ascii');
            var readStream = new stream.PassThrough();
            readStream.end(fileContents);
            res.set('Content-disposition', 'attachment; filename=' + fileName);
            res.set('Content-Type', 'text/plain');
            readStream.pipe(res);
             res.redirect(`/success`);  //THEN THIS
            //--------------------------------------

            }).catch( .....

当您使用app.use方法添加中间件来表达(基于connect建立)时,您将在connect中将项目附加到Server.prototype.stack上。  服务器收到请求后,将在堆栈上进行迭代,并调用(request,response,next)方法。

问题是,如果在一个中间件项目中写入响应主体或标头(看起来是由于某种原因,要么是由于某种原因),但没有调用response.end(),而您调用了{{1 }},然后在核心Server.prototype.handle方法完成后,就会注意到:

next()

因此,它将引发错误。但是它引发的错误只是这个基本响应(来自连接http.js源代码:

there are no more items in the stack, and/or
that response.headerSent is true.

有问题的中间件在不调用res.statusCode = 404; res.setHeader('Content-Type', 'text/plain'); res.end('Cannot ' + req.method + ' ' + req.url); 的情况下设置了响应标头,并调用了next(),这使express的服务器感到困惑。 因此您可以通过response.end()设置标题。现在,如果您尝试再次渲染,则会引发错误。

res.render()

答案 2 :(得分:1)

res.render()函数编译您的模板,在其中插入本地语言,并从这两件事中创建html输出。这就是为什么会出现错误。 不要两次使用它,因为它发送响应。