admin管理员组文章数量:1356895
I have a HTTP server that connects to a gateway over GRPC. the gateway also connects to other . GRPC microservices. the flow looks like this:
Client -> HttpServer -> GRPC server (gateway) -> GRPC microservice server X
The way i handle errors currently is like so (please let me know if there is better practice) i will only show nessaccery code for brevity
GRPC microservice server X
@GrpcMethod() get(clientDetails: Records.UserDetails.AsObject): Records.RecordResponse.AsObject {
this.logger.log("Get Record for client");
throw new RpcException({message: 'some error', code: status.DATA_LOSS})
}
this simple throws an error to the GRPC client (which works fine)
GRPC Server
@GrpcMethod() async get(data: Records.UserDetails.AsObject, metaData): Promise<Records.RecordResponse.AsObject> {
try {
return await this.hpGrpcRecordsService.get(data).toPromise();
} catch(e) {
throw new RpcException(e)
}
}
Grpc server catches the error which is in turn caught buy the global exception handler (this works fine)
@Catch(RpcException)
export class ExceptionFilter implements RpcExceptionFilter<RpcException> {
catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
if( Object.prototype.hasOwnProperty.call(exception, 'message') &&
Object.prototype.hasOwnProperty.call(exception.message, 'code') &&
exception.message.code === 2
){
exception.message.code = 13
}
return throwError(exception.getError());
}
}
This throws the error back to the Http server (grpc client, works fine)
Now when it gets to the Http server i was hoping i could set up another RPC exception handler and transform the error into a HTTP except. but i'm unsure if it is possible, i have only been using nest for a few days and am yet to full understand it.
Here is an example of what i was hoping to do (code is not working, just example of what i want). id prefer to globally catch the exceptions rather than have try/catch blocks everywhere
@Catch(RpcException)
export class ExceptionFilter implements RpcExceptionFilter<RpcException> {
catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
//Map UNKNOWN(2) grpc error to INTERNAL(13)
if( Object.prototype.hasOwnProperty.call(exception, 'message') &&
Object.prototype.hasOwnProperty.call(exception.message, 'code') &&
exception.message.code === 2
){ exception.message.code = 13 }
throw new HttpException('GOT EM', HttpStatus.BAD_GATEWAY)
}
}
I have a HTTP server that connects to a gateway over GRPC. the gateway also connects to other . GRPC microservices. the flow looks like this:
Client -> HttpServer -> GRPC server (gateway) -> GRPC microservice server X
The way i handle errors currently is like so (please let me know if there is better practice) i will only show nessaccery code for brevity
GRPC microservice server X
@GrpcMethod() get(clientDetails: Records.UserDetails.AsObject): Records.RecordResponse.AsObject {
this.logger.log("Get Record for client");
throw new RpcException({message: 'some error', code: status.DATA_LOSS})
}
this simple throws an error to the GRPC client (which works fine)
GRPC Server
@GrpcMethod() async get(data: Records.UserDetails.AsObject, metaData): Promise<Records.RecordResponse.AsObject> {
try {
return await this.hpGrpcRecordsService.get(data).toPromise();
} catch(e) {
throw new RpcException(e)
}
}
Grpc server catches the error which is in turn caught buy the global exception handler (this works fine)
@Catch(RpcException)
export class ExceptionFilter implements RpcExceptionFilter<RpcException> {
catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
if( Object.prototype.hasOwnProperty.call(exception, 'message') &&
Object.prototype.hasOwnProperty.call(exception.message, 'code') &&
exception.message.code === 2
){
exception.message.code = 13
}
return throwError(exception.getError());
}
}
This throws the error back to the Http server (grpc client, works fine)
Now when it gets to the Http server i was hoping i could set up another RPC exception handler and transform the error into a HTTP except. but i'm unsure if it is possible, i have only been using nest for a few days and am yet to full understand it.
Here is an example of what i was hoping to do (code is not working, just example of what i want). id prefer to globally catch the exceptions rather than have try/catch blocks everywhere
@Catch(RpcException)
export class ExceptionFilter implements RpcExceptionFilter<RpcException> {
catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
//Map UNKNOWN(2) grpc error to INTERNAL(13)
if( Object.prototype.hasOwnProperty.call(exception, 'message') &&
Object.prototype.hasOwnProperty.call(exception.message, 'code') &&
exception.message.code === 2
){ exception.message.code = 13 }
throw new HttpException('GOT EM', HttpStatus.BAD_GATEWAY)
}
}
Share
Improve this question
asked Feb 17, 2020 at 17:40
Jay PoveyJay Povey
7672 gold badges7 silver badges15 bronze badges
3 Answers
Reset to default 4I have been stuck at the same place for some time now. What seems to work is that only the string you send as message gets received at the HTTP server. So the code below as a filter in HTTP server works, but you have to check for status via the message string.
@Catch(RpcException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: RpcException, host: ArgumentsHost) {
const err = exception.getError();
// console.log(err);
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
response
.json({
message: err["details"],
code: err['code'],
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
if(err['details'] === UserBusinessErrors.InvalidCredentials.message){
this.logger.error(e);
throw new HttpException( UserBusinessErrors.InvalidCredentials.message, 409)
} else {
this.logger.error(e);
throw new InternalServerErrorException();
}
I was able to create and return a custom error message from server to client since RpcException
's getError()
method is of type string | object
, its actual object is constructed at runtime. Here's what my implementation looks like
Microservice X
import { status } from '@grpc/grpc-js';
import { Injectable } from '@nestjs/mon';
import { RpcException } from '@nestjs/microservices';
import { CreateUserRequest, CreateUserResponse } from 'xxxx';
interface CustomExceptionDetails {
type: string;
details: string,
domain: string,
metadata: { service: string }
}
@Injectable()
export class UsersService {
users: CreateUserResponse[] = [];
findOneById(id: string) {
return this.users.find(e => e.id === id);
}
createUser(request: CreateUserRequest) {
// verify if user already exists
const userExists = this.findOneById(request.email);
if (userExists) {
const exceptionStatus = status.ALREADY_EXISTS;
const details = <CustomExceptionDetails>{
type: status[exceptionStatus],
details: 'User with with email already exists',
domain: 'xapis.',
metadata: {
service: 'X_MICROSERVICE'
}
};
throw new RpcException({
code: exceptionStatus,
message: JSON.stringify(details) // note here (payload is stringified)
});
}
// create user
const user = <CreateUserResponse>{
id: request.email,
firstname: request.firstname,
lastname: request.lastname,
phoneNumber: request.phoneNumber,
email: request.email,
};
this.users.push(user);
return user;
}
}
Gateway Y Server (HttpExceptionFilter)
import { ArgumentsHost, Catch, ExceptionFilter, HttpException,
HttpStatus } from "@nestjs/mon";
import { RpcException } from "@nestjs/microservices";
import { Request, Response } from 'express';
import { ErrorStatusMapper } from "../utils/error-status-mapper.util";
import { Metadata, status } from '@grpc/grpc-js';
interface CustomExceptionDetails {
type: string;
details: string,
domain: string,
metadata: { service: string }
}
interface CustomException<T> {
code: status;
details: T;
metadata: Metadata;
}
@Catch(RpcException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: RpcException, host: ArgumentsHost) {
const err = exception.getError();
let _exception: CustomException<string>;
let details: CustomExceptionDetails;
if (typeof err === 'object') {
_exception = err as CustomException<string>;
details = <CustomExceptionDetails>(JSON.parse(_exception.details));
}
// **You can log your exception details here**
// log exception (custom-logger)
const loggerService: LoggerService<CustomExceptionDetails> =
new LoggerService(FeatureService["CLIENT/UserAccountService"]);
loggerService.log(<LogData<CustomExceptionDetails>>{ type: LogType.ERROR, data: details });
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
// const request = ctx.getRequest<Request>();
const mapper = new ErrorStatusMapper();
const status = mapper.grpcToHttpMapper(_exception.code);
const type = HttpStatus[status];
response
.status(status)
.json({
statusCode: status,
message: details.details,
error: type,
});
}
}
ErrorStatusMapper-util
import { status } from '@grpc/grpc-js';
import { Status } from "@grpc/grpc-js/build/src/constants";
import { HttpStatus, Injectable } from "@nestjs/mon";
@Injectable()
export class ErrorStatusMapper {
grpcToHttpMapper(status: status): HttpStatus {
let httpStatusEquivalent: HttpStatus;
switch (status) {
case Status.OK:
httpStatusEquivalent = HttpStatus.OK;
break;
case Status.CANCELLED:
httpStatusEquivalent = HttpStatus.METHOD_NOT_ALLOWED;
break;
case Status.UNKNOWN:
httpStatusEquivalent = HttpStatus.BAD_GATEWAY;
break;
case Status.INVALID_ARGUMENT:
httpStatusEquivalent = HttpStatus.UNPROCESSABLE_ENTITY;
break;
case Status.DEADLINE_EXCEEDED:
httpStatusEquivalent = HttpStatus.REQUEST_TIMEOUT;
break;
case Status.NOT_FOUND:
httpStatusEquivalent = HttpStatus.NOT_FOUND;
break;
case Status.ALREADY_EXISTS:
httpStatusEquivalent = HttpStatus.CONFLICT;
break;
case Status.PERMISSION_DENIED:
httpStatusEquivalent = HttpStatus.FORBIDDEN;
break;
case Status.RESOURCE_EXHAUSTED:
httpStatusEquivalent = HttpStatus.TOO_MANY_REQUESTS;
break;
case Status.FAILED_PRECONDITION:
httpStatusEquivalent = HttpStatus.PRECONDITION_REQUIRED;
break;
case Status.ABORTED:
httpStatusEquivalent = HttpStatus.METHOD_NOT_ALLOWED;
break;
case Status.OUT_OF_RANGE:
httpStatusEquivalent = HttpStatus.PAYLOAD_TOO_LARGE;
break;
case Status.UNIMPLEMENTED:
httpStatusEquivalent = HttpStatus.NOT_IMPLEMENTED;
break;
case Status.INTERNAL:
httpStatusEquivalent = HttpStatus.INTERNAL_SERVER_ERROR;
break;
case Status.UNAVAILABLE:
httpStatusEquivalent = HttpStatus.NOT_FOUND;
break;
case Status.DATA_LOSS:
httpStatusEquivalent = HttpStatus.INTERNAL_SERVER_ERROR;
break;
case Status.UNAUTHENTICATED:
httpStatusEquivalent = HttpStatus.UNAUTHORIZED;
break;
default:
httpStatusEquivalent = HttpStatus.INTERNAL_SERVER_ERROR;
break;
}
return httpStatusEquivalent;
}
}
I have the same problem. Then I found a solution which works for me.
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response.status(status).json({
success: false,
statusCode: status,
message: exception.message,
path: request.url,
});
}
}
and in the controller, I use pipe
method to catch the error from the GRPC service as
@Post('/register')
@Header('Content-Type', 'application/json')
async registerUser(@Body() credentials: CreateUserDto) {
return this.usersService.Register(credentials).pipe(
catchError((val) => {
throw new HttpException(val.message, 400);
}),
);
}
If you’re familiar with RxJS
you probably already saw that the client (what consumes our microservice) returns an observable, what it essentialy means you can apply other operators, here I used pipe
, to your observable stream and modify response to your needs.
本文标签: javascriptNestJs transform GRPC exception to HTTP exceptionStack Overflow
版权声明:本文标题:javascript - NestJs transform GRPC exception to HTTP exception - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1743945790a2566378.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论