HttpClientModule http.get not working - node.js

I picked up this previous working app (Angular2) and find that it is not working (Angular4) as expected now.
Module was used (it may not matter):
import { HttpModule, JsonpModule } from '#angular/http';
Module being used now:
import { HttpClientModule} from '#angular/common/http';
Trying to get a list of records from the backend (Node.js, Express, and MongoDB) as below.
listResource(resType: string, parameters: string[]) {
console.log("***listResource");
let headers = new HttpHeaders().set("X-CustomHeader", "custom header value");
headers.append('Content-Type', 'application/fhir+json');
headers.append("'Access-Control-Allow-Origin", "*");
headers.append("Accept", "application/fhir+json");
headers.append("USER_KEY", "QIF83Fjoe4sYxdQsah3h"); //TOUCHSTONE KEY
let urlString = this.baseUrl + resType + "/list"; // + queryString;
console.log("List resource URL string: [" + urlString + "]");
return (this.httpClient.get(urlString, { headers })
.map((res: Response) => res.json()))
.catch((error: any) => Observable.throw(error.json().error || 'Server error from Observable http.get call')); //...errors if any
}
when my component is loaded, the above listResource will be called as below.
ngOnInit() {
//Get the initial 25 latest patient
//this.progressBar = true;
this.currentPage = 0;
this.patientList = [];
this.globalSvc.gPatient = [];
console.log("***OnInit");
this.restSvc.listResource("Patient", ["identifier=*", "family=*", "given=*"]).subscribe(
data => {
console.log("Response data: " + JSON.stringify(data));
},
(err: HttpErrorResponse) => {
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
console.log('An error occurred:', err.error.message);
} else {
// The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong,
console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
}
});
}
Below is the output from Chrome console. Of course, I don't get any good response. It seems to me the Chrome browser sends CORS option and the server responds correctly, then the browser doesn't send the actual GET.
If I send the REST API request from PostMan directly which doesn't have CORS, I get the expected good response from the server. Hence, it seems to me the server is ok.
Questions:
Any idea how to debug or fix it?
Will this relate to CORS on both Angular client and Node.js server?
The ${err.status} and ${err.error} are "undefined" in Chrome console. How can I find the actual error?
console.log(Backend returned code ${err.status}, body was: ${err.error});
Update 1 based on Grey's suggestion on the immutable header and const.
The GET is returning data now.

headers.append() does not alter the headers, it returns a new Headers (because Headers is immutable).
So, instead of
let headers = new HttpHeaders().set("X-CustomHeader", "custom header value");
headers.append('Content-Type', 'application/fhir+json');
headers.append("'Access-Control-Allow-Origin", "*");
headers.append("Accept", "application/fhir+json");
headers.append("USER_KEY", "QIF83Fjoe4sYxdQsah3h"); //TOUCHSTONE KEY
you need to do something like:
let headers = new HttpHeaders().set("X-CustomHeader", "custom header value")
.append('Content-Type', 'application/fhir+json')
.append("'Access-Control-Allow-Origin", "*")
.append("Accept", "application/fhir+json")
.append("USER_KEY", "QIF83Fjoe4sYxdQsah3h");
Oh, and that should actually be const headers =, rather than let headers =

Related

How can I intercept a single XHR/fetch request, without affecting requests afterwards?

