UNPKG

ggserver

Version:

GeoGate is an opensource GPS tracking server framework

339 lines (305 loc) 13.4 kB
/* * Copyright 2014 Fulup Ar Foll. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Object: Servs Ajax/Json REST request and basic html pages * * Rest/Json * 1) list all dev with current position [equivalent to telnet dev info]. * 2) return the last xxx info about a given device [equivalent to db search] * Note: To bypass CORS constrains http://api.jquery.com/jquery.getjson/ * just add ?jsoncallback=? at the and your JSON URL * jQuery/JsonP= "http://localhost:4001/geojson.rest?jsoncallback=?"; jQuery/Json = "http://localhost:4001/geojson.rest"; * Http/Pages * Return HTML page, to solve (Cross-Origin Request) error, that browser raise * when an Ajax request is sent to different destination. * Note: * In production you should configure a real web server to server your HTML pages * but hopefully this one should be enough for your development. * * References CORS (Cross-Origin Request): * http://www.html5rocks.com/en/tutorials/cors/ * http://geojson.org/geojson-spec.html * http://leafletjs.com/examples/geojson.html * http://leafletjs.com/examples/sample-geojson.js * http://geojsonlint.com/ [online geojson validation] * Debug * FireBug extension on firefox * sudo tcpdump -i lo -A port 8080 -c 10 [to verify Access-Control-Allow-Origin] */ 'use strict'; var DEMO_API_KEY=123456789; // this is only good for demo and test !!!! var Debug = require("../lib/_Debug"); var util = require("util"); var path = require("path"); var url = require("url"); var fs = require('fs'); // Adapter is an object own by a given device controller that handle nmeadata connection function DevAdapter (controller) { this.id = controller.svc; this.uid = "//" + controller.svcopts.adapter + "/" + controller.svc + ":" + controller.svcopts.port;; this.info = 'HttpdAjax'; this.debug = controller.svcopts.debug; // inherit debug from controller this.controller= controller; // keep a link to device controller and TCP socket this.control = 'http'; this.cors = controller.svcopts.cors || false; this.apikey = parseInt (Math.random()*123456789876); // should depend on user authentication // define root dir for html pages. if (controller.svcopts.rootdir !== undefined) this.rootdir= controller.svcopts.rootdir; else this.rootdir= path.join (__dirname, "../../www"); this.Debug (1,"%s rootdir=%s", this.uid, this.rootdir); }; // Import debug method DevAdapter.prototype.Debug = Debug; // This adapter ignore silently any command request DevAdapter.prototype.SendCommand = function(httpclient, action, arg1) { return (0); }; // return a json object with device name and possition DevAdapter.prototype.QueryDevList = function(question, request, response) { var gateway = this.controller.gateway; var backend = gateway.backend; // start with response header // validate syntaxe at http://geojsonlint.com/ var jsonresponse={"type":"FeatureCollection","features": []}; var srchost=request.headers.host; var query=question.query; var now=new Date(); // loop on device list for (var devid in gateway.activeClients) { var device= gateway.activeClients [devid]; if (device !== undefined && device.stamp !== undefined) { var devinfo=util.format ("Name:%s DevId:%s Position:[%s,%s]",device.name, devid, device.stamp.lon.toFixed (4),device.stamp.lat.toFixed(4)); var devurl =util.format ("[%s/geojson.rest,{key:%s,cmd:track,devid:%s,llist:%s}]",srchost, query.key, devid, query.llist||10); jsonresponse.features.push ( {"type":"Feature" ,"geometry": {"type":"Point" ,"coordinates" :[device.stamp.lon, device.stamp.lat] ,'sog' : device.stamp.sog ,'cog' : device.stamp.cog ,'age' : parseInt ((now.getTime() - device.stamp.date.getTime())/1000) // report age in second },"properties": {"type":"Properties" ,"id" : devid ,"name": device.name ,"title": devinfo ,'url': devurl },"device": {"type":"Device" ,"class": device.class ,"model": device.type ,"call": device.call ,"img": device.img ,"url": device.url }}); }; }; if (query.jsoncallback === undefined) { response.writeHead(200,{"Content-Type": "application/json"}); response.write(JSON.stringify(jsonresponse)); } else { response.writeHead(200,{"Content-Type": "text/javascript",'Cache-Control':'no-cache'}); var fakescript=query.jsoncallback +'(' + JSON.stringify(jsonresponse) +');'; response.write (fakescript); } response.end(); }; // return a json object with device name and possition DevAdapter.prototype.QueryDevTrack = function(question, request, response) { var gateway = this.controller.gateway; var backend = gateway.backend; var srchost = request.headers.host; var query = question.query; var device = gateway.activeClients [query.devid]; var devurl =util.format ("[%s/geojson.rest,{key:%s,cmd:track,devid:%s,llist:%s}]",srchost, query.key, query.devid, query.llist||10); // DB callback return a json object with device name and possition var DBcallback = function(dbresult) { // start with response header var jsonresponse= // validate syntaxe at http://geojsonlint.com/ {"type":"GeometryCollection" ,"device": {"type":"Device" ,"class":device.class ,"model": device.model ,"call": device.call },"properties": {"type":"Properties" ,'id' : query.devid ,"name": device.name ,'url': devurl ,'img': device.img } ,"geometries":[] }; for (var idx in dbresult) { var pos = dbresult [idx]; var ptsinfo=util.format ("%s<br>Position:[%s,%s] Speed:%s",pos.date,pos.lon.toFixed(4),pos.lat.toFixed(4),pos.sog.toFixed(1)); jsonresponse.geometries.push ( {'type':'Point' ,'coordinates' :[pos.lon, pos.lat] ,'sog' : pos.sog ,'cog' : pos.cog ,'date' : pos.date.getTime() ,'properties' : {'type':'Properties' ,"title": ptsinfo }}); }; if (query.jsoncallback === undefined) { response.writeHead(200,{"Content-Type": "application/json"}); response.write(JSON.stringify(jsonresponse)); } else { response.writeHead(200,{"Content-Type": "text/javascript",'Cache-Control':'no-cache'}); var fakescript=query.jsoncallback +'(' + JSON.stringify(jsonresponse) +');'; response.write (fakescript); } response.end(); }; // end callback // in case client quit since last phone device list update if (device === undefined) { response.writeHead(404,"DEV_QUIT", {"Content-Type": "text/html"}); response.write('DEV_QUIT'); response.end(); return; } // loop on device last postion [warning: async mode] gateway.backend.LookupDev (DBcallback, query.devid, parseInt(query.llist)||10); }; // Do basic REST authentication and dispatch request DevAdapter.prototype.ProcessRestApi = function(question, request, response) { var query=question.query; // check user key [in real world should be more serious than this :) var querykey=parseInt (query['key']); if (querykey !== this.apikey && querykey !== DEMO_API_KEY ){ response.writeHeader(200, {"Content-Type": "text/plain"}); response.write("NOT_AUTH"); response.end(); return; } switch (query.cmd) { case 'list' : // 'http://localhost:4080/geojson.rest?key=123456789&cmd=list&group=all' this.QueryDevList (question,request,response); break; case 'track': // 'http://localhost:4080/geojson.rest?key=123456789&cmd=track&devid=123456789&llist=5' this.QueryDevTrack (question,request,response); break; default: response.writeHeader(200, {"Content-Type": "text/plain"}); response.write("UNK_CMD"); response.end(); } }; // Read a file and sent it as it to the browser DevAdapter.prototype.ProcessFile = function(question, request, response) { // push file back onto response HTTP response handler function ReadFileCB (err, byteread, buffer) { if (err) { response.write(err.toString()); } else { response.write(buffer); } response.end(); } // open file and check if its supported function OpenFileCB (err, fd) { if (err) { response.setHeader('Content-Type','text/html'); response.writeHead(404, err.toString("utf8")); response.write("Hoops:" + err ); response.end(); return; } fs.fstat(fd, function(err,stats){ // get file size and allocate buffer to read it var buffer = new Buffer (stats.size); fs.read (fd, buffer,0,buffer.length,0,ReadFileCB); }); } // Need to be known: nodejs files handling method are asynchronous var fullpath =path.join (this.rootdir, question.pathname); fs.open(fullpath, 'r', OpenFileCB); }; // This routine is called from GpsdController each time a new http request popup DevAdapter.prototype.ProcessData = function(request, response) { var gateway=this.controller.gateway; var question=url.parse(request.url, true, true); this.Debug (4,"Path=%s Query=%s", question.path, JSON.stringify(question.query)); // sign or respond with a specific servername header response.setHeader("Server", "GeoGate-HttpAjax"); // provide a default search to index.html if (question.pathname === '/') { question.pathname = "html/index.html"; response.writeHead(302, {'Location': 'html/index.html'}); response.end(); return; } // check extension we only support html,js,css var extension=path.extname(question.pathname).toLowerCase(); if (extension === '') { extension='.html'; question.pathname = question.pathname + extension; } switch (extension) { case ".rest": case ".json": this.ProcessRestApi (question,request,response); break; case ".html": if (this.cors) response.setHeader("Access-Control-Allow-Origin","*"); response.setHeader("Content-Type", "text/html"); this.ProcessFile (question,request,response); break; case '.js': response.setHeader("Content-Type", "text/javascript"); // add special variable directly inside JavaScript to enable JSON/JsonP profile selection response.write ("var HTTP_AJAX_CONFIG={JSONP: false, GPSD_API_KEY:" + this.apikey +"};"); this.ProcessFile (question,request,response); break; case '.css': response.setHeader("Content-Type", "text/css"); this.ProcessFile (question,request,response); break; case '.png': response.setHeader("Content-Type", "image/png"); this.ProcessFile (question,request,response); break; case '.jpg': case '.jpeg': response.setHeader("Content-Type", "image/jpeg"); this.ProcessFile (question,request,response); break; case '.svg': response.setHeader("Content-Type", "image/svg+xml"); this.ProcessFile (question,request,response); break; case '.woff': response.setHeader("Content-Type", "application/font-woff"); this.ProcessFile (question,request,response); break; case '.ttf': response.setHeader("Content-Type", "application/x-font-ttf"); this.ProcessFile (question,request,response); break; default: response.writeHead(404, "unsupported extension", {'Content-Type': 'text/html'}); response.write("Unsupport file type ext:" + extension ); response.end(); } }; // if started as a main and not as module, then process test. if (process.argv[1] === __filename) { console.log ("### Hoops HtmlBasic-adapter no unit test"); }; module.exports = DevAdapter; // http://openmymind.net/2012/2/3/Node-Require-and-Exports/