I am trying to implement a logging interceptor in nestjs such that, it captures all the requests and responses and logs it.
Hence I implemented a LoggingInterceptor like this
import { logger } from './../utils/logger';
import { ExecutionContext, Injectable, NestInterceptor } from '#nestjs/common';
import { Observable, BehaviorSubject, from } from 'rxjs';
import { map, tap, refCount, publish, publishLast } from 'rxjs/operators';
#Injectable()
export class LoggingInterceptorInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, call$: Observable<any>): Observable<any> {
const reqHeaders = context.switchToHttp().getRequest().headers;
const reqBody = context.switchToHttp().getRequest().body;
logger.info('Logging the incoming request:',reqHeaders);
logger.info('Logging the incoming req body:', reqBody);
const now = Date.now();
logger.info('Time of request call is', now);
const serviceBehaviorSubj = new BehaviorSubject<any>(null);
// response will be available only in the call$ observable stream
// so use tap to get a clone of the observable and print the data sent
// this pipe call transforms the response object to start with something called data
// so a default response like "Done", "test" looks now as a json string
// {data : Done} {data: test}
// const anotherrespObs: Observable<any> = call$.pipe(publishLast(), refCount());
// anotherrespObs.pipe(publishLast(), refCount()).subscribe(data => {
// logger.info('Logging the outgoing response', data);
// });
return call$.pipe(map(data => {
console.log('data here is', data);
return ({ dottted: data });
}));
//oo.then(return new Pro)
// return call$.pipe(tap(() => {
// logger.info(`Time of completion is ${Date.now() -now}`);
// }), map(data => {
// console.log('ccccccc', data);
// return data;
// }));
}
}
I understand that call$ operator behaves like an Observable and that will be subscribed internally by nestjs to send the response to client, but I wanted to log the information before being sent and possibly transform the response
So I make use of the map() operator of rxjs. This functions properly if the response set is of type other than 'application/json'. If the Content-Type
is of 'plain/text', the map operation gets applied and gets transformed to the desired json object and sent to the client but not in the case if the response is already of type application/json, i.e a json object. I am unable to apply the transform object. On logging the value sent to map(), I see it gets logged as undefined for json objects. So how do I get the response (even if it a json object) and possibly log it and transform it before sending it to the client in the interceptor
Note: I am wary that the response might contain sensitive information, but I probably would use log masking to just mask the response data, but this is currently for testing purposes
Here is the sample controller for which I am able to log the response in the interceptor
#ApiOperation({ title: 'Get - With Params', description: 'Test Method with parms' })
#Get('/getTest/:id1/:id2')
#ApiOkResponse({ description: 'Sample string is emitted' })
#ApiResponse({ status: 404, description: 'The endpoint is unavailable' })
#ApiResponse({ status: 503, description: 'The endpoint cannot be processed' })
// #Header('sampleHeaderKey', 'sampleHeaderValue')
// NOte if you send params in the URL and do not use #param then the URL will
// result in NO such end point
public getConfigDataInResponse(#Param('id1') id1: number, #Param('id2') id2: number, #Req() req) {
logger.info('request headers', req.headers);
logger.info('reqiest params', req.params);
logger.info('reqiest query params', req.query);
logger.info('reqiest body ', req.body);
return 'TEST';
}
And here is the method for which the response cannot be logged, it comes as "undefined" in the interceptor
public getConfigDataInResponse(#Param('id1') id1: number, #Param('id2') id2: number, #Req() req, #Res() res) {
logger.info('request headers', req.headers);
logger.info('reqiest params', req.params);
logger.info('reqiest query params', req.query);
logger.info('reqiest body ', req.body);
res.set('SampeHeader', 'saomevaluie');
res.status(HttpStatus.OK).send('some data');
}
When you inject #Res() in your controller method, a lot of the features that make nest so great like interceptors won't work.
In most cases, you do not need to inject #Res() because instead you can use dedicated decorators. In your example that would be:
// Sets the http response code (default for POST is 201, for anything else 200)
#HttpCode(204)
// Sets a custom header
#Header('SampleHeader', 'somevalue')
#Get('/getTest/:id1/:id2')
public getConfigDataInResponse(#Param('id1') id1: number, #Param('id2') id2: number) {
return 'some data';
}
Related
I'm calling a Service from an onSubmit(). The service then calls a REST API for data. However the ordering is not what I'd expect.
I think I have 2 issues here:
The log ### CUSTOMER BEFORE RETURN doesn't seem to contain the retrieved data despite initialising the variable at the start of the method. So at this log line, it's still undefined. However at ### UNPACKED DATA the data is visible.
Even if customer was not undefined at the return of loadCustomer, it looks like the line ### CUSTOMER IN onSubmit is executed before data is retrieved and not after, which will be a problem since I need to use the data afterwards!
onSubmit(customerData) {
let customer = this.customerService.loadCustomer(customerData)
console.log("### CUSTOMER IN onSubmit", customer)
// use customer Object to process orders...
}
import { HttpClient } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable()
export class CustomerService {
constructor(private http: HttpClient) { }
loadCustomer(customerId: number) {
console.log("### Customer ID to get: ", customerId)
var myStr: string;
var myObj: any;
var customer: Object
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
this.http.get<any[]>('https://REGION.amazonaws.com/dev/customer', httpOptions).subscribe(
data => {
myStr = JSON.stringify(data);
myObj = JSON.parse(myStr);
console.log("### UNPACKED DATA", myObj['data']['Items'][customerId-1])
if (typeof myObj['data']['Items'][customerId] != "undefined")
customer = myObj['data']['Items'][customerId-1]
},
error => console.log('Failed to get customer.')
);
console.log("### CUSTOMER BEFORE RETURN: ", customer)
return customer;
}
OUTPUT IN CONSOLE:
customer.service.ts:21 ### Customer ID to get: 2
customer.service.ts:51 ### CUSTOMER BEFORE RETURN: undefined
cart.component.ts:64 ### CUSTOMER IN onSubmit undefined
customer.service.ts:38 ### UNPACKED DATA {customer_id: 2, address: "32 Big avenue", row_id: "a97de132-89ac-4f6e-89cd-2005319d5fce", name: "Dave Lawinski", tel_number: "777888999"}
From what I've gathered this looks like something to do with Observable / some form of asynchronous operation, however I've not been able to make sense of where I'm going wrong here.
you are returning an object before the http call returns, you need to return an observable and then subscribe to it in the component:
onSubmit(customerData) {
this.customerService.loadCustomer(customerData)
.subscribe((res) => {
console.log("### CUSTOMER IN onSubmit", res)
// use res Object to process orders...
})
}
and in your loadCustomer function:
loadCustomer(customerId: number) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
return this.http.get<any[]>('https://REGION.amazonaws.com/dev/customer', httpOptions)
.pipe(map((result) => {
const myStr = JSON.stringify(data);
const myObj = JSON.parse(myStr);
let customer;
console.log("### UNPACKED DATA", myObj['data']['Items'][customerId-1])
if (typeof myObj['data']['Items'][customerId] != "undefined") {
customer = myObj['data']['Items'][customerId-1];
}
return customer;
}));
}
I'm working on the test driven angular app. (Don't ask why, That is how client wants)
Below is the spec which I can't modify or edit.
it('should get results', fakeAsync(
inject(
[XHRBackend, NewsService ],
(mockBackend: MockBackend, newsService: NewsService) => {
const expectedUrl = 'https://api.nytimes.com/svc/topstories/v2/home.json?api-key=315a5a51483b469a918246dc2753b339';
mockBackend.connections.subscribe((connection : MockConnection) => {
expect(connection.request.method).toBe(RequestMethod.Get);
expect(connection.request.url).toBe(expectedUrl);
connection.mockRespond(new Response(
new ResponseOptions({ body: mockResponse })
));
});
newsService.getSectionNews('home')
.subscribe( (res: any) => {
expect(res).toEqual(mockResponse);
});
})
));
So based on the spec, I need to write my front end code.
So this is what I've written,
import { Http } from '#angular/http';
constructor(private http: Http) {}
getSectionNews(sectionName: string): any {
// fetch news of that sectionName
// return this.mockResponse;
const expectedUrl = 'https://api.nytimes.com/svc/topstories/v2/home.json?api-key=315a5a51483b469a918246dc2753b339';
return this.http.get(expectedUrl).subscribe(res => res);
}
But while running the test case, I'm getting this error:
TypeError: newsService.getSectionNews(...).subscribe is not a function
please tell me what I'm doing wrong here.
I wanted to pass the test case.
UPDATE
After updating my service spec.
getSectionNews(sectionName: string): Observable<any> {
const expectedUrl = `https://api.nytimes.com/svc/topstories/v2/${sectionName}.json?api-key=315a5a51483b469a918246dc2753b339`;
return this.http.get(expectedUrl);
}
Now I'm getting this below error,
Expected Response with status: null null for URL: null to equal
Objectt({ status: 'OK', copyright: 'C ...
I want to show the data on the home page from a REST api (need authorization 'username' and 'password' to access) using Interceptor in Angular 6. However, previously I had used Node.js to fetch the data and it was pretty successful at that time. Moreover, when I switch to the Interceptor it shows the following error:
ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
at subscribeTo (subscribeTo.js:41)
at subscribeToResult (subscribeToResult.js:6)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._innerSub (mergeMap.js:70)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._tryNext (mergeMap.js:67)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapSubscriber._next (mergeMap.js:50)
at MergeMapSubscriber.push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next (Subscriber.js:54)
at Observable._subscribe (scalar.js:5)
at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable._trySubscribe (Observable.js:42)
at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable.subscribe (Observable.js:28)
at MergeMapOperator.push../node_modules/rxjs/_esm5/internal/operators/mergeMap.js.MergeMapOperator.call (mergeMap.js:28)
In my solution, I have created a function in the landing component which is like this:
const userData = { username: 'anonymous', password: '' };
this.http
.post(`${this.baseURL}/auth/jwt/authenticate`, userData)
.subscribe(res => {
// console.log(res);
const token = JSON.parse(res['_body']).token;
this.publicToken = token;
if (token) {
this.publicTokenStatusListener.next(true);
localStorage.setItem('public-token', token);
}
});
The Interceptor is like this:
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const authToken = localStorage.getItem('public-token');
const copiedReq = req.clone({
params: req.params.set('auth', authToken)
});
return next.handle(copiedReq);
}
From the service, I am returning the REST data (GET) to fetch the observable. However, it doesn't work as I am expecting.
I can get the 'token' in the Interceptor. Waiting for some suggestions.
Thanks in advance.
HttpParams is immutable.
You need to use setParams instead of params:
const copiedReq = req.clone({
setParams: {'auth': authToken}
});
Are you sure that sensitive data should be passed as params?
Might be it would be better to use headers?
This is my first question here, and I just started writing Node JS code recently. Right now I am using NodeJS and EJS as a templating engine for my app. My database is DynamoDB and I wanted to make one the tables Realtime by using AWS Appsync. Using Appsync, I can query and mutate the fields in my table but I cannot seem to subscribe to any mutations. No MQTT websockets are open as well when I call my subscribe.
I tried to follow the example for Appsync subscribe on the Appsync documentation but it does not seem to work.
Note: I have defined my infoID variable already, the type information{} is created in the schema and the mutate and query both work. Its just that the subscription doesn't work and no MQTT websocket is created on the template (is this even possible using NodeJS and EJS?).
My schema is as follows:
type Mutation {
deleteInfo(infoID: String!): information
}
type Subscription {
onDeleteInfo(infoID: String): information
#aws_subscribe(mutations: ["deleteInfo"])
}
and the code I used to query and subscribe is like this:
const query = gql(`
query($infoID: String!){
getInformation(infoID: $infoID) {
infoID
noOfDays
noOfItems
infoName
}
}`);
// Set up a subscription query
const subquery = gql(`
subscription ($infoID: String) {
onDeleteInfo(infoID: $infoID) {
infoID
noOfDays
noOfItems
infoName
}
}`);
const client = new AWSAppSyncClient({
url: url,
region: region,
auth: {
type: type,
credentials: credentials,
}
});
client.hydrated().then(function (client) {
//Now run a query
client.query({ query: query, variables: {infoID : infoID} })
.then(function logData(data) {
console.log('results of query: ', data);
var responseData = data;
res.send(responseData);
})
.catch(console.error);
//Now subscribe to results
const realtimeResults = function realtimeResults(data) {
console.log('realtime data: ', data);
console.log('subcribe is called');
};
const observable = client.subscribe({
query: subquery,
variables: {infoID : infoID} });
observable.subscribe({
next: realtimeResults,
complete: console.log,
error: console.log
});
};
and my mutation code is:
const mutation = gql(`
mutation($infoID: String!){
deleteInfo(infoID: $infoID) {
infoID
noOfDays
noOfItems
infoName
}
}`);
const client = new AWSAppSyncClient({
url: url,
region: region,
auth: {
type: type,
credentials: credentials,
}
});
client.hydrated().then(function (client) {
//Now run a query
client.mutate({ mutation: mutation, variables:{infoID: infoID} })
.then(function logData(data) {
console.log('results of mutate: ', data);
})
.catch(console.error);
});
Thanks to anyone who answers or read or helps in any way!
It sounds like you are successfully connected to AppSync if you can make mutations and queries from your app. To Debug the subscription problem, you will need to look at the network response for the request. The service will send an error message if something is incorrectly setup.
One common thing to check for is whether the subscription is defined in your schema.
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
If this is the case, the error message you would receive when making the subscription request is: Schema is not configured for subscriptions.
Here is a sample code snippet how to call subscription:
// Set up a subscription query
const subquery = gql`
subscription onAccept {
onUpdateDeveloperJob(developerId: "ce261427-84ad-450b-91d1-83b78532dfe6") {
id
jobId
developerId
selected
}
}
`;
// Set up Apollo client
const client = new AWSAppSyncClient({
url,
region,
auth: {
type,
apiKey
}
});
client
.hydrated()
.then(appsyncClient => {
// Now run a query
// appsyncClient
// .query({ query })
// .then(data => {
// console.log('results of query: ', data);
// })
// .catch(console.error);
// Now subscribe to results
const observable = appsyncClient.subscribe({ query: subquery });
const realtimeResults = function realtimeResults(data) {
console.log('realtime data: ', data);
};
observable.subscribe({
next: realtimeResults,
complete: console.log,
error: console.log
});
})
.catch(console.error);
Hope this will be helpful for you.
I am running app on localhost://3000 with npm server
Services file:
import {Injectable} from "#angular/core";
import {Jsonp} from "#angular/http";
import 'rxjs/add/operator/map';
#Injectable()
export class futScoreService{
constructor(private _jsonp:Jsonp){}
getCompetitions(){
let queryString ='?callback=JSONP_CALLBACK';
return this._jsonp.get('http://api.football-data.org/v1/competitions/' + queryString,{method: 'Get'})
.map((res) => res.json());
}
}
Component file:
ngOnInit(){
this._futScoreService.getCompetitions().subscribe(
(comp)=>{
console.log(comp);
},
(err)=>{
console.log(err);
}
);
}
And I'm getting this error in console console-error
and on network tab I get object from API network-tab
Ok solution was making get request with http module and providing header with get request. Header part was main reason why it was failing.
let headers = new Headers({'X-Mashape-Key':'Ns0SkjyRRomshq3PgEnGoz2Zkc71p1CYnWajsnphGctvrGt46W'});
headers.append( 'Accept', 'application/json');
return this._http.get("http://api.football-data.org/v1/competitions/",{
headers: headers
})
.map((res) => res.json());
Angular is replacing JSONP_CALLBACK with
__ng_jsonp____req0_finished
but it should be
__ng_jsonp__.__req0.finished
Inspect your Network response. If you see __ng_jsonp____req0_finished({...json object...}) this is the problem.
Also, some services have different requirements for the callback query string parameter, which proves to be nasty because the error is exactly the same. I was using &callback=__ng_jsonp__.__req0.finished with MailChimp which produced the same error but the response had only a json object and no callback function. This is because MailChimp's spec is to use &c= instead of &callback=
When hardcoding the Jsonp callback (re: JSONP_CALLBACK issue) you need to account for the number of calls made, as Angular persists the state of each call. An example of what I'm doing for Mailchimp:
addEmailToList(email: string, listId: string, jsonpCalls: number, callback: any) {
const cbJsonp = '__ng_jsonp__.__req' + jsonpCalls + '.finished';
let url = [
'http://',
host,
'/subscribe',
'/post-json',
].join('');
let queryParams: URLSearchParams = new URLSearchParams();
queryParams.set('u', Config.MAILCHIMP_API_KEY);
queryParams.set('id', listId);
queryParams.set('EMAIL', email);
queryParams.set('c', cbJsonp); // non-standard; varies by service; usually 'callback'
...
}
this._InstUrl = "your url";
let params1 = new URLSearchParams();
//params.set('search', term); // the user's search value
//params.set('action', 'opensearch');
params1.set('format', 'json');
//params1.set('callback', "ng_jsonp.__req0.finished");
params1.set('callback', "JSONP_CALLBACK");
return this._jsonp
.get(this._InstUrl, { search: params1 })
.map(response => { debugger; this.Result = response.json().data })
.subscribe(
(data) => {
debugger
console.log(this.Result);
},
(error) => {
debugger
console.log(error);
});