ค้นหาเว็บไซต์

javascript - ข้อผิดพลาด: สิ่งนี้เกิดจากข้อบกพร่องใน Node.js หรือการใช้งานภายในของ Node.js ไม่ถูกต้อง


ฉันกำลังสร้างกลไกการตรวจสอบสิทธิ์สำหรับบริการของฉัน และในบางช่วงฉันมีปัญหาเกี่ยวกับคุกกี้ คุณสามารถดูเพิ่มเติมได้ที่นี่ ดังนั้นฉันจึงแก้ไขปัญหานี้แล้ว

ปัญหาคือฉันพยายามส่งคุกกี้ผ่านการร้องขอ 2 ครั้ง หน้า Next.js ของฉันส่งการร้องขอไปยัง API ภายในของมันเอง แล้วหลังจากนั้น API ภายในจึงส่งการร้องขอนี้ไปยังแบ็กเอนด์

วิธีแก้ปัญหานี้ง่ายมาก สิ่งที่ฉันต้องทำคือ ตั้งคุกกี้ในฝั่งเซิร์ฟเวอร์และส่งกลับในส่วนหัว นี่คือลักษณะของการไหลของงาน

นี่คือหน้าตาของมัน, จุดสิ้นสุดใน Next.js ฝั่งหน้า. นอกจากข้อมูลในคำตอบ, มันยังได้รับเฮดเดอร์, ที่มีการตั้งคุกกี้ (คำตอบจากฝั่งหลัง) และส่งมันในเฮดเดอร์ของคำตอบ, ที่จะถูกส่งไปยังฝั่งหน้า, ที่ซึ่งคุกกี้จะถูกตั้งค่า:

import { NextApiRequest, NextApiResponse } from "next";
import { AxiosError } from "axios";
import { api } from "../../../api";

export default async (
  req: NextApiRequest,
  res: NextApiResponse
) => {
  try {
    const { data, headers } = await api.post('/user/sign-in', req.body)

    if (headers["set-cookie"]) {
      res.setHeader("Set-Cookie", headers["set-cookie"]);
    }

    return res.json(data)
  } catch (error) {
    return res
      .status((error as AxiosError).response?.status as number)
      .json((error as AxiosError).response?.data);
  }
}

และจุดสิ้นสุดบนแบ็กเอนด์:

import { Response as Res } from 'express';
import * as dayjs from 'dayjs';
...
async signIn(@Body() signInUserDto: SignInUserDto, @Response() res: Res) {
  const { _at, _rt } = await this.userService.signIn(signInUserDto);

  res.cookie('_rt', _rt, {
    httpOnly: true,
    expires: dayjs().add(7, 'days').toDate()
  });

  return res.send(_at);
}

และนี่คือปัญหา เพราะคลาส Response ของ express ฉันจึงได้รับคำเตือนนี้อยู่ตลอด:

Error: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
Please open an issue with this stack trace at https://github.com/nodejs/node/issues

    at new NodeError (node:internal/errors:371:5)
    at assert (node:internal/assert:14:11)
    at ServerResponse.detachSocket (node:_http_server:249:3)
    at resOnFinish (node:_http_server:819:7)
    at ServerResponse.emit (node:events:390:28)
    at onFinish (node:_http_outgoing:830:10)
    at callback (node:internal/streams/writable:552:21)
    at afterWrite (node:internal/streams/writable:497:5)
    at afterWriteTick (node:internal/streams/writable:484:10)
    at processTicksAndRejections (node:internal/process/task_queues:82:21)

มันแน่นอนว่ามาจากลักษณะของฟังก์ชัน signIn นี้ เพราะฉันพยายามที่จะ return แบบนี้ - return this.userService.signIn(signInUserDto) - และมันทำงานได้ แต่ในกรณีนี้ฉันไม่สามารถใช้ cookie ได้

ดังนั้น คำถามของฉันคือ - ข้อผิดพลาดนี้คืออะไร? ฉันสามารถเพิกเฉยมันได้ไหม? ถ้าไม่ใช่ แล้วฉันจะแก้ไขมันได้อย่างไร?

ขอบคุณล่วงหน้า!

คำตอบ #1

สรุปย่อ

สุดท้ายฉันก็สามารถแก้ไขข้อผิดพลาดนี้ได้ ก่อนอื่นเลย ตามที่ฉันได้กล่าวไป ข้อมูลของฉันต้องผ่าน 2 API จากแบ็กเอนด์ไปยังฟรอนต์เอนด์ API และต่อเมื่อฟรอนต์เอนด์ API นี้จึงจะส่งคำขอนี้ไปยังฟรอนต์เอนด์ที่แท้จริง

