使用fork在子进程中传递和访问请求响应对象

时间:2016-03-01 07:06:19

标签: node.js

我想知道如何使用fork将请求响应对象从父进程传递到子进程。 我做的是

var child = cp.fork(__dirname + '/childProcess.js', req, res);
child.on('message', function(m) 
{
    console.log("child process returned data " + m);
});
child.send("hello");

childProcess.js

var req = process.argv[2];
var res = process.argv[3];
process.on('message', (msg) => 
{
    console.log("req object :-" + req );
    console.log("res object :-" + res);
}
process.send("callparent");

这让我在子进程中未定义。我也试过了child.send("你好",req.socket)。但后来我无法访问子进程中的req方法。它显示为圆形结构。

5 个答案:

答案 0 :(得分:2)

根据docs

child.send(message [,sendHandle] [,callback])

  

可以传递给sendHandle的可选child.send()参数用于将TCP服务器或套接字对象传递给子进程。子进程将接收该对象作为传递给process.on('message')事件上注册的回调函数的第二个参数。

这表明您应该在req函数中发送req.socketchild.send()对象。

此外,您没有从注册到req事件的函数中的第二个参数访问req.socketprocess.on('message')对象。

答案 1 :(得分:1)

您创建服务器,并将句柄发送到客户端。 例如,如果您使用快递,则可以执行以下操作:

<强> Server.js

child= require('child_process').fork('child.js');    
const server = require('net').createServer();
server.on('connection', function(socket)  {    
    child.send('handle', socket);

}).listen(80);

然后你创建一个孩子,并接受句柄:

<强> Child.js

var app=require('express')()
app.get('/',function(req,res){
     console.log(req.headers)
     res.send('Working!')
})  

var myServer=require('http').createServer(app)
process.on('message', function(m, server)  {
  if (m === 'handle') myServer.listen(handle)
});

更多信息:

答案 2 :(得分:1)

其他答案告诉您将套接字发送给孩子并让孩子听取并处理请求,这不是问的问题(如何向孩子发送req / res)。如果您的目标是将套接字传递给子进程并让孩子监听并处理请求,那么这些答案就可以了。如果您的目标是在主进程中侦听和预处理请求,并将这些请求传递给子进程以进行进一步处理,请参阅以下内容:

简短的回答是,您必须实际明确地将请求和响应发送到子进程(它们不会神奇地出现在子进程中)。但是这有一些挑战。

在我们的应用程序中,我们遇到了类似的问题。我们的主要请求处理器检查请求以查看哪个子进程应处理每个请求,然后将该请求发送到子进程(通过child.send())。

在父和子之间传递请求/响应的问题是你必须能够序列化使用send来回发送的对象,特别是请求对象不能被序列化(它是满的循环引用,引用了许多其他对象等。)

所以我们所做的是从子进程需要的请求中提取数据子集,并通过child.send()将“轻量级请求”对象发送到子进程,同时保留对原始数据的引用在父进程的队列中请求。当子进程完成处理时,它会通过parent.send()将其响应数据发送回父进程,并且父进程将其与暂挂队列中的原始请求进行匹配,并使用响应数据满足请求。

这种方法的优点(与在父/子之间制作自定义协议相反)是子请求处理代码仍然在请求/响应对象上运行(只是它们的轻量级版本)。在我们的例子中,它意味着我们可以使用相同的请求处理代码,无论我们是在子进程中调用它,还是让进程监听并直接处理请求。

例如,以下是如何创建一个轻量级,可序列化的请求对象并将其发送到子进程:

var requestData = 
{
    httpVersion: request.httpVersion,
    httpVersionMajor: request.httpVersionMajor,
    httpVersionMinor: request.httpVersionMinor,
    method: request.method,
    url: request.url,
    headers: request.headers,
    body: request.body
}
child.send(requestData);

您也可以使用lodash pick之类的内容来抓取您想要的字段。

使用此解决方案的棘手部分是,如果您需要在请求/响应上调用任何方法,则必须在父级中执行此操作(实际对象所在的位置 - 所有子级都具有所选数据)。

答案 3 :(得分:0)

以下示例可能有助于更好地说明这一点。 它有一个父项和一个子项,父项创建一个服务器,在客户端连接时,套接字句柄被传递给子项。

孩子只有一个消息循环,在其中接收句柄,并且能够通过它接触到客户端。

$ cat foo.js

const cp = require('child_process')
const n = require('net')

if (process.argv[2] === 'child') {
  process.on('message', (m, h) =>  {
    h.end('ok')
  })
}
else {
  const c = cp.fork(process.argv[1], ['child'])
  const s = n.createServer();
  s.on('connection', (h) => {
    c.send({}, h)
  }).listen(12000, () => {
  const m = n.connect(12000)
  m.on('data', (d) => {
    console.log(d.toString())
  })
 })
}

围绕握手的相关部分进行分析。

[pid 12055] sendmsg(11, {msg_name(0)=NULL, msg_iov(1)=[{"{\"cmd\":\"NODE_HANDLE\",\"type\":\"net.Socket\",\"msg\":{},\"key\":\"6::::12000\"}\n", 70}], msg_controllen=24, {cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, {14}}, msg_flags=0}, 0) = 70

[pid 12061] recvmsg(3, {msg_name(0)=NULL, msg_iov(1)=[{"{\"cmd\":\"NODE_HANDLE\",\"type\":\"net.Socket\",\"msg\":{},\"key\":\"6::::12000\"}\n", 65536}], msg_controllen=24, {cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, {12}}, msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 70

[pid 12061] getsockname(12, {sa_family=AF_INET6, sin6_port=htons(12000), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0

[pid 12061] getsockopt(12, SOL_SOCKET, SO_TYPE, [1], [4]) = 0

[pid 12061] write(3, "{\"cmd\":\"NODE_HANDLE_ACK\"}\n", 26) = 26

[pid 12055] <... epoll_wait resumed> {{EPOLLIN, {u32=11, u64=11}}}, 1024, -1) = 1

[pid 12055] recvmsg(11, {msg_name(0)=NULL, msg_iov(1)=[{"{\"cmd\":\"NODE_HANDLE_ACK\"}\n", 65536}], msg_controllen=0, msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 26

如您所见,父和子参与传输套接字句柄的协议,子进程从这样接收的句柄信息中重新创建net.Socket对象。随后,孩子能够处理此连接下的客户端。

这个协议,以及跨进程传输句柄和工作量的方式,是cluster模块的核心和灵魂。

答案 4 :(得分:0)

您不能(至少在 Node.js <= 15.13.0 中不能)将 HTTP 请求或响应对象传递给子进程。因此我想出了一个使用 IPC 的解决方案,我将在这篇文章中分享。

首先使用此内容创建一个 package.json 文件:

{
  "type": "module"
}

然后使用以下内容创建文件 server.js

import * as http from 'http'
import * as child_process from 'child_process'
import * as net from 'net'
import * as fs from 'fs'
import * as crypto from 'crypto'

const ipcPrefix = (process.platform != 'win32' ? '/tmp/' : '\\\\.\\pipe\\') 
                + crypto.randomBytes(8).toString('hex')
const subprocess = child_process.fork('subprocess.js', {
  stdio: ['pipe', 'pipe', 'inherit', 'ipc']
})
let requestNumber = 0

const server = http.createServer()
.listen(8080)
.on('request', (request, response) => {
  if (!subprocess.connected) {
    console.error('Subprocess not connected for: '+request.url)
    response.statusCode = 500
    response.end()
    return
  }
  const {headers, url, method} = request
  const ipcPath = ipcPrefix + requestNumber++ // a unique IPC path
  try {fs.unlinkSync(ipcPath)} catch{} // delete the socket file if it exist already
  let headerChunks = [], headerSize, sizeGotten = 0
  net.createServer() // the IPC server
  .listen(ipcPath, () => subprocess.send({cmd: 'req', headers, url, method, ipcPath}))
  .on('connection', socket => {
    socket.on('close', () => {
      response.end()
      fs.unlinkSync(ipcPath) // clean it up
    })
    socket.once('data', async chunk => {
      headerSize = chunk.readUInt32LE()
      headerChunks.push(chunk)
      sizeGotten += chunk.byteLength
      while (sizeGotten < headerSize) {
        let timer
        const timedOut = await Promise.race([
          // race next packet or timeout timer
          new Promise(resolve => {
            socket.once('data', chunk => {
              headerChunks.push(chunk)
              sizeGotten += chunk.byteLength
              resolve(false)
            })
          }),
          new Promise(resolve => {timer = setTimeout(resolve, 2000, true)})
        ])
        clearTimeout(timer) // to not leave it hanging
        if (timedOut) {
          response.statusCode = 500
          // response.write('lol, bye') // optional error page
          socket.destroy() // this causes the close event which ends the response
          console.error('Subprocess response timed out...')
          return // break the loop and exit function
        }
      }
      const data = Buffer.concat(headerChunks)
      const headerData = data.slice(4, 4+headerSize)
      const header = JSON.parse(headerData.toString())
      response.writeHead(header.statusCode, header.headers)
      if (data.byteLength - 4 - headerSize > 0) {
        response.write(data.slice(4 + headerSize))
      }
      socket.pipe(response) // just pipe it through
      //socket.on('data', response.write.bind(response)) // or do this
      // but set a timeout on the socket to close it if inactive
      socket.setTimeout(2000)
      socket.on('timeout', () => {
        socket.destroy()
        console.log('Subprocess response timed out...')
      })
    })
  })
})

然后创建文件 subprocess.js。与此内容:

import * as net from 'net'

process.on('message', msg => {
  switch (msg.cmd) {
    case 'req': {
      const socket = net.createConnection({path: msg.ipcPath})
      // depending on {headers, url, method} in msg do your thing below
      writeHead(socket, 200, {'testHeader': 'something'})
      socket.end('hello world '+msg.url)
    } break
  }
})

function writeHead(socket, statusCode, headers) {
  const headerBuffer = new TextEncoder().encode(JSON.stringify({statusCode, headers}))
  const headerSize = Buffer.allocUnsafe(4)
  headerSize.writeUInt32LE(headerBuffer.byteLength)
  socket.write(headerSize)
  socket.write(headerBuffer)
}

现在运行服务器 node server 并在浏览器中转到 localhost:8080 以查看其运行情况。

如果您喜欢我的回答,请随时在 GitHub 上赞助我。