I am using both a Exception Filter, Interceptor and Validation Pipe. All of these work as expected on their own. I set these globally, as that is all I need at this time. The AllExceptionsFilter enriches the default exception from NestJS to include the URL, Http method, etc. The AddProgramVersionToResponseHeaderInterceptor is used to add a custom header to the response, which contains my application and version. The AppGlobalValidationPipeOptions is used to convert the Http Status code from 400 on a validation error, to be 422 Unprocessable Entity.
main.ts
...
const AppHttpAdapter = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(AppHttpAdapter));
app.useGlobalInterceptors(new AddProgramVersionToResponseHeaderInterceptor());
app.useGlobalPipes(new ValidationPipe(AppGlobalValidationPipeOptions));
await app.listen(IpPort);
All of these work fine, until I get an exception in the code, such as a bad route.
GET /doesnotexist will return the enriched 404 result, which has what I want, but the Http Header field from the AddProgramVersionToResponseHeaderInterceptor does not update the header.
I have defined the global settings in different orders to see if that would allow the interceptor to add the Http header, but that did not work.
AddProgramVersionToResponseHeaderInterceptor
export class AddProgramVersionToResponseHeaderInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const AppInfo: ApplicationInformationService = new ApplicationInformationService();
const EmulatorInfo: string = `${AppInfo.Name}/${AppInfo.Version}`;
const ResponseObj: ExpressResponse = context.switchToHttp().getResponse();
ResponseObj.setHeader('my-custom-header', EmulatorInfo);
return next.handle().pipe();
}
}
AllExceptionsFilter
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost): void {
// In certain situations `httpAdapter` might not be available in the
// constructor method, thus we should resolve it here.
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const HttpStatusCode: number = exception instanceof HttpException ? exception.getStatus() : HttpStatus.EXPECTATION_FAILED;
const RequestData: string = httpAdapter.getRequestUrl(ctx.getRequest());
const RequestURLInfo: string[] = RequestData.split('?');
const ResponseBody: IBackendException = {
Hostname: httpAdapter.getRequestHostname(ctx.getRequest()),
Message: exception instanceof HttpException ? (exception.getResponse() as INestHttpException).message : [(exception as Error).message.toString()],
Method: httpAdapter.getRequestMethod(ctx.getRequest()),
StackTrace: exception instanceof HttpException ? '' : (exception as Error).stack,
StatusCode: HttpStatusCode,
Timestamp: new Date().toISOString(),
URL: RequestURLInfo[0],
Parameters: RequestURLInfo[1],
};
httpAdapter.reply(ctx.getResponse(), ResponseBody, HttpStatusCode);
};
AppGlobalValidationPipeOptions
export const AppGlobalValidationPipeOptions: ValidationPipeOptions = {
errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
transform: true,
whitelist: true,
};
Interceptors bind to route handlers (class methods) that are marked with an HTTP verb decorator. If there is no route handler (like a 404) then then interceptor cannot be called. I have the same problem with my OgmaInterceptor. You can either make an #All('*') handler that throws a NotFoundException or just take care of it in the filter as you already are.
Related
I have ApolloServer running where the frontend makes a query request and the ApolloService fetches the request and then performs a request with RESTDataSource to a third-party service, I receive a response with a header.
Currently, ApolloServer only parses the body through the resolver and sends it back to the client
I wanted to pass also the header received to the client
I don't know how to do that at the RESTDataSource level since I don't have access to the Apollo response
I hope this was clear enough to explain the problem
export abstract class myClass extends RESTDataSource {
getSomething() {
const endpoint = this.endpointPath;
return this.get(endpoint);
}
async didReceiveResponse<T>(response, request): Promise<T | null> {
// these are the response headers desired to have them sent back to the client
console.log(response.headers);
if (response.ok) {
return this.parseBody(response) as any as Promise<T>;
} else {
throw await this.errorFromResponse(response);
}
}
}
In the appolloService initialization i have
const apolloServer = new ApolloServer({
context: async ({ res, req }) => {
// these headers are not the same as received from the getSomething() response above
console.log(res.getHeaders)
}
)}
I solved the issue by passing the res to the context and accessing the response in the didReceiveResponse, then adding the headers needed.
adding a response to context
const apolloServer = new ApolloServer({
context: async ({ res, req }) => {
return {
res: res,
};}
using the response to append the headers to it
async didReceiveResponse<T>(response, request): Promise<T | null> {
// use this.authHeader value in the class anywhere
this.context.res.setHeader(
"x-is-request-cached",
response.headers.get("x-is-request-cached") ?? false
);
this.context.res.setHeader(
"x-request-cached-time",
response.headers.get("x-request-cached-time")
);
if (response.ok) {
return (await this.parseBody(response)) as any as Promise<T>;
} else {
throw await this.errorFromResponse(response);
}}
by doing this you will achieve the desired outcome of passing the headers to the graphQl client
I'm creating an application that is using Nestjs with websockets, but now I need to add rate limit on the sockets, but analyzing the documentation documentation link and implementing what it says in it, when I use #UseGuards(MyGuard) an error occurs in the application.
My Guard:
#Injectable()
export class NewThrottlerGuard extends ThrottlerGuard {
protected async handleRequest(
context: ExecutionContext,
limit: number,
ttl: number,
): Promise<boolean> {
console.log('Request');
const client = context.switchToWs().getClient();
const ip = client.conn.remoteAddress;
const key = this.generateKey(context, ip);
const ttls = await this.storageService.getRecord(key);
if (ttls.length >= limit) {
throw new ThrottlerException();
}
await this.storageService.addRecord(key, ttl);
return true;
}
}
Websocket:
#UseGuards(NewThrottlerGuard)
#SubscribeMessage('sendMessage')
sendMessage(
#ConnectedSocket() client: Socket,
#MessageBody() message: string,
) {
client.rooms.forEach((room) => {
if (room !== client.id) {
client.broadcast.to(room).emit('message', message);
}
});
}
Error in console:
/node_modules/#nestjs/common/utils/validate-each.util.js:22
throw new InvalidDecoratorItemException(decorator, item, context.name);
^
Error: Invalid guard passed to #UseGuards() decorator (ChatGateway).
at validateEach
The file in: #nestjs/common/utils/validate-each.util.js:22
function validateEach(context, arr, predicate, decorator, item) {
if (!context || !context.name) {
return true;
}
console.log(context, arr)
const errors = arr.some(str => !predicate(str));
if (errors) {
throw new InvalidDecoratorItemException(decorator, item, context.name);
}
return true;
}
i put some console.log then in the terminal it show:
[Function: ChatGateway] [ undefined ]
In Github Throttler documentation they say: You cannot bind the guard with APP_GUARD or app.useGlobalGuards() due to how Nest binds global guards.
So, im using #UseGuards()
The guard itself was written correctly, but it was put in a location that importing it made a circular reference between files, so when #UseGuards() was used it became #UseGuards(undefined) which caused the cryptic error message. Moving the guard to a dedicated file will fix the error
I follow your github reference settings and it doesn't work,The following is my code, where is my setting wrong, and the request to ws is not intercepted(In the handleRequest method)
I am new to Ionic. I didn't get any solution for this from here.
This is my ionic.config.json
{
"name":"BarcodeScannerApp",
"integrations": {
"cordova": {}
},
"type":"angular",
"proxies":[
{
"proxyUrl":"http://localhost:8085"
}
]
}
Below is the method in service class
public getProduct(barocode: any, basketBarcode: any): Observable<any> {
this.url = '/api/v1/barcode/scan';
let queryParameters = new HttpParams();
queryParameters = queryParameters.set('barcodeInfo', barocode);
queryParameters = queryParameters.set('basketId', basketBarcode);
const requestOptions: any = {
params: queryParameters
};
return this.httpclient.post(this.url, requestOptions);
}
This gives below error
POST http://192.168.0.9:8100/api/v1/barcode/scan 404 (Not Found)
Server side method is below
#RequestMapping(value = "api/v1/barcode/scan", method = {RequestMethod.POST})
public ResponseEntity<ServerResponse> getProductInfo(#RequestParam("barcodeInfo") String barcodeNo, #RequestParam("basketId") String basketId) {
return null;
}
I solved the issue . It was the CORS issue, which i was supposed to add #crossOrigin(" * ") annotation.
If the server is CORS enabled, it will parse the Access-Control-Request-* headers and understand that a request is trying to be made from http://localhost:8100 (Ionic client) with a custom Content-Type.
I think it's should be work. You should use the HTTP header like "Content-type" etc
I am calling an api endpoint in an MVC 6 WebAPI:
POST http://localhost:57287/mytestapi/testentity/ HTTP/1.1
Accept: application/json
X-APIKey: 00000000-0000-0000-0000-000000000000
Content-Type: application/json; charset=utf-8
Host: localhost:57287
Content-Length: 1837
Expect: 100-continue
Connection: Keep-Alive
In the body I have json serialized test entity.
I have a bug in my entity controller code and the api is returning a 500 response 'Server Error' I know what the bug is an will fix it, however the issue I need some help with is that the API is returning HTML instead of the json serialized exception object - Json is what I expect: it's what the old webapi would return. I have ported the coded from an old test project that I know works.
So why is MVC 6 WebAPI returning html rather than json? Is there some configuration I need to do?
EDIT:
I added Accept: application/json to headers as suggested by #danludwig, however this did not resolve the issue, I still got an html error page back.
I looked at my StartUp.cs and found:
if (env.IsDevelopment())
{
//app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
in the ConfigureApp method. I tested with app.UseDeveloperExceptionPage(); commented out. This prevented the return of the html error page in the api response body, however I am still not getting the json serialised exception object.
The ExceptionHandlerMiddleware configured when using UseExceptionHandler("Home/Error") does not include any support for JSON. It will just return the error html page. The same can be said when using UseDeveloperExceptionPage.
As far as I know you will need to add yourself some piece of code that will handle errors and return a json.
One option is to use an exception filter and add it either globally or on selected controllers, although this approach would only cover exceptions coming from the controller action methods. For example the following filter will return a json object only when the request accept was application/json (Otherwise it would let the exception pass through which for example could be handled by the global error page):
public class CustomJSONExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.HttpContext.Request.GetTypedHeaders().Accept.Any(header => header.MediaType == "application/json"))
{
var jsonResult = new JsonResult(new { error = context.Exception.Message });
jsonResult.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
context.Result = jsonResult;
}
}
}
services.AddMvc(opts =>
{
//Here it is being added globally.
//Could be used as attribute on selected controllers instead
opts.Filters.Add(new CustomJSONExceptionFilter());
});
Another option is to add your own exception handler middleware using the app.UseExceptionHandler overload that lets you specify the behavior of the alternative pipeline that will process the exception. I have quickly wrote a similar example using an inline middleware, which will return a json object only when the request accept was application/json:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Use(async (context, next) =>
{
var excHandler = context.Features.Get<IExceptionHandlerFeature>();
if (context.Request.GetTypedHeaders().Accept.Any(header => header.MediaType == "application/json"))
{
var jsonString = string.Format("{{\"error\":\"{0}\"}}", excHandler.Error.Message);
context.Response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
await context.Response.WriteAsync(jsonString, Encoding.UTF8);
}
else
{
//I haven't figured out a better way of signally ExceptionHandlerMiddleware that we can't handle the exception
//But this will do the trick of letting the other error handlers to intervene
//as the ExceptionHandlerMiddleware class will swallow this exception and rethrow the original one
throw excHandler.Error;
}
});
});
Both approaches will let you have other error handlers that maybe provide html pages for non json requests (Another idea would be to either return a json or an html page from your custom error handler).
PS. If using the second approach, you most likely want to put that logic into its own middleware class and use a different approach to generate the json response. In that case take a look at what JsonResultExecutor does
I found a cheap hack to get what I want by adding this to the Startup Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Simple error page to avoid a repo dependency.
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
if (context.Response.HasStarted)
{
throw;
}
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
var json = JToken.FromObject(ex);
await context.Response.WriteAsync(json.ToString());
}
});
//Rest of configure method omitted for brevity.
}
I have a backbone model. I add the following attributes to the model
model.set('x',x);
model.set('y',y);
model.set('z',z);
and than i call model.save....
In the backend what i do is i set some more properties to brandDTO
But what i see is that the error attribute is not there in the error callback model
app.Model.BrandModel = Backbone.Model.extend({
url : '/brand/cu'
});
var brand = new app.Model.BrandModel();
brand.save(null, {
success : function(model, response) {
},
error : function(model, response) {
}
});
#RequestMapping(value = "/brand/cu", method = RequestMethod.POST, produces = "application/json")
#ResponseBody
public BrandDTO createBrand(#RequestBody BrandDTO brandDTO,
HttpServletResponse response) {
brandDTO.setErro("error", error)
This error field is not there in the error callback model..
I am not sure if i am thinking right....
You are thinking right, just need to arrange this both on client and server.
I see you are using Java, is this SpringMvc?
First, you should serialize your response:
On server:
1.)Create an object/class called SerializedResponse which has "data", "success", "exception" properties.
2.)Then you can wrap a try{}catch{} on the create brand business logic, and if it succeeded set the "success" to true, if it failed to "false", if theres an exception, put it in the exception object.
Note that even if you have an error in your logic, you will always get success, as long as the operation was performed and you have recieved a response, and unless the http status code is not 200, this is handled by the jqXhr object so you can handle the callbacks from there, meaning:
success : function(model, response) {
var data = JSON.parse(response);
if(data.success){
//ALL OK
}
else {
//Fail
}
},
error: function(){
//Error
}