Tuesday, April 17, 2012

JSON services in node.js

For a client I am working on a mobile app that needs to get the temperature from GPS. I have found a weather service at geonames.org that looks pretty good so I will make a node.js script that retrieves the weather from GPS coordinates and returns the data. So what I need to do is get the parameters from a URL request, send a HTTP request to the weather server, get the response, and return the response to the sender.

I was surprised that I did not find this so quickly by a Google search, so I hereby write it up and hope it will help someone else. I did find some things that will help so I will assemble these together.

Of course, the first task is to build a HTTP server, easily done by copying the sample from the node.js website. To get the request parameters:
and to send the outgoing HTTP request:

Of course, there is a bit of functional programming to make the pieces work together asynchronously. I will use CoffeeScript since it should make the functional programming much easier to understand.

I: Get parameters from HTTP request

var http = require('http');
var url = require('url');

http.createServer(function (req, res) {

// http://stackoverflow.com/questions/6912584/how-to-get-get-query-string-variables-in-node-js
var url_parts = url.parse(req.url, true);
var query = url_parts.query;

  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World query: ' + JSON.stringify(query) + '\n');
}).listen(8082, '62.212.66.2');

on server: node test1.js

and from a test client (my Mac):
$ curl "http://62.212.66.2:8082/findNearByWeatherJSON?lat=43&lng=-2&username=demo"
Hello World query: {"lat":"43","lng":"-2","username":"demo"}

II. Add outgoing HTTP request

On server: npm install -g js2coffee and js2coffee test1.js > test1.coffee

http = require("http")
url = require("url")
http.createServer((req, res) ->
  url_parts = url.parse(req.url, true)
  query = url_parts.query
  res.writeHead 200,
    "Content-Type": "text/plain"

  res.end "Hello World query: " + JSON.stringify(query) + "\n"
).listen 8082, "62.212.66.2"

But first we want to add the outgoing HTTP request from the Javascript sample in http://docs.nodejitsu.com/articles/HTTP/clients/how-to-create-a-HTTP-request:

var http = require('http');
var url = require('url');

http.createServer(function (req1, res) {

// http://stackoverflow.com/questions/6912584/how-to-get-get-query-string-variables-in-node-js
var url_parts = url.parse(req1.url, true);
var query = url_parts.query;

var options = {
  host: 'api.geonames.org',
  path: '/findNearByWeatherJSON?lat=43&lng=-2&username=demo'
};

callback = function(response) {
  var str = '';

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

  //the whole response has been recieved, so we just print it out here
  response.on('end', function () {
    console.log(str);
  });
}

http.request(options, callback).end();


  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World query: ' + JSON.stringify(query) + '\n');
}).listen(8082, '62.212.66.2');

On the server:  node test1.js

Upon receiving a request from the client:
{"weatherObservation":{"clouds":"few clouds","weatherCondition":"n/a","observation":"LESO 170730Z 17003KT 140V200 9999 FEW048 BKN069 07/02 Q1020","windDirection":170,"ICAO":"LESO","elevation":8,"countryCode":"ES","lng":-1.8,"temperature":"7","dewPoint":"2","windSpeed":"03","humidity":70,"stationName":"San Sebastian / Fuenterrabia","datetime":"2012-04-17 07:30:00","lat":43.35,"hectoPascAltimeter":1020}}

So there are now two problems: (i) including the actual coordinates from the client and (ii) putting the received data into the response. Both of these problems will be easier to solve in the CoffeeScript version.

III. Putting it all together

On server (again): js2coffee test1.js > test1.coffee

http = require("http")

url = require("url")
http.createServer((req1, res) ->
  url_parts = url.parse(req1.url, true)
  query = url_parts.query
  options =
    host: "api.geonames.org"
    path: "/findNearByWeatherJSON?lat=43&lng=-2&username=demo"

  callback = (response) ->
    str = ""
    response.on "data", (chunk) ->
      str += chunk

    response.on "end", ->
      console.log str

  http.request(options, callback).end()
  res.writeHead 200,
    "Content-Type": "text/plain"

  res.end "Hello World query: " + JSON.stringify(query) + "\n"
).listen 8082, "62.212.66.2"

So the next steps are:
  • put the incoming request data into the outgoing weather request
  • Indent the res.writeHead and res.end code by 4 spaces, to include these in the callback function, and keep the http.request statement outside the callback block
  • Of course, JSON.parse the response data, and
  • return the JSON response with the desired data, using the application/JSON content type
So here is the complete CoffeeScript:

http = require("http")

url = require("url")
http.createServer((req1, res) ->
  url_parts = url.parse(req1.url, true)
  query = url_parts.query
  options =
    host: "api.geonames.org"
    path: "/findNearByWeatherJSON?lat=#{query.lat}&lng=#{query.lng}&username=demo"

  callback = (response) ->
    str = ""
    response.on "data", (chunk) ->
      str += chunk

    response.on "end", ->
      console.log str

      data = JSON.parse str

      res.writeHead 200,
        "Content-Type": "application/json"

      res.end JSON.stringify
        temperature: data.weatherObservation.temperature

  http.request(options, callback).end()

).listen 8082, "62.212.66.2"

Compile to Javascript and run on Node, here are the results from the client:
{"temperature":"8"}

Of course, it would be nice to include the locality of the weather station, cleanup the log output, etc. etc.

No comments:

Post a Comment