I am building a React/Typescript app, running completely client-side in browser. In componentDidMount(), I make a fetch request which I intercept successfully, to change the URL, and then make that request.
For reference, the API object is also from a third party library, loaded in via an HTML script tag, so I don't have access to the inner workings of the object. That's why I'm attempting to intercept the call instead to point the URL at a different endpoint.
async function makeRequest() {
let originalFetch = redefineFetch();
let data;
try {
data = await API.fetchData();
} catch (error) {
resetFetch(originalFetch);
return;
}
resetFetch(originalFetch);
return data;
}
const redefineFetch = () => {
const { fetch: originalFetch } = window;
let originalWindowFetch = window.fetch;
window.fetch = async (...args) => {
let [resource, config] = args;
resource = NEW_URL;
const response = await originalFetch(resource, config);
return response;
};
return originalWindowFetch;
};
const resetFetch = (
originalFetch: ((input: RequestInfo | URL, init?: RequestInit | undefined) => Promise<Response>) &
((input: RequestInfo | URL, init?: RequestInit | undefined) => Promise<Response>)
) => {
console.log("Resetting fetch");
window.fetch = originalFetch;
};
How I'm currently doing it:
I copied how it was done in this blog post. https://blog.logrocket.com/intercepting-javascript-fetch-api-requests-responses/.
As you can see, makeRequest() calls redefineFetch(), which redefines window.fetch to point to the NEW_URL instead.
redefineFetch() returns the original implementation of fetch as originalFetch.
After making the request, I call resetFetch() and pass originalFetch.
I then set window.fetch = originalFetch.
What I think is the issue
Every request including and after API.fetchData() now point to the NEW_URL.
These requests are out of my control in timing as they are made by 3rd party portions of my code.
I think I'm either not setting window.fetch back to its original value correctly, OR there's a race condition in which these mistakenly intercepted requests are being made before resetFetch() is called.
My Questions
How can I redefine fetch only for the API.fetchData() call without risking affecting any other calls made in my app?
Is there a better way to accomplish what I'm doing?

Why does my Angular / Express.js Server-Sent Events Code not work?

