I found ServiceStack 4 unwrap the exception automatically, how to prevent that?
For example, I have the following API exposed.
public async Task GET(XXXRequest request)
{
try
{
throw new Exception("#inner");
}
catch(Exception ex)
{
throw new Exception("#outer", ex);
}
}
And I have a logger capturing the errors
Plugins.Add(new RequestLogsFeature()
{
EnableRequestBodyTracking = true,
EnableResponseTracking = true,
EnableErrorTracking = true,
RequestLogger = new CustomRequestLogger()
});
class CustomRequestLogger : InMemoryRollingRequestLogger
{
public override void Log(IRequest request, object requestDto, object response, TimeSpan requestDuration)
{
base.Log(request, requestDto, response, requestDuration);
// handle errors
HttpError httpError = response as HttpError;
if (httpError != null)
{
// log error
// httpError is #inner
return;
}
}
}
The problem is that: I only captured the inner exception, the outer exception disappeared
You can disable this in your AppHost with:
SetConfig(new HostConfig {
ReturnsInnerException = false,
});
Related
Spring boot was able to handle various errors using #RestControllerAdvice. The code created by spring boot is as follows.
#RestControllerAdvice
public class ControllerExceptionHandler {
// #valid에서 바인딩 에러가 발생
#ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException ==> " + e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.WRONG_INPUT_VALUE, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
// 사용자 생성시 이메일이 중복된 경우
#ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<ErrorResponse> handleDuplicateEmailException(DuplicateEmailException e) {
log.error("handleDuplicateEmailException ==> " + e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.DUPLICATE_EMAIL_VALUE);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
...
}
Currently, I was looking for features similar to #RestControllerAdvice while using NestJs to handle errors, and I found out about ExceptionFilter. I wrote the code, but I had a question after writing it. If we process the error like this, we can only process errors related to HttPexception, right?
I want to handle other errors globally besides HttPexception. However, unlike Spring Boot's #RestControllerAdvice, ExceptionFilter does not seem to be able to handle many errors in a single class. Am I using it wrong?
#Catch()
export class ExceptionHandler 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({
statusCode: status,
message: exception.message,
path: request.url
});
}
}
You have to pass exception class to #Catch() decorator.
If you want to catch all http exceptions you can do
import { ExceptionFilter, Catch } from '#nestjs/common';
#Catch(HttpException)
export class ExceptionHandler 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({
statusCode: status,
message: exception.message,
path: request.url
});
}
}
This will catch all errors thrown when executing any route.
You can also pass a few exception classes to catch only particular exceptions.
#Catch(BadRequestException, UnauthorizedException)
You can read more at https://docs.nestjs.com/exception-filters
I need to write an http header interceptor to add Authorization header, if there is a 401 error, submit another request for a new token, then resubmit the original request with the new token.
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
return next.handle().pipe(
catchError(async error => {
if (error.response.status === 401) {
const originalRequest = error.config;
var authRes = await this.authenticationService.getAccessToken();
this.authenticationService.accessTokenSubject.next(authRes.access_token);
// I need to resubmit the original request with the new token from here
// but return next.handle(originalRequest) doesn't work
}
return throwError(error);
}),
);
}
But next.handle(originalRequest) doesn't work. How to resubmit the original request in the interceptor? Thank you very much in advance for your help.
I just encountered a similar problem, where I can catch the exception from exception filter but can't do so in interception layer.
So I looked up the manual and found it says:
Any exception thrown by a guard will be handled by the exceptions layer
(global exceptions filter and any exceptions filters that are applied to the current context).
So, if the exception is thrown from AuthGuard context(including the validate method in your AuthService), probably better to move the additional logic by extending the Authguard
like this:
export class CustomizedAuthGuard extends AuthGuard('strategy') {
handleRequest(err, user, info, context, status) {
if (err || !user) {
// your logic here
throw err || new UnauthorizedException();
}
return user;
}
}
or simply using customized exception filter.
It's been a while since the question but maybe it will help someone.
Ok, suppose that we need handle unauthorize exception out of route and guards, maybe service to service. So you can implement a interceptor like that and add some logic to get some data if needed, Ex: inject some Service in the interceptor.
So, throw an unauthorize exception and we are going to intercept it:
#Injectable()
export class UnauthorizedInterceptor implements NestInterceptor {
constructor(
private readonly authService: AuthService,
private readonly httpService: HttpService,
) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError((err) => {
const {
response: { status, config },
} = err;
// assuming we have a request body
const jsonData = JSON.parse(config.data);
if (status === HttpStatus.UNAUTHORIZED) {
// We can use some data in payload to find user data
// here for example the user email
if (jsonData?.email) {
return
from(this.authService.getByUserEmail(jsonData.email)).pipe(
switchMap((user: User) => {
if (user) {
// Ex: we can have stored token info in user entity.
// call function to refresh access token and update user data
// with new tokens
return from(this.authService.refreshToken(user)).pipe(
switchMap((updatedUser: User) => {
// now updatedUser have the new accessToken
const { accessToken } = updatedUser;
// set the new token to config (original request)
config.headers['Authorization'] = `Bearer ${accessToken}`;
// and use the underlying Axios instance created by #nestjs/axios
// to resubmit the original request
return of(this.httpService.axiosRef(config));
}),
);
}
}),
);
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
} else {
return throwError(() => new HttpException(err, Number(err.code)));
}
}),
);
}
}
problem
How to get response body on invoke next context using custom middle ware ??
After reach to line of await _next.Invoke(context) from debug;
not return json data from action result getusermenu
[HttpGet(Contracts.ApiRoutes.Security.GetUserMenus)]
public IActionResult GetUserMenu(string userId)
{
string strUserMenus = _SecurityService.GetUserMenus(userId);
return Ok(strUserMenus);
}
I need to get response body from action result above
header :
key : Authorization
value : ehhhheeedff .
my code i try it :
public async Task InvokeAsync(HttpContext context, DataContext dataContext)
{
// than you logic to validate token
if (!validKey)
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await context.Response.WriteAsync("Invalid Token");
}
//if valid than next middleware Invoke
else
{
await _next.Invoke(context);
// i need after that get result on last of thread meaning return data of usermenu
}
}
}
public static class TokenExtensions
{
public static IApplicationBuilder UseTokenAuth(this IApplicationBuilder builder)
{
return builder.UseMiddleware<TokenValidateMiddleware>();
}
}
[no return data from access token][1]
https://i.stack.imgur.com/PHUMs.png
if(validtoken)
{
continue display getusermenuaction and show result
}
when valid token it return data like below on browser googlechrom
[
{
"form_name": "FrmAddPrograms",
"title": "Adding Screens",
"url": "",
"permissions": {
"Insert": "True",
"Edit": "True",
"Read": "True",
"Delete": "True",
"Print": "True",
"Excel": "False",
"RecordList": "False"
}
},
but on my app browser return
not valid token
Try to get response body in custom middleware using below code:
public class CustomMiddleware
{
private readonly RequestDelegate next;
public CustomMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (!validKey)
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await context.Response.WriteAsync("Invalid Token");
}
//if valid than next middleware Invoke
else
{
Stream originalBody = context.Response.Body;
try
{
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
await next(context);
memStream.Position = 0;
string responseBody = new StreamReader(memStream).ReadToEnd();//get response body here after next.Invoke()
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
}
finally
{
context.Response.Body = originalBody;
}
}
}
}
I'm trying to create an application with NestJS framework and I'd like to check if a specific Service is in the application context but, the framework default behavior is exiting the process when it doesn't find the Service inside the context.
I've surrounded the get method with try...catch but it doesn't work because of the process.exit(1) execution.
private async loadConfigService(server: INestApplication): Promise<void> {
try {
this.configService = await server.get<ConfigService>(ConfigService, { strict: false });
} catch (error) {
this.logger.debug('Server does not have a config module loaded. Loading defaults...');
}
this.port = this.configService ? this.configService.port : DEFAULT_PORT;
this.environment = this.configService ? this.configService.environment : Environment[process.env.NODE_ENV] || Environment.DEVELOPMENT;
this.isProduction = this.configService ? this.configService.isProduction : false;
}
I'd like to catch the exception to manage the result instead of exiting the process.
Here's what I came up with:
import { NestFactory } from '#nestjs/core';
export class CustomNestFactory {
constructor() {}
public static create(module, serverOrOptions, options) {
const ob = NestFactory as any;
ob.__proto__.createExceptionZone = (receiver, prop) => {
return (...args) => {
const result = receiver[prop](...args);
return result;
};
};
return NestFactory.create(module, serverOrOptions, options);
}
}
Now, instead of using the default NestFactory I can use my CustomNestFactory
Dirty, but it works
Here is my code to solve same issue:
import { ExceptiinsZone } from '#nestjs/core/errors/exceptions-zone';
ExceptionsZone.run = callback => {
try {
callback();
} catch (e) {
ExceptionsZone.exceptionHandler.handle(e);
throw e;
}
};
You may need to override asyncRun() also for other method.
I am testing OWIN middleware and wrote the following middleware class. But strangely, I see that this middleware is called about four times in total when I request a URL without any path (localhost:<port>). However when I call localhost:<port>/welcome.htm or when I call localhost:<port>/default.htm it is only called once as expected. What am I missing?
public class MyMiddleware : OwinMiddleware
{
public MyMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await this.Next.Invoke(context);
if (context.Request.Path.HasValue && (context.Request.Path.Value == "/" || context.Request.Path.Value.EndsWith(".htm")))
{
using (var writer = new StreamWriter(context.Response.Body))
{
await writer.WriteLineAsync("Next middleware: " + this.Next.ToString());
}
}
}
}
and here's my Startup configuration:
public void Configuration(IAppBuilder appBuilder)
{
appBuilder.UseErrorPage(new ErrorPageOptions {
ShowCookies = true,
ShowEnvironment = true,
ShowExceptionDetails = true,
ShowHeaders = true,
ShowQuery = true,
ShowSourceCode = true,
SourceCodeLineCount = 2
});
appBuilder.Use<MyMiddleware>();
appBuilder.UseWelcomePage("/welcome.htm");
appBuilder.UseStaticFiles();
}