我如何 return 来自环回 4 中控制器的错误?

How do I return an error from a Controller in Loopback 4?

我有一个控制器方法

// ... inside a controller class

@get('/error', {})
async error() {
  throw new Error("This is the error text");
}

我从这个错误前端得到的响应是:

{ "error": { "statusCode": 500, "message": "Internal Server Error" } }

我希望错误是:

{ "error": { "statusCode": 500, "message": "This is the error text" } }

如何 return Loopback 4 中的控制器出错?

来自 LoopBack 团队的问候

在您的控制器或存储库中,您应该完全按照问题中所示抛出错误。

现在,当 LoopBack 捕获到错误时,它会调用 reject 操作来处理错误。 reject 的内置实现通过 console.error 和 returns 带有 4xx/5xx 错误代码和描述错误的响应正文的 HTTP 响应记录消息。

默认情况下,LoopBack 隐藏 HTTP 响应中的实际错误消息。这是一项安全措施,可防止服务器泄露潜在的敏感数据(无法打开的文件路径、无法访问的后端服务的 IP 地址)。

在后台,我们使用 strong-error-handler 将错误对象转换为 HTTP 响应。该模块提供两种模式:

  • 生产模式(默认):5xx 错误不包含任何附加信息,4xx 错误包含部分信息。
  • 调试模式 (debug: true):响应中包含所有错误详细信息,包括完整的堆栈跟踪。

可以通过将以下行添加到应用程序构造函数来启用调试模式:

this.bind(RestBindings.ERROR_WRITER_OPTIONS).to({debug: true});

在我们的文档中了解更多信息:Sequence >> Handling errors

或者,您可以实现自己的错误处理程序并将其绑定为序列操作 reject。请参阅我们的文档中的 Customizing sequence actions

export class MyRejectProvider implements Provider<Reject> {
  constructor(
    @inject(RestBindings.SequenceActions.LOG_ERROR)
    protected logError: LogError,
    @inject(RestBindings.ERROR_WRITER_OPTIONS, {optional: true})
    protected errorWriterOptions?: ErrorWriterOptions,
  ) {}

  value(): Reject {
    return (context, error) => this.action(context, error);
  }

  action({request, response}: HandlerContext, error: Error) {
    const err = <HttpError>error;

    const statusCode = err.statusCode || err.status || 500;
    const body = // convert err to plain data object

    res.statusCode = statusCode;
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.end(JSON.stringify(body), 'utf-8');

    this.logError(error, statusCode, request);
  }
}

对于我的情况,我在 sequence.ts 文件中找到了一个 catch。在 catch 中,它检查错误的状态代码是否为 4xx,如果不是,它只返回一个匿名的 500。

这是我正在寻找的执行逻辑的代码:

// sequence.ts
...
} catch (err) {
  console.log(err);
  let code: string = (err.code || 500).toString();
  if (code.length && code[0] === '4') {
    response.status(Number(code) || 500);
    return this.send(response, {
      error: {
        message: err.message,
        name: err.name || 'UnknownError',
        statusCode: code
      }
    });
  }
  return this.reject(context, err);
}
...

这是告诉它做什么的方法:

// ... inside a controller class

@get('/error', {})
async error() {
  throw {
    code: 400,
    message: "This is the error text",
    name: "IntentionalError"
  }
}

如果你只想显示错误信息,你只需扩展 Error 对象并像下面那样抛出它。 (环回文档无论如何都没有提到这一点)

避免使用 5xx 错误并使用 4xx 错误向用户显示一些重要的事情是最佳实践,因此 Loopback4 是这样实现的。

class NotFound extends Error {
  statusCode: number

  constructor(message: string) {
    super(message)
    this.statusCode = 404
  }
}

...

if (!await this.userRepository.exists(id)) {
  throw new NotFound('user not found')
}

为了抛出自定义验证错误,我使用了这个方法:

private static createError(msg: string, name?: string): HttpErrors.HttpError {
    const error = new HttpErrors['422'](msg);
    error.name = name ?? this.name;
    return error;
}

此处的捕获错误示例是针对 defaultSequence 的,覆盖了 handle 方法。 但是现在的应用程序模板使用 MiddlewareSequence。

所以这里是例子,如何修改中间件序列中的响应,你可以使用这个例子:

import { Middleware, MiddlewareContext } from '@loopback/rest';

export const ErrorMiddleware: Middleware = async (middlewareCtx: MiddlewareContext, next) => {
    // const {response} = middlewareCtx;
    try {
        // Proceed with next middleware
        return await next();
    } catch (err) {
        // Catch errors from downstream middleware
        // How to catch specific error and how to send custom error response:
        if (HttpErrors.isHttpError(err) || (err as HttpErrors.HttpError).statusCode) {
            const code: string = (err.statusCode || 500).toString();
            if (code.length && code[0] === '4') {
                response.status(Number(code) || 500);
                return response.send({
                    error: {
                        message: err.message,
                        name: err.name || 'UnknownError',
                        statusCode: code
                    }
                });
            }
        }
        throw err;
    }
};

并在application.ts

中注册中间件
this.middleware(ErrorMiddleware);