I am having trouble with receiving array data as it flows from another source into my client. My goal is to have the HTML document populate with data from the array as it is received by the server.
Server:
const keywordsList = [];
const keywordsListSerialize = JSON.stringify(keywordsList);
const arrayToString = JSON.stringify(Object.assign({}, keywordsList))
// const keywordsListObject = JSON.parse(arrayToString);
app.use(cors())
const bodyParser = require('body-parser');
app.get('/', (req, res) => {
// res.writeHead(200, {
// 'Connection': 'keep-alive',
// 'Content-Type': 'text/event-stream',
// 'Cache-Control': 'no-cache',
// });
//res.flushHeaders();
res.send(keywordsList);
//res.write(arrayToString);
//res.status(200).send(arrayToString);
//res.status(500).send({ error: 'something blew up' })
});
As you can see from the server code, I have tried multiple variations of sending data as a JSON object/just an array or a string and I can't seem to get ANY of it to even show up on my client.
Client:
var xhttp = new XMLHttpRequest();
console.log(xhttp);
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(xhttp.responseText)
var jsonObj = JSON.parse(xhttp.responseText);
for(i = 0; i < jsonObj.length; i++) {
document.getElementById("keywordsList").innerHTML = jsonObj;
}
}
};
xhttp.open("GET", "/", true);
xhttp.send();
The client does two very basic but interesting things. 1. the xhttp.responseText is the whole HTML file which makes me think I should be handling the request somehow in my server (I figured I could get away with just constantly streaming data to the client) and 2. Sends me an error
VM812:1 Uncaught SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttpRequest.xhttp.onreadystatechange
I believe I am having multiple issues. I understand that I will want to send data almost definitely as a JSON object and parse it on the client but at the most basic examples, I can't even get that to work. I have double checked that how I have my client and server setup is correct and if need be, I can discuss how it's setup as to make sure they are linked correctly locally.
I am looking for guidance as well as possibly a technical answer. Please do not just link me ajax documentation because I have probably read it 3 times over (minimum) at this point. I thought of just throwing everything away and using a websocket as it would accomplish what I am trying to achieve but that means learning all of that.
Thank you for your time.
So I figured it out eventually. The only post above from someone else is correct. It's that simple to send data across. Unfortunately, when trying to understand AJAX and express at the same time, you get confused on whether you are really sending data AND if that data is structured correctly so the client doesn't just throw out errors. I was conflating AJAX and express issues together which could have been resolved quicker had I understood how to correctly test stuff but alas, I am by myself working this all out.
For any poor sap who might come across this, probably don't use XMLHttpRequests and just use JQuery.
Thanks for the response.
Server:
const express = require('express')
const app = express()
const port = 3000
const keywordsList = ['apple', 'banana', 'cucumber']
app.get('/', (req, res) => {
res.send(JSON.stringify(keywordsList))
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Testing the web server:
curl http://localhost:3000
["apple","banana","cucumber"]
To iterate over the response in the client:
const arrayOnClientSide = JSON.parse(xhttp.responseText);
arrayOnClientSide.forEach(element => console.log(element));
which outputs:
apple
banana
cucumber
Note that every time you call .innerHTML you will overwrite whatever value it previously held, so construct the presentation format of your display value first, then assign it to innerHTML at the end.
Related
I am trying to figure out what is the best way to prevent random people access/make requests to the server.
Currently, everyone that have the url and the endpoint can easily make requests and I believe it's kind of security breach.
I am using Express.js that hosting static build of React.js
Here is an example of a call:
// Client:
async function getData(id){
try {
const res = await axios.get(`${backendDomain}/data/${id}`)
const data = res.data
return data
} catch (error) {
...
}
}
// Server:
app.get('/data/:id', function(req, res) {
...logic
res.send(data);
});
I tried adding to the Client "x-api-key" header and pass an api key that only I have and add a middleware that will check the api and see if it passed currectly from the client. But obviously it is not a good solution because you can see the key on "Network" section while inspecting.
What can I do?
Looking for the best way to prevent random people accessing the data
I'm trying to make a POST request to Express, but whenever I do, I can't access req.query. It's always empty.
The POST request works in Postman, but I can't get it to work in React.
I am wondering if whatever axios is sending over is not readable by the Express middleware. I'm new to Express, so I'm sure I'm missing something basic. Thanks for reading!
From my React file:
tryPost = () => {
axios.post('/login', {
firstName: 'Tom',
lastName: 'Rains'
});
}
From my Express file:
app.post('/login', (req, res) => {
console.log('test'); //prints test
console.log(req.query); //prints as {}
})
req.query refers to query string parameters, in your example you POST a JSON formatted body, no query string parameters are sent hence req.query is empty.
Depending on what way you intend to POST the data, if you want to pass it as query string data then you need to do:
axios.post('/login?firstName=Tom&lastName=Rains');
And then your code would work as is. However, If you want to POST the data as a body (like your example), then there is an additional change that needs applied in your express app i.e.
app.use(express.json())
This will ensure the JSON body gets parsed, then you can access the data via req.body from your route.
Note - Make sure this is configured before you setup your routes
Working on a SSR app using React/Express i'm trying to get a grip on renderToNodeStream and streams in general.
I have a large page (400kb not compressed) and using renderToNodeStream give a really good TTFB (time to first byte), all I miss is some compression to make the HTML response smaller sent back smaller but I cant make it work with renderToNodeStream.
am I missing something ?
Is it possible to stream freshly rendered responses AND compress them ?
const stream = renderToNodeStream(<MyApp/>)
// this doesn't work
stream.pipe(zlib.createGzip())
stream._flush = zlib.Z_SYNC_FLUSH
stream.pipe(
res,
{ end: "false" }
)
// stream.on("data", data => {
// console.log(data)
// })
stream.on("end", () => {
res.end(pageEnd())
})
If you are using express and compression middleware, you probably need to write
res.setHeader('Content-Type', 'text/html');
before streaming to the client.
As shown in compression middleware code, if Content-Type does not exist, the middleware won't compress the content.
The final code would be:
res.setHeader('Content-Type', 'text/html');
const stream = renderToNodeStream(<MyApp/>)
stream.pipe(
res,
{ end: "false" }
)
stream.on("end", () => {
res.end(pageEnd())
})
https://reactjs.org/docs/react-dom-server.html#rendertonodestream
renderToNodeStream() returns a readable stream. With this function you are assembling a snippet of HTML and send it to the users' browser and the process repeats.
The reason you may want to take this approach as you know is for performance, but I am not too sure if compression is a part of the process.
I'm having much trouble getting OAuth2 to work with a generic OAuth2 provider. Here's the situation.
A service provides an OAuth2 authentication method to where I want to authorize with. I've created an AngularJS app that has the following configuration for satellizer:
authProvider.baseUrl = 'http://localhost:3030/user/authorize';
$authProvider.oauth2({
name: 'customname',
url: '/token',
clientId: 'someapp',
requiredUrlParams: ['scope'],
scope: ['profile'],
authorizationEndpoint: 'http://location.to.oathserver',
redirectUri: 'http://localhost:3000'
});
The baseUrl points to my node server that should handle the middleware part.
I've also the following code that triggers the authentication part.
$scope.authenticate = function(provider) {
$auth.authenticate(provider)
.then(function(response) {
console.log(response);
})
.catch(function() {
//something went wrong
});
}
So far this all seems to work great and looks very similar to what is documented by Satellizer! Now once I start the angular app and start the authentication I see requests coming by that target my Node service.
Next I've my node.js service that hooks to the 'user/authorize/token' URL. Here's the code:
router.options('/authorize/token', function(req, res, next) {
//var token = req.header('Authorization').split(' ')[1];
res.end();
});
and:
router.post('/authorize/token', function(req, res, next) {
var authCode = req.param('code');
var cliendId = req.param('clientId');
var payload = jwt.decode(authCode, 'mySecret');
});
Here's where it all seems to go wrong. First I seem to get an OPTIONS request. I've not really an idea what to do with it as I can't seem to find anything in the documentation about an OPTIONS request. I thought it would might contain the 'Authorization' header but that doesn't seem the case so I close the connection with a res.end();
I also inspected the request in Chrome but I can't seem to find a header that has this exact name.
Next I get a POST request. This does seem to contain some things, hooray! I get the following object:
{
code: "ZFFeat9pWfHzw4rGmjFYwucPRMFnBOkd2odEObvo",
cliendId: "someapp",
redirectiUri: "http://localhost:3000"
}
This looks to me like the authorization code that I should have to decode. That's what you see me trying as well in the code above. Unfortunately this seems to throw me an error
Error: Not enough or too many segments
This tells me I'm doing probably something wrong, and I got stuck.
I do have some PHP code that seems to work for someone else but I don't fully understand and can't really relate the code to my code since PHP is not my speciality and node.js/JavaScript not his. So here goes the PHP code:
handle_cors(); // Handle CORS for cross domain requests
// Get JSON data
$input = json_decode(file_get_contents("php://input"), true);
// Create Provider
$provider = new SomeApp\OAuth2\Client\Provider\SomeApp([
'clientId' => 'someapp',
'clientSecret' => 'mySecret',
'redirectUri' => $input['redirectUri'],
]);
// Optional: Now you have a token you can look up a users profile data
try {
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken('authorization_code', [
'code' => $input['code']
]);
// We got an access token, let's now get the user's details
$user = $provider->getResourceOwner($token);
header('Content-Type: application/json');
$result = $user->toArray();
$result['token'] = create_token('my-example-key', $user->getId());
echo json_encode($result);
exit();
} catch (Exception $e) {
// Failed to get user details
exit('Oh dear...' . $e->getMessage());
}
Hopefully someone can help me out! Thanks in advance.
Sorry guys, I've been able to solve it myself. I found out that I was missing some URL's to POST to and GET from. After that the examples from Satellizer became clear and was able to use them almost as a carbon copy.
I have a doozy that I can't figure out whats going on.
In Mongo, I do a document.find(), which returns an array of object/s - All good.
I'm then trying to send on two objects back to the Angular Controller, the document object/s and the length of the array of objects.
Code:
function loadConnections(req, res) {
getConnections(req.user)
.then(function(results){
console.log('here');
console.log(results);
console.log(results.length);
var returnObject = {}
returnObject.count = results.length;
//returnObject.results = results[0]; // PROBLEM LINE
res.status(200).send(returnObject);
});
}
Problem I'm facing. In this scenario, it returns an array with 1 object. The array looks like:
[{id: XXX, test: YYY, test1: ZZZ}]
These have been what I've tried and tested:
returnObject.results = results[0].id; // works
returnObject.results = results[0].test; // works
returnObject.results = results[0].test1; //works
returnObject.results = results[0]; // doesn't work
returnObject.results = results; //doesn't work
But if I try to pass the entire object or the entire array, it hits an issue and doesn't send the response to the controller.
Any thoughts?
If you are using express, try to do a
res.send(200, returnObject);
or
res.json(200, returnObject);
that should do the trick!
Personally I would just calculate the length of the array after it arrives in my Angular client, rather than on the server. However, you should be able to accomplish what you're trying to do by doing this:
var returnObject = {
'count': results.length,
'results': results
};
res.json(returnObject);
Here's the doc: http://expressjs.com/en/api.html#res.json
Also just for general troubleshooting, the first thing you want to do is figure out where the inconsistency is starting. Does the data look right on the server before you send the response? Does the data look right on the client when your API handler receives the response? (I'm assuming $resource, or maybe you're using $http). So add a line like this just before you send the response from the server, and then add a similar line just after you receive the response in the client:
console.log(JSON.stringify(returnObject, null, 2));
Turns out it was my lodash library. I was using _.pluck and somewhere along the way, it's stopped working. Had to update it to _.map.