Goal: Use Server-Sent Events in my Angular App with Express Backend
Problem: Server-Sent Events do not reach the client
Backend Code
router.get('/options/:a/:b/:c', async (req, res) => {
console.log('options endpoint called..', req.params.a,req.params.b,req.params.c);
// ---- SERVER SENT EVENTS ---- //
// Setting Headers
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // flush the headers to establish SSE with client
for(let i=0;i<10;i++){
console.log("should now send this response...banane")
res.write('banane!')
}
});
Client Side Code
export class NetworkingService {
BASE_URL;
OPTIONS = 'options'
searchResultSubject: Subject<SearchResults>;
constructor(private http: HttpClient, private ls: LoggingService, private _zone: NgZone) { // shortened }
getOptions(a: string, b: string, c: string) {
this.ls.log("get options endpoint about to be triggered")
this.getServerSentEvent(this.BASE_URL + this.OPTIONS + "/" + a + "/" + b + "/" + c).subscribe(resp => {
this.ls.log("got from server: ", resp)
});
return this.searchResultSubject;
}
getServerSentEvent(url: string): Observable<any> {
console.log('creating an eventsource...')
return new Observable(observer => {
const eventSource = this.getEventSource(url);
eventSource.onopen = event => {
console.log('opening connection', eventSource.readyState)
};
eventSource.onmessage = event => {
console.log('event from server..')
this._zone.run(() => {
observer.next(event);
});
};
eventSource.onerror = error => {
console.log('error from server..')
this._zone.run(() => {
observer.error(error);
});
};
});
}
private getEventSource(url: string): EventSource {
return new EventSource(url);
}
}
Server Side log output (as expected)
options endpoint called.. A B C
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
should now send this response...banane
Client Side log output (NOT as expected)
get options endpoint about to be triggered
creating an eventsource...
opening connection 1
...and then nothing.
What have I tried?
I fail to see how I differ from these: so-question, tutorial, tutorial
In the Networks Tab in Dev Tools I see a Status 200, type eventsource line entry with the correct headers. But only one!
I think I am making a really obvious mistake, since it is ALMOST working and seems to be straightforward from the examples.
My Angular is 10.1.6 and express is 4.17.1
I am new to interacting directly with ngZone is there a potential error?
The problem persists even when I comment out the compression library or use res.flush(), as suggested here.
I was having the same problem, I was not getting the response on the client.
After a number of changes it seems to be working.
First I set the headers:
response.setHeader('Cache-Control', 'no-cache');
response.setHeader('Content-Type', 'text/event-stream');
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Connection', 'keep alive');
response.setHeader('X-Accel-Buffering', 'no');
response.flushHeaders(); // flush headers to establish SSE with client
If you are using the compress middleware, it is necessary that when sending the data to the client, you put
response.flush();
example
response.write(`event: ${event}\ndata: ${data}\n\n`);
response.flush();
It seems that the client receives messages from the generated event,
that is, if you send the client the following response
response.write(`event: some_event\ndata: some_data\n\n`);
the client should have a code similar to this:
const eventSource = new EventSource(url);
eventSource.addEventListener('some_event', listener => {
console.log(listener);
...
}, false);
I hope to be helpful
What worked for me was similar to what daacdev said, but not entirely.
Serverside I used:
res.setHeader('Cache-Control', 'no-cache, no-transform'); // Notice the no-transform!
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // Flush the headers to establish SSE with client
// Do some stuff...
// Like sending an event with data
res.write('event: fooEvent\n'); // Note the 1 newline after the event name
res.write(`data: ${JSON.stringify({ foo: 'bar' })}\n\n`); // Note the 2 newlines after the data
// And when you're done, end it
res.end();
And clientside, we have eventListeners for event types:
const eventSource = new EventSource('/api/foo');
eventSource.addEventListener('fooEvent', event => {
console.log('Got fooEvent: ', event.data); // event.data has your data
eventSource.close(); // Close the eventSource if the client is done
});
You can leave out the event type altogether and only send 'data' in your res.write(). In which case your client listens for event type-less messages like this:
eventSource.onmessage = event => {
console.log('Got some data: ', event.data); // event.data has your data
eventSource.close(); // Close the eventSource if the client is done
Noteworthy:
The Cache-Control header needed 'no-transform' aside from 'no-cache'. Before I did that, the client would only get the messages after the server ended the whole thing
I did not need to flush after every write
You can't only send an event type. It always needs data to follow it. So if you simply want to send a message, do something like:
res.write('event: doubleProcesses\n');
res.write('data: doubleProcesses\n\n');
You must adhere to the defined structure: the optional 'event' (followed by a colon, a name, and 1 newline) and the required 'data' (followed by a colon, your data as a string, and 2 newlines). You cannot simply put anything in your res.write.
That last point is for sure what you were missing.
Docs here:
https://developer.mozilla.org/en-US/docs/Web/API/EventSource
https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
For anyone who landed here after searching similar issues, what our problem turns out to be is that our node.js app is being hosted on Azure windows, and it is using IIS under the hood. The handler iisnode has a default setting of buffering response, which is the reason why response from our server was never received at the client side. All we had to do was to edit the web.config files according to this Microsoft guide
https://learn.microsoft.com/en-us/azure/app-service/app-service-web-nodejs-best-practices-and-troubleshoot-guide
pay attention to the
flushResponse section

How to use the full request URL in AWS Lambda to execute logic only on certain pages

I have a website running on www.mywebsite.com. The files are hosted in an S3 bucket in combination with cloudFront. Recently, I have added a new part to the site, which is supposed to be only for private access, so I wanted to put some form of protection on there. The rest of the site, however, should remain public. My goal is for the site to be accessible for everyone, but as soon as someone gets to the new part, they should not see any source files, and be prompted for a username/password combination.
The URL of the new part would be for example www.mywebsite.com/private/index.html ,...
I found that an AWS Lambda function (with node.js) is good for this, and it kind of works. I have managed to authenticate everything in the entire website, but I can't figure out how to get it to work on only the pages that contain for example '/private/*' in the full URL name. The lambda function I wrote looks like this:
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
// Configure authentication
const authUser = 'USER';
const authPass = 'PASS';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
The part that doesn't work is the following part:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
If you're using a Lambda#Edge function (appears you are). Then you can view the Request Event structure here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#lambda-event-structure-request
You can see the actual value of the request URI field by using console.log and checking the respective logs in Cloudwatch.
The problem might be this line:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
If you're strictly looking to check if a JavaScript string contains another string in it, you probably want to do this instead:
if (!request.uri.toLowerCase().indexOf("/private/") !== -1) {
Or better yet, using more modern JS:
if (!request.uri.toLowerCase().includes("/private/")) {

How can response header can be modified?

i have a problem that break my mind since 2 days.
request( {url: url } , function(err,res, body){ res.headers['...'] = ...; return res }).pipe(response);
I thought this code change the header from the first response then put him on the second response. but NOT, all attempt fail. I try and I try but NOTHING, NOTHING WORKK.
Look, I'm really in peace and open-minded.
that's my code clear and concise :
modify_header(err,res,body){
var header = res.headers;
header['x-frame-options'] = null;
header['Set-Cookie'] = 'HttpOnly;';
return res;
}
request_src(req,response){
let isabsolute = this.decode_href(req.url);
if(!isabsolute) {
request.get({ url : this.url+req.url , headers : this.headers },this.modify_header).pipe(response);
}else{
request.get({ url : isabsolute , headers : this.headers },this.modify_header).pipe(response);
}
return false;
}
request_src(req,response) is a function called in http.createServer so, req & response are just the request from clients.
then, when i do request.get( {url:this.url ... I send client's request to an another site, like a proxy. but i need to change the header between the "other site" and the client. and believe me for sure, function modify_header modify nothing.
no, i lie just a little, when i set header['x-frame-options'] = null;res.headers is equals to null. that's ok
BUT, in the browser (client side) It just doesn't work that way. 'x-frame-options' is deny or something that's not mine (the same for cookie).
can you help me please, I pull out my hair since 2 days and this isn't good for me.
thank you.
The issue is that callbacks actually happen after a request is complete, which is way after the pipe has already begun streaming data to the response. You probably want to hook into the response event, which would look something like this:
request.get({ url: this.url + req.url, headers: this.headers })
.on('response', function (resp) {
resp.headers['my-custom-header'] = 'blah';
})
.pipe(response);
The response event is emitted before streaming to the destination (which in this case, is back to the original caller), so you should be able to do what you want using this method.
my code looks like that right now :
modify_header(res){
var header = res.headers;
header['x-frame-options'] = null;
header['Set-Cookie'] = 'HttpOnly;';
return res;
}
request_src(req,response){
console.log('original: ',req.url);
let isabsolute = this.decode_href(req.url);
if(!isabsolute) {
request.get({ url : this.url+req.url , headers : this.headers })
.on('response',this.modify_header)
.pipe(response);
}else{
request.get({ url : isabsolute , headers : this.headers})
.on('response',this.modify_header)
.pipe(response);
}
return false;
}

angular2-rc1 http.post not working correctly

I am creating one angular2 app in which I am using http in my service to make POST call to mongoDB.
When I am making a POST call for the first time it is working fine i.e. entry is inserted into database correctly but when I am sending the data for second time it is not getting inserted.
If I am reloading the page after first insertion then its working fine.
I did some debugging and found that during second request my req.body is blank.
Here is my code:
page.service.ts
savePage(page: Object) {
this.headers.append('Content-Type', 'application/json');
let url = this.baseUrl+'/pm/pages/';
let data={};
data["data"]=page;
console.log(data); //this is printing both times correctly
//on second request data is blank where as it works correctly for first time
return this.http.post(url, JSON.stringify(data),{headers: this.headers})
.map((res: Response) => res.json()).catch(this.handleError);
}
Here is my data in req.body shown in node services.
First Request:
body:{
data:{
name: 'wtwetwet',
desc: 'wetwetwetetwte',
isPublic: true,
createdBy: 'Bhushan'
}
}
Second Request
body: {}
any inputs?
It looks more like a backend thing. You should however include the code that subscribes on this http call.
By the way, why are you using RC1? Angular 2 is now on RC5.
I finally realised that my method used to set content-type each time it was getting called.
Thus changing code from :
savePage(page: Object) {
this.headers.append('Content-Type', 'application/json');
let url = this.baseUrl+'/pm/pages/';
let data={};
data["data"]=page;
return this.http.post(url, JSON.stringify(data),{headers: this.headers})
.map((res: Response) => res.json()).catch(this.handleError);
}
to:
savePage(page: Object) {
this.headers=new Headers();
this.headers.append('Content-Type', 'application/json');
let url = this.baseUrl+'/pm/pages/';
let data={};
data["data"]=page;
return this.http.post(url, JSON.stringify(data),{headers: this.headers})
.map((res: Response) => res.json()).catch(this.handleError);
}
did the trick for me.
Actually we need to set headers only once while making a rest call,but in my code it was already set thus creating new Headers Object helped me clearing any previously set configurations.
Thanks for the help guys.

Resources