【问题标题】:node.js express, a very strange behaviournode.js express,一个很奇怪的行为
【发布时间】:2017-12-12 10:11:20
【问题描述】:

我有一个 Express 服务器正在运行。

我所做的是从 HTML 上传一个 Excel 文件,然后 Express 解析该文件并进行一些计算。

在 Excel 文件中,每一行都有用户地址的信息。

对于每个地址,我们的 Express 服务器将使用谷歌地图地理编码 API 来计算纬度和经度。

但是,由于 Google 每秒不接受超过 50 次对其地理编码 API 的请求,所以我必须使用 settimeout 来延迟计算。

例如,如果 Excel 文件有 50 个地址,那么我必须对每个地址使用 settimeout 以避免该速率限制。

这是我使用 settimeout 计算纬度、经度的代码

createFromFile(req, res) { 
    var form = new formidable.IncomingForm();
    return form.parse(req, function (err, fields, files) {
      if (err)
        return res.status(500).json({error: err.message})

      var workbook = new exceljs.Workbook();
      return workbook.xlsx.readFile(files.excelfile.path).then(function() {
        // use workbook
        var worksheet = workbook.getWorksheet(1)
        var data = []
        for (var i=2; i<=worksheet.rowCount; i++) {
          data.push({
            from_name: worksheet.getCell('A'+i).value + '',
            from_address: worksheet.getCell('B'+i).value + '',
            from_phone: worksheet.getCell('C'+i).value + '',
            receiver_name: worksheet.getCell('D'+i).value + '',
            receiver_address: worksheet.getCell('E'+i).value + '',
            receiver_phone: worksheet.getCell('F'+i).value + '',
            note: worksheet.getCell('H'+i).value + ''
          })
        }

        var delay = function(t) {
          return new Promise(function(resolve)           {
            setTimeout(resolve, t)
          })
        }

        return Promise.all(data.map(function(item, i) {
          return function() {
            return delay(750*i).then(function() {
              winston.log('debug', 'process for item '+i)
              return geocoder.geocode(item.from_address).then(function(geo_data) {
                data[i].from_coord = {
                  lat: geo_data[0].latitude,
                  lng: geo_data[0].longitude
                }
                return geocoder.geocode(item.receiver_address).then(function(geo_data) {
                  data[i].receiver_coord = {
                    lat: geo_data[0].latitude,
                    lng: geo_data[0].longitude
                  }
                })
              })
              .catch(function(geo_error) {
                winston.log('error', 'geo_error', {error: geo_error})
                throw new Error('Address in line ' + i + ' is not valid')
              })
            })
          }()
        }))
        .then(function() {
          winston.log('debug', 'we are done calculating location')
          return res.status(201).json(data)
        })
      })
      .catch(function(e) {
        winston.log('error', 'an error occurred')
        return res.status(500).json({error: e.message})
      })
    })
  }

下面是我调用 Express API 的代码,我使用 React 来做前端工作并使用 javascript fetch api 来请求服务器。

startUploadFile() {
  this.props.showLoader()
  let data = new FormData()
  data.append('excelfile', this.state.selectedFile)
  data.append('name', 'excelfile')
  var me = this
  fetch(Const.restServer + '/create-from-file', {
    headers: {
      'Access-Token': this.props.access_token,
    },
    method: 'POST',
    body: data
  })
  .then(function(r) {
    return r.json()
  })
  .then(function(r) {
    if (r.hasOwnProperty('error'))
      throw new Error(r.error)

    me.props.hideLoader()
    me.props.showDialog('success', 'Process Complete')
  })
  .catch(function(e) {
    console.log(e)
    me.props.hideLoader()
    me.props.showDialog('error', e.message)
  })
}

我的问题是当我使用上述代码在浏览器上上传文件时,我在快速日志文件中看到两个请求。像这样的:

我也会在这里提供我的代码来记录每个请求的信息

app.use(function(req, res, next) {
  winston.log('debug', 'call api:', {
    api: req.url, requestMethod: req.method
  })
  res.header("Access-Control-Allow-Origin", "*");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept, Access-Token, Web-Admin-Request"
  );
  next()
});

function isAuthenticated(req, res, next) {
  /**
  ** Rest API Middleware check if access_token is valid
  */
  let accessToken = req.body.accessToken || req.get('Access-Token')

  // bunch of codes to check accessToken
  next()
}

app.post('/order/create-from-file', isAuthenticated, orderController.createFromFile);

我不明白为什么会这样。如果我使用 Postman 并选择文件并上传,它工作正常 - 日志中只有一个请求。

谁能告诉我是什么原因。我觉得这是 Express 的错误。我的 Express 版本是 4.15.2

【问题讨论】:

  • 您可能想使用stackoverflow.com/posts/44982171/edit 来编辑/更新您的问题,并在浏览器开发工具中添加网络窗格显示的日志。如果这表明浏览器实际上实际上发送了两次 POST 请求,那么这不会是 Express 中的错误。另一方面,如果这没有显示浏览器发送了两次,而是 Express 以某种方式处理了浏览器实际上没有发送的第二个 POST 请求,那么是的,这显然是 Express 中的一个错误
  • 您是否有可能看到两个 POST 请求,因为第一个来自您的 startUploadFile() 函数,第二个是默认表单帖子(由您从和调用 startUploadFile() 的同一个按钮触发由浏览器自动执行)?如果是这样,您需要阻止按钮的默认操作。如果这可能是错误,我们可以通过查看上传的 HTML 以及如何调用 startUploadFile() 来更具体地提供帮助。
  • @jfriend00 谢谢,但我不使用表单中的按钮,我只是在其上声明一个按钮和一个点击处理程序。 startUploadFile() 是点击处理程序。而且因为我的 api 服务器与这个 HTML 页面有不同的域名,如果表单自动提交它也无法到达我的 api 服务器。
  • 你确定startUploadFile()被调用一次还是两次?
  • @sideshowbarker 在我的 chrome 网络监控工具中,有 2 个请求,但第一个是 OPTIONS 请求,第二个是真正的 POST 请求。没有任何其他 POST 请求。

标签: javascript node.js express cors fetch-api


【解决方案1】:

您的代码添加到请求中的 Access-Token 请求标头会触发浏览器在尝试 POST 请求之前发送 CORS 预检 OPTIONS 请求。

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests 提供了更多细节,但要点是,只要您的代码将 Access-Token 标头添加到请求中,您就无法阻止浏览器进行额外的预检 OPTIONS 请求,因为它是作为 CORS 协议的一部分,浏览器会自动执行的操作。

使用 Postman 时看不到额外请求的原因是它不执行 CORS 预检 OPTIONS 请求 - 只有浏览器发送它,并且仅适用于 XHR/Fetch 从前端 JavaScript 代码发出的请求在特定来源的浏览器中运行(Postman 不是)。

【讨论】:

  • 我知道 OPTIONS 请求,但我不明白的是第二个 POST 请求。请仔细看我附上的截图,你会看到第二个 POST 请求。我不明白它为什么会出现,因为我只调用了一次 startUploadFile()。
  • 第二个 POST 请求与第一个重叠。所以在我的例子中,有两个进程在做这项工作。
【解决方案2】:

好吧,最后我可以通过将“Access-Token”传递到请求正文而不是请求标头来解决这个问题(现在,我的服务器总是只接收一个请求)。感谢@sideshowbarker,因为您的评论促使我采用这种方法。

我仍然认为这个问题是Express中的一个错误,但是因为它没有发生在我本地的开发环境中,所以我不会向他们发送报告。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-26
    • 2012-09-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多