ดังนั้น สิ่งที่ฉันทำคือเพียงส่งคืนโทเค็น 2 ตัว - refresh และ access - เป็นตัวเนื้อหา

@ApiOperation({ summary: 'Resource for sign in user.' })
@ApiResponse({ status: 200, type: TokensDto })
@Post('/sign-in')
async signIn(@Body() signInUserDto: SignInUserDto) {
  return this.userService.signIn(signInUserDto);
}

จากนั้น ในฝั่งหน้าเว็บ ผมได้ติดตั้ง cookie และ @types/cookie และในจุดเชื่อมต่อนี้ของฝั่งหน้าเว็บ ใน headers ผมเพียงแค่ทำการแปลงโทเค็นรีเฟรชนี้จาก payload ของ body เป็นแบบ serialized และลบออกจาก body

import { NextApiRequest, NextApiResponse } from "next";
import { AxiosError } from "axios";
import { api } from "../../../api";
import { serialize } from 'cookie';

export default async (
  req: NextApiRequest,
  res: NextApiResponse
) => {
  try {
    const { data } = await api.post('/user/sign-in', req.body)

    res.setHeader('Set-Cookie', serialize(
      '_rt',
      data._rt,
      { path: '/', httpOnly: true })
    );
    delete data._rt

    return res.json(data)
  } catch (error) {
    return res
      .status((error as AxiosError).response?.status as number)
      .json((error as AxiosError).response?.data);
  }
}

และมันทำงานได้อย่างสมบูรณ์แบบ ฉันไม่มีข้อผิดพลาด Node.js นี้อีกต่อไปเนื่องจากการตอบสนองด้วยคลาส Express response และฉันสามารถตั้งค่า cookie ได้

แก้ไข

ฉันได้ปรับปรุงโค้ดนี้ให้ดียิ่งขึ้นโดยใช้ fastify และในทั้งกระบวนการ pipeline คุกกี้จะถูกตั้งค่าในส่วน header ก่อนอื่น ในฝั่ง back-end ติดตั้ง @fastify/cookie และ @nestjs/platform-fastify จากนั้น เพิ่มสิ่งนี้ในไฟล์ที่คุณเริ่มแอป Nest.js ของคุณ:

import {
  FastifyAdapter,
  NestFastifyApplication
} from '@nestjs/platform-fastify';
import { fastifyCookie } from '@fastify/cookie';

async function bootstrap() {
  const PORT = process.env.PORT || 3002;
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter()
  );

  await app.register(fastifyCookie, {
    secret: 'my-secret'
  });

สิ่งนี้จะอนุญาตให้คุณใช้ FastifyReply จาก fastify, สิ่งนี้จะกำจัดข้อผิดพลาด Node.js นี้ ในฐานะคลาสการตอบสนอง:

import { FastifyReply } from 'fastify';

@ApiTags('User')
@Controller('user')
export class UserController {
  constructor(private userService: UserService) {}

  @Post('/sign-in')
  async signIn(
    @Body() signInUserDto: SignInUserDto,
    @Res({ passthrough: true }) res: FastifyReply
  ) {
    const { _at, _rt } = await this.userService.signIn(signInUserDto);

    res.setCookie('_rt', _rt);

    return res.send(_at);
  }
...

และขั้นตอนสุดท้าย บนจุดสิ้นสุดของหน้าแรก ใช้ cookie ทำการแยกวิเคราะห์คู๊กกี้นี้และส่งไปยังหน้าแรก

const { data, headers } = await api.post('/user/sign-in', req.body)

if (headers["set-cookie"]) {
  const refreshToken = headers["set-cookie"][0].split('=')[1];
  res.setHeader('Set-Cookie', serialize(
    '_rt', refreshToken, { path: '/', httpOnly: true })
  );
}

return res.json(data)

และนี่คือวิธีที่ดีที่สุดที่ฉันพบ เพราะมันช่วยให้คุณสามารถส่งคุกกี้ในเฮดเดอร์ตลอดทั้งพายป์ไลน์ ไม่ใช่ในบอดี้ แล้วจึงลบมัน และวิธีแก้ปัญหานี้ก็ขจัดข้อผิดพลาดแปลก ๆ ของ Node.js ได้