I am not entirely sure if this is a bad thing or not, but i was monitoring (logging) what incoming http request i get with this piece of code (note that running an NodeJS application on the scalable OpenShift platform):
function onRequest(request, response)
{
var date = new Date();
console.log(date.toUTCString() + " A request was made with url: " + request.url + " and header: " + JSON.stringify(request.headers));
// continue handling the request
}
The results i get are the following (every 2 seconds):
Fri, 07 Mar 2014 09:43:59 GMT A request was made with url: / and header: {}
Fri, 07 Mar 2014 09:44:01 GMT A request was made with url: / and header: {}
Fri, 07 Mar 2014 09:44:03 GMT A request was made with url: / and header: {}
So i am wondering if this is normal behaviour for a scalable NodeJS app (with a MongoDB database gear attached) in openshift, or is this something that could cause problems?
Sincerely,
Hylke Bron
If you are running a scaled application, then that is haproxy making sure that your application is up so that it can forward requests to it. You can change haproxy settings in your haproxy/haproxy.cfg file on your main gear.
I am using this method to inform OPENSHIFT that the application is alive
app.head("/", function(req, res, next){
res.status(200).end();
});
Since I didn't want to mess with haproxy.cfg, I did this and it seems to work. In your main app, add a middleware function to abort on the ping
function ignoreHeartbeat(except) {
except = except || 0;
var count = except;
return function(req, res, next) {
if (req.headers["x-forwarded-for"])
return next();
if (except > 0) {
if (--count <= 0) {
count = except;
return next();
}
}
res.end();
}
}
app.use(ignoreHeartbeat(1000));
Place that code before the call to setup the logger (Express 3 example shown below)
app.use(express.logger(...))
This logs out every 1000th ping. Set except to 0 or -1 to ignore all the pings.
Related
When I make requests to a server with Postman(an api service), chrome automatically makes a cookie. However, when I make a request with my nodejs server, the cookie is not being made even thought the request is successful.
//Headers
var options = {
method: 'GET'
};
options.headers = {};
options.headers.Authorization = auth;
options.url = urlm;
console.log(options);
request(options, function(error,response,body) {
res.status(200).send(response.headers);
});
The response header is
{"date":"Tue, 23 Feb 2016 20:06:57 GMT","server":"Jetty(9.2.1.v20140609)","x-csrf-header":"X-CSRF-TOKEN","expires":"Thu, 01 Jan 1970 00:00:00 GMT","x-csrf-token":"xxxxxxxxxxx","cache-control":"no-store","content-type":"audio/mpeg","set-cookie":["JSESSIONID=uiqwnksadbohqjkq675d;Path=/;HttpOnly"],"connection":"close","transfer-encoding":"chunked"}
Pass { jar: true } in your request options.
From the documentation:
jar - If true, remember cookies for future use (or define your custom cookie jar; see examples section)
I'd like to send a response code of 401 if the requesting user is not authenticated, but I'd also like to redirect when the request was an HTML request. I've been finding that Express 4 doesn't allow this:
res.status(401).redirect('/login')
Does anyone know of a way to handle this? This might not be a limitation of Express, since I'm asking to essentially pass two headers, but I don't see why that should be the case. I should be able to pass a "not authenticated" response and redirect the user all in one go.
There are some subtle diferences with the methods for sending back a new location header.
With redirect:
app.get('/foobar', function (req, res) {
res.redirect(401, '/foo');
});
// Responds with
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Location: /foo
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 33
Date: Tue, 07 Apr 2015 01:25:17 GMT
Connection: keep-alive
Unauthorized. Redirecting to /foo
With status and location:
app.get('/foobar', function (req, res) {
res.status(401).location('/foo').end();
});
// Responds with
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Location: /foo
Date: Tue, 07 Apr 2015 01:30:45 GMT
Connection: keep-alive
Transfer-Encoding: chunked
With the original (incorrect) approach using redirect:
app.get('/foobar', function (req, res) {
res.status(401).redirect('/foo')();
});
// Responds with
HTTP/1.1 302 Moved Temporarily
X-Powered-By: Express
Location: /foo
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 38
Date: Tue, 07 Apr 2015 01:26:38 GMT
Connection: keep-alive
Moved Temporarily. Redirecting to /foo
So it looks like redirect will abandon any previous status codes and send the default value (unless specified inside the method call). This makes sense due to the use of middleware within Express. If you had some global middleware doing pre-checks on all requests (like checking for the correct accepts headers, etc.) they wouldn't know to redirect a request. However the authentication middleware would and thus it would know to override any previous settings to set them correctly.
UPDATE: As stated in the comments below that even though Express can send a 4XX status code with a Location header does not mean it is an acceptable response for a request client to understand according to the specs. In fact most will ignore the Location header unless the status code is a 3XX value.
You can certainly send a Location: /login header alongside with your 401 page, however, this is ill-advised, and most browsers will not follow it, as per rfc2616.
One way to do overcome this, is to serve <meta http-equiv="refresh" content="0; url=/login"> alongside with your 401 page:
res.set('Content-Type', 'text/html');
res.status(401).send('<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=/login"></head></html>');
I fell on the same issue and decided to use the session to handle this kind of job.
I didn't want to have an intermediate view...
With the below code, I can redirect to the homepage, which will be rendered with 401 unauthorized code.
app.get('patternForbiddenRoute', (req, res, next) => {
// previousCode
if (notForbidden === true) {
return res.render("a_view");
}
req.session.httpCode = 401;
res.redirect('patternHomeRoute');
});
app.get('patternHomeRoute', (req, res, next) => {
res.render("my_home_view", {}, (error, html) => {
// ... handle error code ...
const httpCode = req.session.httpCode;
if (httpCode !== undefined) {
delete req.session.httpCode;
res.status(httpCode);
}
res.send(html);
}));
});
I'm trying out restify, and though I'm more comfortable with Express, so far it's pretty awesome. I'm trying to set the content type header in the response like so:
server.get('/xml', function(req, res) {
res.setHeader('content-type', 'application/xml');
// res.header('content-type', 'application/xml'); // tried this too
// res.contentType = "application/xml"; // tried this too
res.send("<root><test>stuff</test></root>");
});
But the response I get back is instead application/octet-stream.
I also tried res.contentType('application/xml') but that actually threw an error ("Object HTTP/1.1 200 OK\ has no method 'contentType'").
What is the correct way to set the content type header to xml on the response?
Update:
When I do console.log(res.contentType); it actually outputs application/xml. Why is it not in the response headers?
Curl snippet:
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /xml?params=1,2,3 HTTP/1.1
> User-Agent: curl/7.39.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/octet-stream
< Content-Length: 8995
< Date: Mon, 23 Feb 2015 20:20:14 GMT
< Connection: keep-alive
<
<body goes here>
Turns out the reason this was failing is because I was not sending the response using Restify's response handler; it was defaulting to the native Node.js handler.
Where I was doing this:
res.send(js2xmlparser("search", obj));
I should have been doing this:
res.end(js2xmlparser("search", o));
// ^ end, not send!
When I do console.log(res.contentType); it actually outputs application/xml. Why is it not in the response headers?
All you've done there is set a property on the res object. And because this is JavaScript, that works fine and you can read the property value back, but that's not the correct API for either node core or restify, so it is ignored by everything other than your code.
Your res.header("Content-Type", "application/xml"); looks correct to me based on the restify docs you linked to. Therefore my hunch is your tooling may be misleading you. Are you sure you are seeing the raw values in the response (many developer tools will unhelpfully "prettify" or otherwise lie to you) and you are hitting the route you really think you are? Output of curl -v or httpie --headers would be helpful.
It is possible to return application/xml by adding a formatter to the server instance at server creation:
var server = restify.createServer( {
formatters: {
'application/xml' : function( req, res, body, cb ) {
if (body instanceof Error)
return body.stack;
if (Buffer.isBuffer(body))
return cb(null, body.toString('base64'));
return cb(null, body);
}
}
});
Then at some part of the code:
res.setHeader('content-type', 'application/xml');
res.send('<xml>xyz</xml>');
Please, take a look at: http://restify.com/#content-negotiation
You can send the XML response using sendRaw instead of send. The sendRaw method doesn't use any formatter at all (you should preformat your response if you need it). See an example below:
server.get('/xml', function(req, res, next) {
res.setHeader('content-type', 'application/xml');
res.sendRaw('<xml>xyz</xml>');
next();
});
How can I adjust the Hapi reply function such that it would reply JSON objects only?
Should I send it as plain and send? I seem not to find a good example
Here is some edit - added some sample code to understand what's happening.
The route:
server.route({
method: 'GET',
path: '/user/',
handler: function (request, reply) {
var ids = null;
mysqlConnection.query('SELECT ID FROM Users;',function(err,rows,fields){
if(err) throw err;
ids = rows;
// console.log(ids);
reply(ids);
});
}
});
The reply:
<html><head></head><body>
<pre style="word-wrap: break-word; white-space: pre-wrap;">[{"ID":1},{"ID":2},{"ID":3},{"ID":4},{"ID":5},{"ID":6},{"ID":7},{"ID":8},{"ID":9},{"ID":10},{"ID":11},{"ID":12},{"ID":13},{"ID":14},{"ID":15},{"ID":16},{"ID":17},{"ID":18},{"ID":19},{"ID":20},{"ID":21}]
</pre></body></html>
I hope I understand the question ok. Are we talking version 8.x ? For me it seems the default. With this code as a route handler,
folders: {
handler: function( request, reply ) {
'use strict';
reply({
folders: folders
}).code( 200 );
}
},
and doing
curl http://localhost:3001/folders
I get the following output
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3001 (#0)
> GET /folders HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:3001
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: application/json; charset=utf-8
< cache-control: no-cache
< content-length: 266
< accept-ranges: bytes
< Date: Tue, 03 Feb 2015 23:19:31 GMT
< Connection: keep-alive
<
{folders ..... }
Also, note that I only call reply()not return reply()
HTH
As for v17 and above, the reply() interface was removed. Now the handlers uses async functions, and you can just return the value.
From the hapi docs example:
// Before
const handler = function (request, reply) {
return reply('ok');
};
// After
const handler = function (request, h) {
return 'ok';
};
Using hapi's reply(data) and passing a data object will do the work for you. Internally, hapi will create the appropriate JSON of your data object and respond it.
There's a tutorial on how to reply JSON for a given request using hapi that might give some more insights.
Using v17 and above, simply returning a naked string doesn't result in a json encoded reply.
Use return JSON.stringify() to ensure the string is json encoded
e.g.
function (request, h) {
return JSON.stringify('ok');
};
I am experiencing occasional retries for a POST request, when there is no response from server due to timeout. All modern browsers have retry logic for idempotent requests (GET, HEAD, etc) but I am unable to reason out why it happens for a POST request.
I am testing this case using a simple node.js server with 3 routes and chrome browser .
/ : gives a html page with jquery and code snippets to fire ajax requests
/hi : gives a text response 'hello'
/sleep : request will timeout without any response
By default, node.js http server times out a request after 2 minutes.
retry.js
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
console.log(new Date() + ' ' + req.method + ' request on ' + req.url);
if (req.url === '/sleep') {
console.log('!!! sleeping');
} else if (req.url === '/') {
html = "$.post('/hi', {'for':'server'}, function() { console.log(arguments) } ).error(function() { console.log(arguments) })";
html += "<br><br>";
html += "$.post('/sleep', {'for':'infinite'}, function() { console.log(arguments) } ).error(function() { console.log(arguments) })";
html += '<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>';
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(html);
} else {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('hello');
}
});
server.listen(2020);
console.log('server listening on port 2020');
run it
$ node retry.js
server listening on port 2020
1
Load this page in browser http://localhost:2020
Fri Mar 01 2013 12:21:59 GMT+0530 (IST) GET request on /
Fri Mar 01 2013 12:21:59 GMT+0530 (IST) GET request on /favicon.ico
2
From Dev console, fire an ajax POST request to /hi using jquery
$.post('/hi', {'for':'server'}, function() { console.log(arguments) } ).error(function() { console.log(arguments) })
Fri Mar 01 2013 12:22:05 GMT+0530 (IST) POST request on /hi
3
Fire a POST request to /sleep, results in a retry after 2 mins and errors out after 4 mins.
$.post('/sleep', {'for':'infinite'}, function() { console.log(arguments) } ).error(function() { console.log(arguments) })
server logs shows 2 requests
Fri Mar 01 2013 12:22:21 GMT+0530 (IST) POST request on /sleep
!!! sleeping
Fri Mar 01 2013 12:24:21 GMT+0530 (IST) POST request on /sleep
!!! sleeping
Firing it again, errors out in 2 mins without any retry.
Fri Mar 01 2013 12:30:01 GMT+0530 (IST) POST request on /sleep
!!! sleeping ?
It's not getting retried until we fire a request to /hi (or any other url) that results in a response. And retry happens for just one subsequent request to /sleep.
In browser, the network tab shows the pattern like
/hi - success
/sleep - cancelled - 4 mins (retry happens)
/sleep - cancelled - 2 mins (no retry)
/sleep - cancelled - 2 mins (no retry)
/hi - success
/sleep - cancelled - 4 mins (retry happens)
/sleep - cancelled - 2 mins (no retry)
/sleep - cancelled - 2 mins (no retry)
Question
Though we need to design our web app to tolerate these extra requests (either by browsers or any other intermediaries), this inconsistent browser retries looks weird. I had observed this behaviour in chrome (v16 & v24) & firefox.
Can someone help me to understand the browser retry logic behind timed out non-idempotent requests ?
Other relevant stackoverflow questions
What happens when no response is received for a request? I'm seeing retries
Browsers retry requests (including POSTs) when the connection is closed before receiving a response from the server. This is defined in the HTTP 1.1 Spec Section 8.2.4.