PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : node.js - warten auf http response möglich?


Watson007
2015-01-02, 17:30:03
hier kennt sich doch bestimmt jemand mit node.js-Programmierung aus.

Der Anwender soll über einen URL-Aufruf einer node.js/express-Webanwendung einen http-get-Befehl auf dem Server auslösen, der die Daten aus der NoSQL-Datenbank zurückliefert (als JSON), und das Ergebnis soll dann direkt in der Webseite angezeigt werden. Das Problem ist hier der asynchrone Aufruf.

Ich kann die Daten erst dann in der Webseite anzeigen lassen wenn sie bereits zurückgeliefert wurden ;) Vermutlich ist node.js nicht die beste Voraussetzung für eine CRUD-Anwendung, aber andererseits wieder doch weil es http-Befehle und json direkt unterstützt und abgesehen davon auch recht einfach ist.

ich rufe über jquery eine Server-Funktion auf, die einen HTTP-GET-Befehl auslöst. Der Server soll auf das Ergebnis warten, und erst dann zurückkehren wenn die Callback-Funktion abgearbeitet wurde.

es gibt da so ein paar Bibliotheken dazu, aber die erwarten alle eine übergebene Funktion die danach aufgerufen werden soll. Der http-Call soll innerhalb von

router.get('/userlist', function(req, res) {
...

geschehen, also wenn die Url localhost/userlist aufgerufen wird soll ein GET-Befehl ausgeführt werden und auf dessen Ausführung gewartet werden. Die meisten Bibliotheken bringen mir nichts an der Stelle, denn bevor er aus dieser Funktion rausspringt soll er auf das Ergebnis der callback-funktion des http.get-befehls warten.

Es sieht so aus als ob wait.for meinen Anforderungen entspricht:

https://www.npmjs.com/package/wait.for

aber wie bekomme ich das mit einem http-request zum laufen?

var request = wait.forMethod(http, "request", options, callback);

und

var request = wait.for(http.request, options, callback);

funktionieren nicht. Jemand eine Idee? Es eilt leider etwas....

Marscel
2015-01-02, 18:20:00
Zeig mal, wie dein Code zwischen

router.get('/userlist', function(req, res) {

und deinem wait.forMethod aussieht.

Watson007
2015-01-02, 20:10:22
da ist zur Zeit nicht viel, bin ja am Testen....

var express = require('express');
var router = express.Router();
var http = require('http');
var request = require('request');
var wait = require('wait.for');
var async = require('async');

var str = '';


var options = {
host: ' meine interne IP-Adresse... ',
port: 80,
path: '/_users/_all_docs',
method: 'GET',
auth: 'benutzer:passwort'
};

callback = function(response) {
response.setEncoding('utf8');

var bodyarr = [];
var parsedResponse;

console.log('test');

//another chunk of data has been recieved, so append it to `str`
response.on('data', function (chunk) {
bodyarr.push(chunk);
});

//the whole response has been recieved, so we just print it out here
response.on('end', function () {
str = bodyarr.join('').toString();
parsedResponse = JSON.parse(str);
str = parsedResponse["rows"];
str = JSON.stringify(str);
console.log("server: " + str);
});
}

/* GET users listing. */
router.get('/userlist', function(req, res) {
//options.host = req.db;
var request = http.request(options, callback);
request.on('error', function(response) {
console.log('STATUS: ' + response.statusCode);
console.log('HEADERS: ' + JSON.stringify(response.headers));
});
request.end();

res.send(str);
});

den wait.for-Aufruf vor dem http-request habe ich zur Zeit rausgenommen weils ja nicht funktioniert... ich tendiere gerade dazu als Notlösung clientseitig das Browserfenster automatisch neu laden zu lassen nach 3 Sekunden...

speziell clientseitig wird ja gar nichts an Fehlermeldungen geworfen, das ist beim debuggen ätzend... oder kann ich das über ein Chrome-Addon einsehen? Firebug oder so?

Ich verwende gerade CouchDB als Datenbankserver

Marscel
2015-01-02, 22:03:15
Ja, das wait.for will ich ja sehen ... Ich vermute nämlich einfach, dass du das Handbuch nicht ganz gelesen hast: https://www.npmjs.com/package/wait.for#proper-use-

Watson007
2015-01-02, 22:50:39
werde ich nächste Woche nochmal probieren, ich habe es jetzt vorerst über einen Workaround gelöst... in der callback-Funktion von jQuery, die die Serverfunktion aufruft, lasse ich nach Erhalt der Daten die Webseite automatisch neu laden.

eigentlich die bessere Lösung als den Server zu blockieren, oder? Bin mir aber nicht sicher...

es funktioniert erstmal, jetzt kommt was anderes dran... bin etwas unter Zeitdruck gerade^^
schreiben in die Datenbank kann ja ruhig asynchron passieren... man ich habe kopfschmerzen

Marscel
2015-01-02, 23:02:30
Hört sich alles sehr race-conditionig und nicht-transactional an.

tatarus
2015-01-03, 13:26:02
Wer versucht asynchrone Aufrufe in einer eventbasierten Umgebung zu synchronisieren macht in der Regel was falsch. Dabei ist es vollkommen egal, ob man JS, LUA oder sonstwas benutzt. Generell kann man sich nämlich nie sicher sein, wann ein asynchroner Aufruf zurückkommt. Es ist ja gerade der Vorteil, dass man nicht warten muss, sondern auf Daten oder Fehler asynchron reagieren kann.

Bei einem Datenbankaufrauf, der länger dauert, könnte man z.B. die chunks von node.js direkt an den client weiterleiten. So könnte man die entsprechenden Ausgabefelder iterativ füllen. Man könnte auch die Datenbank und die Anfragen so strukturieren, dass weniger Daten auf einmal abgefragt werden müssen. Man könnte beim Klick auf den Abfragebutton im Browser, diesen deaktivieren und erst wieder aktivieren, wenn der Aufruf komplett ist. Der User könnte dann währenddessen andere Aktionen dürchführen und müsste nicht auf das Event warten. Man könnte die Daten auch über einen Websocket übertragen. Das geht schneller und man muss nicht dauernd neue Verbindungen aufmachen.

Dein Beispielcode wird übrigens eher sporadisch funktionieren. Bei res.send(str) ist str nur dann gültig, wenn der callback vom vorherigen request in 'end' gelaufen ist. Genau da darfst du eigentlich auch erst senden.

Watson007
2015-01-03, 16:06:25
also, wenn ich es so mache wartet er jedenfalls nicht:

function getData(){
var request = http.request(options, callback);
request.on('error', function(response) {
console.log('STATUS: ' + response.statusCode);
console.log('HEADERS: ' + JSON.stringify(response.headers));
});
request.end();
}

/* GET users listing. */
router.get('/userlist', function(req, res) {
//options.host = req.db;

wait.launchFiber(getData);

res.send(str);
});


und das hier geht nicht:

router.get('/userlist', function(req, res) {
//options.host = req.db;
var request = wait.for(http.request, options, callback);
request.on('error', function(response) {
console.log('STATUS: ' + response.statusCode);
console.log('HEADERS: ' + JSON.stringify(response.headers));
});
request.end();

res.send(str);
});

und das hier geht auch nicht:

var request = http.request(options, callback);
//request.end();
wait.forMethod(request,'end', null);

... beim request die End-Methode aufzurufen ist zwingend nötig....

Marscel
2015-01-03, 18:00:35
Ja, das wait.forMethod geht hier echt nicht so straight-forward. Dafür eine einfachere Methode:

var express = require("express");
var http = require("http");

var app = express();

var extCallback = function(extResponse, callback) {
var chunks = [];

extResponse.on("data", function(data) {
chunks.push(data);
});

extResponse.on("end", function() {
body = chunks.join("");
var ip = JSON.parse(body)["ip"];
callback(ip);
});
};

var indexHandler = function(callback) {
http.get({
"host": "ip.jsontest.com",
"path": "/"
}, function(extResponse) {
extCallback(extResponse, callback);
}).end();
};

app.get("/", function(request, response) {
indexHandler(function(ip) {
response.send("IP ADDRESS: " + ip);
});
});

var server = app.listen(8080, function() {});

Watson007
2015-01-03, 19:37:37
es funktioniert, danke :)

aber wieso wartet er jetzt bevor er aus der funktion

router.get('/userlist', function(req, res)

herausspringt? Oder springt er erst raus nachdem response gesetzt wurde?

Marscel
2015-01-03, 21:49:10
Der einzige, der beidiesem Code wartet, ist der Client, dass irgendwann mal eine HTTP-Antwort zurückkommt. In welchem Callback/Handler/Thread Node herumeiert, bis endlich mal was kommt, das sieht er ja nicht. Also greift man sich einfach den Zeitpunkt, wo entsprechende Daten angekommen sind.

Watson007
2015-01-03, 22:58:48
nun alles fein, aber: laut stackoverflow sieht das error-handling bei http.get so aus:

http.get({host: host}, function(res) {
...
}).on('error', function(e) {
console.log("Got error: " + e.message);
});

http://stackoverflow.com/questions/8656852/how-to-catch-node-js-http-error-on-nonexistent-host

aber der node.js-server loggt mir nichts wenn ich ein falsches Passwort für die Datenbank übergebe... der node.js-server zeigt mir "GET /users/userlist 200" an, für den ist alles in Ordnung, es werden nur keine Daten zurückgeliefert...

Aber ich kann zumindest die zurückgelieferten Daten auf null abfragen, das reicht wohl vorerst....

Marscel
2015-01-03, 23:18:42
Dein on("error") wird ja auch nur ausgelöst, wenn es einen Netzwerkfehler gibt. Was du suchst, ist wahrscheinlich HTTP-Fehlercode 401.

Watson007
2015-01-03, 23:21:54
ja genau... wie logge ich das denn?

edit: über ajax

merkwürdig, gibt mir bei Authentifizierungsfehlern den Code 200 mit "Unexpected end of Input" zurück...

Watson007
2015-01-04, 10:51:37
ich habe jetzt einen Login-Dialog und will dabei die Datenbankverbindung testen.

Wie mache ich dass er auf das http.get bei einer funktion wartet? Geht das nur mit anonymen funktionen, die man auf Variablen mappt und diesen dann die callback-funktion übergibt?

Aber wie realisiere ich das bei einer Funktion?

Marscel
2015-01-04, 12:31:11
Das ist total unerheblich in JS, das etwas, das du übergibst, muss einfach aufrufbar sein.

Watson007
2015-01-04, 14:22:29
aber wenn ich das hier mache:

function testConnection(db_host, db_port, db_path, db_user, db_password, callback) {
options.host = db_host;
options.port = db_port;
options.path = db_path;
options.auth = db_user + ":" + db_password;

indexHandler(function(data) {
//console.log("test");
//console.log(!(data == null));
callback(true);
//callback(!(data == null));
});
};

module.exports.testConnection = testConnection;


Aufruf:


app.post('/login',function(req,res){
db_user = req.body.user;
db_password = req.body.password;

var result;
usersJS.testConnection(db_host, db_port, db_path_users, db_user, db_password, function(data) {
result = data;
res.end("done");
});
console.log(result);
}); ;

zeigt er mir "undefined" an?!

Marscel
2015-01-04, 14:55:04
Was sollte er auch anders anzeigen? Das result wird ja asynchron bei dir gesetzt.

Watson007
2015-01-04, 15:12:13
auf diese Weise wartet er auch nicht, gibt immer noch undefined aus:

function test(callback) {
usersJS.testConnection(db_host, db_port, db_path_users, db_user, db_password, function(data) {
callback(data);
});
};

app.post('/login',function(req,res){
db_user = req.body.user;
db_password = req.body.password;

var result;
test(function(data) {
result = data;
res.send("done");
// res.end("done");
});
console.log(result);
});

aber vielleicht funktioniert das warten in der Post-Funktion ja nicht?!
die Webseite triggert den Post-Befehl bei einem Klick auf den login-Button:

var user,pass;
$("#submit").click(function(){
user=$("#user").val();
pass=$("#password").val();
$.post("http://localhost:3000/login",{user: user,password: pass}, function(data){
if(data==='done')
{
window.location.href="/index";
}
});
});

Marscel
2015-01-04, 15:24:55
Du bist hier in der NodeJS-Welt, also verabschiede dich mal komplett vom Konzept des Wartens, sonst kommt da bei dir auch nichts raus.

Und abgesehen davon wartet in diesem Code-Snippet schon gar nichts mehr - egal ob GET oder POST - und das ist auch in Ordnung so. Einzig dein Browser auf seine Antwort.

Wenn du prozedural denkst:


var a = A();
var b = B(a);
var c = C(b);
success(c);


Dann musst du in NodeJS so gut wie immer folgende Konstrukte bauen:


A(function(resultA) {
B(resultA, function(resultB) {
C(resultB, success);
});
});

Ein Konzept, dass das ein wenig streamlined, sind z.B. Promises (https://www.promisejs.org).

Wenn du mit dem "gibt undefined" dich auf das console.log beziehst, dann kommt da auch nichts raus, weil du hier dieses Prinzip noch nicht durchziehst.

Watson007
2015-01-04, 15:59:04
ich habs:

$(document).ready(function(){
var user,pass;
$("#submit").click(function(){
user=$("#user").val();
pass=$("#password").val();
$.post("http://localhost:3000/login",{user: user, password: pass}, function(data){
if(data==='done')
{
$.ajax({
type:"GET",
async: false,
processData: false,
url:"/users/test",
success:function(data) {
if (data == true)
{
window.location.href="/index";
} else {
window.location.href="/error";
}
}
})
}
});
});
});

wäre nur schön wenn er mir jetzt einen differenzierten Fehlercode auf der Fehlerseite anzeigen würde, er zeigt mir jetzt immer 404 an, egal ob falsche URL angegeben oder Datenbank-Authentifizierung nicht erfolgreich... Edit: über Authentifizierungsfehler gebe ich jetzt mir einer Messagebox bescheid statt über eine Weiterleitung auf die 404-Seite, geht auch