AWS X-Ray w/ HAPI - No logs in Console - node.js

Trying out AWS new service X-RAY for my microservice deployment on Kubernetes on EC2. Here are the logs I am seeing:
root#internal-reporting-test-1680758663-3gweh:/usr/src/app# cat AWSXRay.log
2016-12-06 04:36:23.5840 +00:00 [INFO] UDP message sent: {"trace_id":"1-58464048-03fbcdf5c861f95fc5204ec6","id":"105107e6c9df4bfe","start_time":1480998983.553,"name":"localhost","http":{"request":{"method":"GET","user_agent":"curl/7.38.0","client_ip":"127.0.0.1","url":"http://localhost"},"response":{"status":404,"content_length":0}},"service":{"version":"1.0.0"},"error":true,"end_time":1480998983.574}
2016-12-06 04:36:33.2560 +00:00 [INFO] UDP message sent: {"trace_id":"1-58464051-3e3799a566c0933093f51b7a","id":"94d4267609d2ba40","start_time":1480998993.205,"name":"localhost","http":{"request":{"method":"GET","user_agent":"curl/7.38.0","client_ip":"127.0.0.1","url":"http://localhost"},"response":{"status":200,"content_length":0}},"service":{"version":"1.0.0"},"end_time":1480998993.252}
2016-12-06 04:36:35.5810 +00:00 [INFO] UDP message sent: {"trace_id":"1-58464054-5df1e9ec46ef2b26b2d29965","id":"113fd36cddb5de95","start_time":1480998995.552,"name":"localhost","http":{"request":{"method":"GET","user_agent":"curl/7.38.0","client_ip":"127.0.0.1","url":"http://localhost"},"response":{"status":200,"content_length":0}},"service":{"version":"1.0.0"},"end_time":1480998995.579}
2016-12-06 04:36:41.3350 +00:00 [INFO] UDP message sent: {"trace_id":"1-58464059-0dfd2e274ca20499c6bce17f","id":"1dfac818142b2d28","start_time":1480999001.304,"name":"localhost","http":{"request":{"method":"GET","user_agent":"curl/7.38.0","client_ip":"127.0.0.1","url":"http://localhost"},"response":{"status":200,"content_length":0}},"service":{"version":"1.0.0"},"end_time":1480999001.332}
Curl I am using:
# curl localhost/v1/internal/report/users
Supervisor Conf:
[supervisord]
nodaemon=true
[program:xray]
command=/usr/src/app/xray
[program:npm]
command=npm start
Mods to express middleware to use with hapi:
/**
* Express_mw module.
*
* Exposes middleware functions to enable automated data capturing on a web service. To enable on a Node.js/Express application,
* use 'app.use(AWSXRay.express.openSegment())' before defining your routes. After your routes, before any extra error
* handling middleware, use 'app.use(AWSXRay.express.closeSegment())'.
* Use AWSXRay.getSegment() to access the current segment/subsegment.
* Otherwise, for manual mode, this appends the Segment object to the request object as req.segment.
* #module express_mw
*/
var _ = require('underscore');
var CLSUtils = require('../utils').CLSUtils;
var MWUtils = require('./mw_utils');
var Local = require('../segments/attributes/local');
var Segment = require('../segments/segment');
var getCauseType = require('../utils').getCauseTypeFromHttpStatus;
var XRAY_HEADER = 'x-amzn-trace-id';
var NAME = process.env.XRAY_TRACING_NAME;
var DEFAULT_NAME = process.env.XRAY_TRACING_DEFAULT_NAME;
/**
* Use 'app.use(AWSXRay.express.openSegment())' before defining your routes.
* Use AWSXRay.getSegment() to access the current segment/subsegment.
* Otherwise, for manual mode, this appends the Segment object to the request object as req.segment.
* #alias module:express_mw.openSegment
* #returns {function}
*/
module.exports.openSegment = function openSegment() {
if (_.isUndefined(DEFAULT_NAME) && _.isUndefined(MWUtils.defaultName)) {
throw new Error('Default segment name must be set using "process.env.XRAY_TRACING_DEFAULT_NAME"' +
' or "setDefaultName()".');
}
return function open(request, next) {
/*
* Get the true HTTP objects
*/
req = request.raw.req;
res = request.raw.res;
var amznTraceHeader = processHeaders(req);
var segment = new Segment(resolveName(req.headers.host), amznTraceHeader.Root, amznTraceHeader.Parent);
resolveSampling(amznTraceHeader, segment, res);
segment.addAttribute('http', new Local(req));
MWUtils.populate(segment);
res.on('finish', function () {
if (this.statusCode === 429)
segment.addThrottle();
else if (getCauseType(this.statusCode))
segment[getCauseType(this.statusCode)] = true;
segment.http.close(this);
segment.close();
});
if (CLSUtils.isCLSMode()) {
var ns = CLSUtils.getNamespace();
ns.bindEmitter(req);
ns.bindEmitter(res);
ns.run(function () {
CLSUtils.setSegment(segment);
if (next) { next(); }
});
} else {
req.segment = segment;
if (next) { next(); }
}
};
};
/**
* After your routes, before any extra error handling middleware, use 'app.use(AWSXRay.express.closeSegment())'.
* #alias module:express_mw.closeSegment
* #returns {function}
*/
module.exports.closeSegment = function closeSegment() {
return function close(request, next) {
req = request.raw.req;
res = request.raw.res;
var segment = CLSUtils.isCLSMode() ? CLSUtils.getSegment() : segment = req.segment;
if (segment && err) {
segment.counter = 0;
segment.close(err);
} else if (segment) {
segment.close();
}
if (next)
next(err);
};
};
function processHeaders(req) {
var amznTraceHeader = {};
if (req && req.headers && req.headers[XRAY_HEADER]) {
_.each(req.headers[XRAY_HEADER].replace(/ /g,'').split(';'), function (header) {
var pair = header.split('=');
this[pair[0]] = pair[1];
}, amznTraceHeader);
}
return amznTraceHeader;
}
function resolveName(hostName) {
if (NAME)
return NAME;
var regex = new RegExp('(?:[0-9]{1,3}\.){3}[0-9]{1,3}', 'g');
var hostIp = hostName.match(regex) || _.isEmpty(hostName);
return !hostIp ? hostName : (DEFAULT_NAME ? DEFAULT_NAME : MWUtils.defaultName);
}
function resolveSampling(amznTraceHeader, segment, res) {
var isSampled;
if (amznTraceHeader.Sampled === '1')
isSampled = true;
else if (amznTraceHeader.Sampled === '0')
isSampled = false;
isSampled = !_.isUndefined(isSampled) ? isSampled : MWUtils.shouldSample();
if (amznTraceHeader.Sampled === '?')
res.header[XRAY_HEADER] = 'Root=' + amznTraceHeader.Root + '; Sampled=' + (isSampled ? '1' : '0');
if (!isSampled)
segment.notTraced = true;
}
The changes are relatively simple while playing with this service. Just pretty much forcing the raw node HTTP objects to the middleware.
Logs are showing the UDP requests to the xray service are working as expected. Yet in the console I see no results.
Anyone have any ideas?

Related

Correct way to write the Handler for AWS Lambda Serverless.yml

#!/usr/bin/env node
const server = require('./Helpers/server');
const app = {};
/**
* Initiating the Server.
*/
app.init = () => {
server.init();
};
app.init();
module.exports = app;
I have this index.js file which initiates the server.
/**
* dependencies:
* #type {module:http}:
*/
const http = require('http');
const url = require('url');
const config = require('./config');
const StringDecoder = require('string_decoder').StringDecoder;
const router = require('./../Helpers/routing');
const generator = require('./../Services/generator');
const validator = require('./../Helpers/validator');
const printer = require('./../Helpers/printer');
const constants = require('./constants');
const Cookies = require('cookies');
const server = {};
/**
* Core Server logic for parsing and choosing the handlers.
* #param req: the Request object.
* #param res: The Response object.
*/
server.unifiedServer = function (req, res) {
const parsedUrl = url.parse(req.url, true);
const pathName = parsedUrl.pathname;
const path = pathName.replace(/^\/+|\/+$/g, '');
const firstRoute = path.split("/")[0];
const secondPath = path.substr(path.indexOf("/") + 1);
const method = req.method.toLowerCase();
const queryString = parsedUrl.query;
const decoder = new StringDecoder('utf-8');
const header = req.headers['content-type'];
const cookie = new Cookies(req, res);
const chosenHandler = router.getPath(firstRoute);
let postData = "";
let handlerData;
if (header === 'application/octet-stream') {
let data = [];
req.on('data', d => {
data.push(d);
}).on('end', () => {
const buffer = Buffer.concat(data);
handlerData = {
path: secondPath,
method,
queryString,
data: buffer
};
execHandlers(handlerData);
});
} else {
req.on('data', function (data) {
postData += decoder.write(data);
}).on('end', () => {
postData += decoder.end();
postData = generator.generateParsedJSON(postData);
handlerData = {
path: secondPath,
method,
queryString,
postData
};
execHandlers(handlerData);
});
}
/**
* Method to send the response back to the client.
* #param responseData: The response data to be send.
* #param statusCode: The status code that to be send.
* #param cookies: The array containing the cookies.
*/
function sendResponse(responseData, statusCode, cookies) {
responseData = validator.validateUndefined(responseData) ? responseData : {};
statusCode = validator.validateNumber(statusCode) ? statusCode : 500;
responseData = JSON.stringify(responseData);
try {
if (validator.validateUndefined(cookies)) {
for (let i = 0; i < cookies.length; i++) {
const oneCookie = cookies[i];
printer.printHighlightedLog(oneCookie);
cookie.set(oneCookie[constants.COOKIE_KEY], oneCookie[constants.COOKIE_VALUE]);
}
}
res.setHeader(constants.CONTENT_TYPE_TEXT, constants.CONTENT_TYPE_JSON);
res.writeHead(statusCode, constants.HEADERS);
res.end(responseData);
printer.printHighlightedLog("RETURNING: " + responseData + "FOR PATH: " + handlerData.path);
} catch (e) {
printer.printError(e);
}
}
/**
* Method to execute the Handlers.
* #param handlerData: The request object after parsing it.
*/
function execHandlers(handlerData) {
if (handlerData.method === 'options') {
sendResponse({}, 200);
} else {
let promise = chosenHandler(handlerData);
promise.then((responseObject) => {
sendResponse(responseObject[0], responseObject[1], responseObject[2]);
}).catch(err => {
sendResponse(err[0], err[1], err[2]);
});
}
}
};
//TODO: Add the HTTPS Server.
/**
* Method to create the Server.
*/
server.httpServer = http.createServer((req, res) => {
server.unifiedServer(req, res);
});
/**
* Initializing the server.
*/
server.init = () => {
/**
* Method to listen on the port.
*/
server.httpServer.listen(config.port, () => {
console.log("Server Listening on Port ", config.port);
});
};
/**
* exporting the server module.
*/
module.exports = server;
This is my Server.js app.
#serverless.yml
service: serverless-nodejs-app
provider:
name: aws
runtime: nodejs10.x
stage: dev
region: ap-south-1
functions:
app:
handler: index.app # reference the file and exported method
events: # events trigger lambda functions
- http: # this is an API Gateway HTTP event trigger
path: /
method: ANY
cors: true
- http: # all routes get proxied to the Express router
path: /{proxy+}
method: ANY
cors: true
This is my serverless.yml.
{
"errorType": "Runtime.ImportModuleError",
"errorMessage": "Error: Cannot find module 'app'",
"stack": [
"Runtime.ImportModuleError: Error: Cannot find module 'app'",
" at _loadUserApp (/var/runtime/UserFunction.js:100:13)",
" at Object.module.exports.load (/var/runtime/UserFunction.js:140:17)",
" at Object.<anonymous> (/var/runtime/index.js:45:30)",
" at Module._compile (internal/modules/cjs/loader.js:778:30)",
" at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)",
" at Module.load (internal/modules/cjs/loader.js:653:32)",
" at tryModuleLoad (internal/modules/cjs/loader.js:593:12)",
" at Function.Module._load (internal/modules/cjs/loader.js:585:3)",
" at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)",
" at startup (internal/bootstrap/node.js:283:19)"
]
}
This is the error Message.
I want to know what is the correct way of writing the handler for Serverless.yml.
I do not want to use express. How can I invoke my index.js-> app.init() from serverless.yml?
And what am I doing wrong in this?
you can try this, it uses lambda-api instead of express. the syntax looks similar to express. but i haven't used this. have a look at it.
# Functions
functions:
serverless-api-sample:
name: ${self:service}-${self:provider.stage}-serverless-api-sample
handler: handler.router
timeout: 30
events:
- http:
path: 'v1/{proxy+}'
method: any
lambda code
const app = require('lambda-api')({ version: 'v1.0', base: 'v1' })
app.put('/posts/:post_id', (req,res) => {
res.status(200).json({
params: req.params
})
})
Reference:
https://www.jeremydaly.com/build-serverless-api-serverless-aws-lambda-lambda-api/
https://github.com/jeremydaly/lambda-api
I can see that you are trying to create a server inside a lambda using express which is wrong.
If you want to create serverless project,
Create a simple node project using
npm init
Install serverless package
Generate serverless files using
serverless create --template aws-nodejs
Install serverless-offline package
Add its referance in serverless.yml under plugins
create function inside serverless.yml as follows
serverless create -f functionName
Your function is ready.
You can run project offline using command serverless offline
Now, you can hit URI mentioned under functions inside serverless.yml to test your lambda function.

How to get the value from an api call and store it in a variable and updated a dynamodb record

I have a web request which gets some data and returns the response. I am looking to see how to store the response in a variable so it can be used later on in the code.
This is for node js running in a lambda function
/**
* Performs operations for vehicle management actions interfacing primiarly with
* Amazon DynamoDB table.
*
* #class vehicle
*/
/**
* Registers a vehicle to and owner.
* #param {JSON} ticket - authentication ticket
* #param {JSON} vehicle - vehicle object
* #param {createVehicle~callback} cb - The callback that handles the response.
*/
vehicle.prototype.createVehicle = function(ticket, vehicle, cb) {
let vehicle_data = [];
vehicle_data.push(vehicle);
let vin_data = _.pluck(vehicle_data, 'vin');
let vin_number = vin_data[0];
console.log(vin_number);
var options = {
url: 'https://vindecoder.p.mashape.com/decode_vin?' + 'vin=' + vin_number,
headers: {"X-Mashape-Key": "XXXXXXXXXXXXXXXXXXXXXXXXXX","Accept": "application/json"}
};
var data;
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
var result = JSON.parse(body);
var data = result['specification'];
//console.log(data);
}
}
request(options, callback);
var year = data['year'];
var make = data['make'];
var model = data['model'];
var trim_level = data['trim_level'];
var engine = data['engine'];
var body_style = data['style'];
var made_in = data['made_in'];
var steering_type = data['steering_type'];
var anti_brake_system = data['anti_brake_system'];
var fuel_tank = data['tank_size'];
var overall_height = data['overall_height'];
var overall_length = data['overall_length'];
var overall_width = data['overall_width'];
var standard_seating = data['standard_seating'];
var optional_seating = data['optional_seating'];
var highway_mileage = data['highway_mileage'];
var city_mileage = data['city_mileage'];
vehicle.owner_id = ticket['cognito:username'];
// vehicle.vehicle_year = year;
// vehicle.make = make;
// vehicle.model = model;
// vehicle.trim_level = trim_level;
// vehicle.engine = engine;
// vehicle.body_style = style;
// vehicle.made_in = made_in;
// vehicle.steering_type = steering_type;
// vehicle.anti_brake_system = anti_brake_system;
// vehicle.fuel_tank = fuel_tank;
// vehicle.overall_height = overall_height;
// vehicle.overall_length = overall_length;
// vehicle.overall_width = overall_width;
// vehicle.standard_seating = standard_seating;
// vehicle.optional_seating = optional_seating;
// vehicle.highway_mileage = highway_mileage;
// vehicle.city_mileage = city_mileage;
let params = {
TableName: ddbTable,
Item: vehicle
};
let docClient = new AWS.DynamoDB.DocumentClient(dynamoConfig);
docClient.put(params, function(err, data) {
if (err) {
console.log(err);
return cb(err, null);
}
return cb(null, vehicle);
});
};
I expect the response from the api call to be stored in a an object so it can be used to update the dynamodb record
There are various problems in your code. See the comments I left in the code.
Your function is failing because of the asynchronous nature of javascript. Basically, you have a request callback with a result that never gets seen by the rest of your code. Using promises and async/await is one clean way to resolve this. See below:
// use request promise instead of request
// promises will make your life much easier
// https://hackernoon.com/javascript-promises-and-why-async-await-wins-the-battle-4fc9d15d509f
// https://github.com/request/request-promise
const request = require('request-promise')
vehicle.prototype.createVehicle = function(ticket, vehicle, cb) {
// No need to use var anymore with es6, just use let and const
// https://www.sitepoint.com/es6-let-const/
let vehicle_data = [];
vehicle_data.push(vehicle);
let vin_data = _.pluck(vehicle_data, "vin");
let vin_number = vin_data[0];
console.log(vin_number);
const options = {
uri: "https://vindecoder.p.mashape.com/decode_vin?" + "vin=" + vin_number,
headers: {
"X-Mashape-Key": "XXXXXXXXXXXXXXXXXXXXXXXXXX",
Accept: "application/json"
}
};
// Here's the main mistake
// request has a callback function that is asynchronous
// the rest of your code never sees the result because the result doesn't leave the callback
// your code continues to execute without waiting for the result (this is the gist of asynchronity in js)
// function callback(error, response, body) {
// if (!error && response.statusCode == 200) {
// const result = JSON.parse(body);
// const data = result["specification"]; // this variable doesn't leave the scope of this callback
// //console.log(data);
// }
// }
// therefore this is the failure point
// request(options, callback);
// Do this instead
// here I utilize promises and async/ await
// https://medium.com/codebuddies/getting-to-know-asynchronous-javascript-callbacks-promises-and-async-await-17e0673281ee
try {
const result = await request(options);
const data = result["specification"];
} catch (error) {
console.log(error);
}
// Now data is available to be used below in dynamodb
// also, utilize objects, its much cleaner
const carVariables = {
year: data["year"],
make: data["make"],
model: data["model"],
trim_level: data["trim_level"],
engine: data["engine"],
body_style: data["style"],
made_in: data["made_in"],
steering_type: data["steering_type"],
anti_brake_system: data["anti_brake_system"],
fuel_tank: data["tank_size"],
overall_height: data["overall_height"],
overall_length: data["overall_length"],
overall_width: data["overall_width"],
standard_seating: data["standard_seating"],
optional_seating: data["optional_seating"],
highway_mileage: data["highway_mileage"],
city_mileage: data["city_mileage"],
};
vehicle.owner_id = ticket["cognito:username"];
vehicle = { ...vehicle, ...carVariables} // ES6 spread operator does the below code for you:
// one line versus 20. win. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
// vehicle.vehicle_year = year;
// vehicle.make = make;
// vehicle.model = model;
// vehicle.trim_level = trim_level;
// vehicle.engine = engine;
// vehicle.body_style = style;
// vehicle.made_in = made_in;
// vehicle.steering_type = steering_type;
// vehicle.anti_brake_system = anti_brake_system;
// vehicle.fuel_tank = fuel_tank;
// vehicle.overall_height = overall_height;
// vehicle.overall_length = overall_length;
// vehicle.overall_width = overall_width;
// vehicle.standard_seating = standard_seating;
// vehicle.optional_seating = optional_seating;
// vehicle.highway_mileage = highway_mileage;
// vehicle.city_mileage = city_mileage;
let params = {
TableName: ddbTable,
Item: vehicle
};
// This will now probably work. yay!
let docClient = new AWS.DynamoDB.DocumentClient(dynamoConfig);
docClient.put(params, function(err, data) {
if (err) {
console.log(err);
return cb(err, null);
}
return cb(null, vehicle);
});
};
This is untested but I believe it should work. And there may some typos or whatever but the main takeaway here is the use of promises and async/await to wait for and expose the result from the request. See the links and references in the comments for further reading.

nodejs process.exit() prevents function from executing properly

I am writing my first NodeJs script. Below is some code I have set up to test a database connection.
When I include process.exit() at the very end of the script, nothing is logged to the console - however, when I simply remove that line of code, the function logs the query results appropriately (output included also).
I am wondering why the process.exit() at the very end of the code prevents the function from functioning and if I am using the exit method wrong.
Code:
/*
* Runs every minute on an uptime agent
* Checks list of sites to see if they're up
*/
// strict mode (see http://stackoverflow.com/questions/8651415/what-is-strict-mode-and-how-is-it-used for information)
'use strict';
// cuz im lazy
String.prototype.lc = function() { return this.toLowerCase(); }
String.prototype.uc = function() { return this.toUpperCase(); }
/** EXCEPTIONS **/
var DbConnectError = function(m) {
this.name = 'DbConnectError';
this.message = m;
}
var DbConfigError = function(m) {
this.name = 'DbConfigError';
this.message = m;
}
/** DATABSE **/
/*
* change log
* 08/07/2015 Tyler J Barnes
* -- init dev
*/
var db = function() {
// error messages
this._errors = [];
// connection state
this._isConnected = false;
// db configuration
this._config = {
host: '',
user: '',
password: '',
database: ''
};
// reference
this._db = null;
// connection obj ref
this._con = null;
// sql statement
this._sqlStmt = '';
// is prepared statement -- needs data binded
this._isPrepared = false;
// data to bind to prepared stmts
this._sqlData = null;
// query result set
this._result = null;
/*
* initialize
* #param (object) : cofig prop and values
* #return void
*/
this.ini = function(config) {
// make sure config was passed
if(!config || (typeof config).lc() != 'object') {
throw new DbConnectError('Invalid DB Configuration');
}
// check for appropriate properties
// if exist, store value
for(var p in this._config) {
if(!(p in config)) {
this._errors.push('Missing database config: '+p+'\n');
} else {
this._config[p] = config[p];
}
}
// throw any errors before continue
if(this._errors.length > 0) {
var tmp = '';
for(var i = 0; i < this._errors.length; i++) {
tmp+=this._errors[i];
}
throw new DbConfigError(tmp);
}
this._db = require('mysql');
};
// create connection -- returns thread id
this.con = function() {
this._con = this._db.createConnection(this._config);
this._con.connect();
this._isConnected = true;
};
// sets sql statement
this.setSqlStmt = function(str, prepared, bindData) {
this._sqlStmt = str;
if(prepared) {
this._isPrepared = true;
this._sqlData = bindData;
} else {
this._isPrepared = false;
this._sqlData = null;
}
};
// kills connection
this.die = function() {
if(this._isConnected) {
this._con.end();
}
};
}
var c = {
host: 'asdfasdf',
user: 'asdfasdf',
password: 'asdfasdf',
database: 'asdfasdf'
};
var d = new db();
d.ini(c);
d.con();
d._con.query('SELECT * FROM Agents', function(err,rows,fields) {
if(err) console.log(err);
console.log(rows);
});
d._con.end();
// this is what upsets me
process.exit();
Output when process.exit() is removed:
[{agentid:1, host: 'asdfasdfadf'....etc}]
The database query operation is asynchronous. This means that it will only be concluded after the program executes all of the main module, including the exit system call.
Instead, you should only terminate the process when the results are obtained from the database:
var d = new db();
d.ini(c);
d.con();
d._con.query('SELECT * FROM Agents', function(err,rows,fields) {
if(err) console.log(err);
console.log(rows);
process.exit();
});
d._con.end();
This is one of the typical misunderstandings of how JavaScript's asynchronous function calls work. I would advise you to read on the subject. These two questions may have some useful content:
How does Asynchronous Javascript Execution happen? and when not to use return statement?
How does JavaScript handle AJAX responses in the background?

Why is PUT request body undefined?

I'm making the following request to my koajs server:
$.ajax({
type : 'PUT', // this.request.body undefined server side
// type : 'POST', // this.request.body all good server side
url : url,
data : body,
dataType : 'json'
})
But on the server side this.request.body is always undefined.
If I change the request type to POST, it works fine.
Any ideas?
EDIT
I'm using koa-route.
EDIT 2
Just realised I'm using koa-body-parser, which is probably more relevant.
Try using the koa-body parser:
const bodyParser = require('koa-bodyparser')
app.use(bodyParser())
I think koa-router will parse typical request stuff, url params, forms etc. If you want to parse the body of a request that contains a JSON object you need to apply a middleware (as alex alluded to).
Also please check to see if you are putting valid JSON.
Take a look at this Koa-bodyparser:
/**
* #param [Object] opts
* - {String} jsonLimit default '1mb'
* - {String} formLimit default '56kb'
* - {string} encoding default 'utf-8'
*/
return function *bodyParser(next) {
if (this.request.body !== undefined) {
return yield* next;
}
if (this.is('json')) {
this.request.body = yield parse.json(this, jsonOpts);
} else if (this.is('urlencoded')) {
this.request.body = yield parse.form(this, formOpts);
} else {
this.request.body = null;
}
yield* next;
};
there looks to be a 1mb limit on the amount of JSON. then to co-body/lib/json.js
module.exports = function(req, opts){
req = req.req || req;
opts = opts || {};
// defaults
var len = req.headers['content-length'];
if (len) opts.length = ~~len;
opts.encoding = opts.encoding || 'utf8';
opts.limit = opts.limit || '1mb';
return function(done){
raw(req, opts, function(err, str){
if (err) return done(err);
try {
done(null, JSON.parse(str));
} catch (err) {
err.status = 400;
err.body = str;
done(err);
}
});
}
};

SmoothStreaming issues on Chromecast

I'm trying to load a smoothstreaming media into the chromecast. For that I've used the samples provided by google:
<body>
<video id='vid' />
<script type="text/javascript"
src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js">
</script>
<script type="text/javascript"
src="//www.gstatic.com/cast/sdk/libs/mediaplayer/0.3.0/media_player.js">
</script>
<script type="text/javascript">
window.onload = function() {
// If you set ?Debug=true in the URL, such as a different App ID in the
// developer console, include debugging information.
cast.receiver.logger.setLevelValue(cast.receiver.LoggerLevel.DEBUG);
var mediaElement = document.getElementById('vid');
// Create the media manager. This will handle all media messages by default.
window.mediaManager = new cast.receiver.MediaManager(mediaElement);
// Remember the default value for the Receiver onLoad, so this sample can Play
// non-adaptive media as well.
window.defaultOnLoad = mediaManager.onLoad;
mediaManager.onLoad = function (event) {
/proto
// MPEG-DASH
protocol = cast.player.api.CreateDashStreamingProtocol(host);
} else if (url.indexOf('.ism/') >= 0) {
// Smooth Streaming
protocol = cast.player.api.CreateSmoothStreamingProtocol(host);
}
// How to override a method in Host. I know that it's safe to just provide this
// method.
host.onError = function(errorCode) {
console.log("Fatal Error - "+errorCode);
window.player.unload();
};
// If you need cookies, then set withCredentials = true also set any header
// information you need. If you don't need them, there can be some unexpected
// effects by setting this value.
// host.updateSegmentRequestInfo = function(requestInfo) {
// requestInfo.withCredentials = true;
// };
console.log("we have protocol "+ext);
if (protocol !== null) {
console.log("Starting Media Player Library");
window.player = new cast.player.api.Player(host);
window.player.load(protocol, initStart);
}
mediaElement.autoplay = autoplay; // Make sure autoplay get's set
if (url.lastIndexOf('.m3u8') >= 0) {
// HTTP Live Streaming
protocol = cast.player.api.CreateHlsStreamingProtocol(host);
} else if (url.lastIndexOf('.mpd') >= 0) {
// MPEG-DASH
protocol = cast.player.api.CreateDashStreamingProtocol(host);
} else if (url.indexOf('.ism/') >= 0) {
// Smooth Streaming
protocol = cast.player.api.CreateSmoothStreamingProtocol(host);
}
// How to override a method in Host. I know that it's safe to just provide this
// method.
host.onError = function(errorCode) {
console.log("Fatal Error - "+errorCode);
window.player.unload();
};
// If you need cookies, then set withCredentials = true also set any header
// information you need. If you don't need them, there can be some unexpected
// effects by setting this value.
// host.updateSegmentRequestInfo = function(requestInfo) {
// requestInfo.withCredentials = true;
// };
console.log("we have protocol "+ext);
And on the sender:
/**
* global variables
*/
var currentMediaSession = null;
var currentVolume = 0.5;
var progressFlag = 1;
var mediaCurrentTime = 0;
var session = null;
var mediaURLs = [
'http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/',
'http://commondatastorage.googleapis.com/gtv-videos-bucket/ED_1280.mp4',
'http://commondatastorage.googleapis.com/gtv-videos-bucket/tears_of_steel_1080p.mov',
'http://commondatastorage.googleapis.com/gtv-videos-bucket/reel_2012_1280x720.mp4',
'http://commondatastorage.googleapis.com/gtv-videos-bucket/Google%20IO%202011%2045%20Min%20Walk%20Out.mp3'];
var mediaTitles = [
'Big Buck Bunny',
'Elephant Dream',
'Tears of Steel',
'Reel 2012',
'Google I/O 2011 Audio'];
var mediaThumbs = [
'images/bunny.jpg',
'images/ed.jpg',
'images/Tears.jpg',
'images/reel.jpg',
'images/google-io-2011.jpg'];
var currentMediaURL = mediaURLs[0];
/**
* Call initialization
*/
if (!chrome.cast || !chrome.cast.isAvailable) {
setTimeout(initializeCastApi, 1000);
}
/**
* initialization
*/
function initializeCastApi() {
// default app ID to the default media receiver app
// optional: you may change it to your own app ID/receiver
var applicationID = '21176C05';
var sessionRequest = new chrome.cast.SessionRequest(applicationID);
var apiConfig = new chrome.cast.ApiConfig(sessionRequest,
sessionListener,
receiverListener);
chrome.cast.initialize(apiConfig, onInitSuccess, onError);
};
/**
* initialization success callback
*/
function onInitSuccess() {
appendMessage("init success");
}
/**
* initialization error callback
*/
function onError() {
console.log("error");
appendMessage("error");
}
/**
* generic success callback
*/
function onSuccess(message) {
console.log(message);
}
/**
* callback on success for stopping app
*/
function onStopAppSuccess() {
console.log('Session stopped');
appendMessage('Session stopped');
document.getElementById("casticon").src = 'images/cast_icon_idle.png';
}
/**
* session listener during initialization
*/
function sessionListener(e) {
console.log('New session ID: ' + e.sessionId);
appendMessage('New session ID:' + e.sessionId);
session = e;
if (session.media.length != 0) {
appendMessage(
'Found ' + session.media.length + ' existing media sessions.');
onMediaDiscovered('onRequestSessionSuccess_', session.media[0]);
}
session.addMediaListener(
onMediaDiscovered.bind(this, 'addMediaListener'));
session.addUpdateListener(sessionUpdateListener.bind(this));
}
/**
* session update listener
*/
function sessionUpdateListener(isAlive) {
var message = isAlive ? 'Session Updated' : 'Session Removed';
message += ': ' + session.sessionId;
appendMessage(message);
if (!isAlive) {
session = null;
}
};
/**
* receiver listener during initialization
*/
function receiverListener(e) {
if( e === 'available' ) {
console.log("receiver found");
appendMessage("receiver found");
}
else {
console.log("receiver list empty");
appendMessage("receiver list empty");
}
}
/**
* select a media URL
* #param {string} m An index for media URL
*/
function selectMedia(m) {
console.log("media selected" + m);
appendMessage("media selected" + m);
currentMediaURL = mediaURLs[m];
var playpauseresume = document.getElementById("playpauseresume");
document.getElementById('thumb').src = mediaThumbs[m];
}
/**
* launch app and request session
*/
function launchApp() {
console.log("launching app...");
appendMessage("launching app...");
chrome.cast.requestSession(onRequestSessionSuccess, onLaunchError);
}
/**
* callback on success for requestSession call
* #param {Object} e A non-null new session.
*/
function onRequestSessionSuccess(e) {
console.log("session success: " + e.sessionId);
appendMessage("session success: " + e.sessionId);
session = e;
document.getElementById("casticon").src = 'images/cast_icon_active.png';
}
/**
* callback on launch error
*/
function onLaunchError() {
console.log("launch error");
appendMessage("launch error");
}
/**
* stop app/session
*/
function stopApp() {
session.stop(onStopAppSuccess, onError);
}
/**
* load media
* #param {string} i An index for media
*/
function loadMedia(i) {
if (!session) {
console.log("no session");
appendMessage("no session");
return;
}
console.log("loading..." + currentMediaURL);
appendMessage("loading..." + currentMediaURL);
var mediaInfo = new chrome.cast.media.MediaInfo(currentMediaURL);
mediaInfo.contentType = 'application/vnd.ms-sstr+xml';
var request = new chrome.cast.media.LoadRequest(mediaInfo);
request.autoplay = false;
request.currentTime = 0;
var payload = {
"title:" : mediaTitles[i],
"thumb" : mediaThumbs[i]
};
var json = {
"payload" : payload
};
request.customData = json;
session.loadMedia(request,
onMediaDiscovered.bind(this, 'loadMedia'),
onMediaError);
}
/**
* callback on success for loading media
* #param {Object} e A non-null media object
*/
function onMediaDiscovered(how, mediaSession) {
console.log("new media session ID:" + mediaSession.mediaSessionId);
appendMessage("new media session ID:" + mediaSession.mediaSessionId + ' (' + how + ')');
currentMediaSession = mediaSession;
mediaSession.addUpdateListener(onMediaStatusUpdate);
mediaCurrentTime = currentMediaSession.currentTime;
playpauseresume.innerHTML = 'Play';
document.getElementById("casticon").src = 'images/cast_icon_active.png';
}
/**
* callback on media loading error
* #param {Object} e A non-null media object
*/
function onMediaError(e) {
console.log("media error");
appendMessage("media error");
document.getElementById("casticon").src = 'images/cast_icon_warning.png';
}
/**
* callback for media status event
* #param {Object} e A non-null media object
*/
function onMediaStatusUpdate(isAlive) {
if( progressFlag ) {
document.getElementById("progress").value = parseInt(100 * currentMediaSession.currentTime / currentMediaSession.media.duration);
}
document.getElementById("playerstate").innerHTML = currentMediaSession.playerState;
}
/**
* play media
*/
function playMedia() {
if( !currentMediaSession )
return;
var playpauseresume = document.getElementById("playpauseresume");
if( playpauseresume.innerHTML == 'Play' ) {
currentMediaSession.play(null,
mediaCommandSuccessCallback.bind(this,"playing started for " + currentMediaSession.sessionId),
onError);
playpauseresume.innerHTML = 'Pause';
//currentMediaSession.addListener(onMediaStatusUpdate);
appendMessage("play started");
}
else {
if( playpauseresume.innerHTML == 'Pause' ) {
currentMediaSession.pause(null,
mediaCommandSuccessCallback.bind(this,"paused " + currentMediaSession.sessionId),
onError);
playpauseresume.innerHTML = 'Resume';
appendMessage("paused");
}
else {
if( playpauseresume.innerHTML == 'Resume' ) {
currentMediaSession.play(null,
mediaCommandSuccessCallback.bind(this,"resumed " + currentMediaSession.sessionId),
onError);
playpauseresume.innerHTML = 'Pause';
appendMessage("resumed");
}
}
}
}
/**
* stop media
*/
function stopMedia() {
if( !currentMediaSession )
return;
currentMediaSession.stop(null,
mediaCommandSuccessCallback.bind(this,"stopped " + currentMediaSession.sessionId),
onError);
var playpauseresume = document.getElementById("playpauseresume");
playpauseresume.innerHTML = 'Play';
appendMessage("media stopped");
}
/**
* set media volume
* #param {Number} level A number for volume level
* #param {Boolean} mute A true/false for mute/unmute
*/
function setMediaVolume(level, mute) {
if( !currentMediaSession )
return;
var volume = new chrome.cast.Volume();
volume.level = level;
currentVolume = volume.level;
volume.muted = mute;
var request = new chrome.cast.media.VolumeRequest();
request.volume = volume;
currentMediaSession.setVolume(request,
mediaCommandSuccessCallback.bind(this, 'media set-volume done'),
onError);
}
/**
* mute media
* #param {DOM Object} cb A checkbox element
*/
function muteMedia(cb) {
if( cb.checked == true ) {
document.getElementById('muteText').innerHTML = 'Unmute media';
setMediaVolume(currentVolume, true);
appendMessage("media muted");
}
else {
document.getElementById('muteText').innerHTML = 'Mute media';
setMediaVolume(currentVolume, false);
appendMessage("media unmuted");
}
}
/**
* seek media position
* #param {Number} pos A number to indicate percent
*/
function seekMedia(pos) {
console.log('Seeking ' + currentMediaSession.sessionId + ':' +
currentMediaSession.mediaSessionId + ' to ' + pos + "%");
progressFlag = 0;
var request = new chrome.cast.media.SeekRequest();
request.currentTime = pos * currentMediaSession.media.duration / 100;
currentMediaSession.seek(request,
onSeekSuccess.bind(this, 'media seek done'),
onError);
}
/**
* callback on success for media commands
* #param {string} info A message string
* #param {Object} e A non-null media object
*/
function onSeekSuccess(info) {
console.log(info);
appendMessage(info);
setTimeout(function(){progressFlag = 1},1500);
}
/**
* callback on success for media commands
* #param {string} info A message string
* #param {Object} e A non-null media object
*/
function mediaCommandSuccessCallback(info) {
console.log(info);
appendMessage(info);
}
/**
* append message to debug message window
* #param {string} message A message string
*/
function appendMessage(message) {
var dw = document.getElementById("debugmessage");
dw.innerHTML += '\n' + JSON.stringify(message);
};
I am trying media #1 which is .ism - I also tried with the /Manifest - but no luck.
In the debugging console I am getting:
[214.054s] [cast.player.api.Player] load -3
media_player.js:23
[214.060s] [goog.net.XhrIo] Opening Xhr [GET http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest -1]
media_player.js:23
[214.065s] [goog.net.XhrIo] Will abort after 10000ms if incomplete, xhr2 false [GET http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest -1]
media_player.js:23
[214.070s] [goog.net.XhrIo] Sending request [GET http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest -1]
media_player.js:23
[214.088s] [goog.net.XhrIo] Request complete [GET http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/Manifest 200]
media_player.js:23
[214.120s] [cast.player.api.Player] sourceopen
media_player.js:23
[214.142s] [cast.player.api.Player] play
media_player.js:23
[214.291s] [cast.receiver.MediaManager] Load metadata error
cast_receiver.js:19
[214.296s] [cast.receiver.MediaManager] Resetting media element
cast_receiver.js:19
[214.303s] [cast.receiver.MediaManager] Sending error message to 4:client-34217
cast_receiver.js:19
[214.307s] [cast.receiver.IpcChannel] IPC message sent: {"namespace":"urn:x-cast:com.google.cast.media","senderId":"4:client-34217","data":"{\"requestId\":9217017,\"type\":\"LOAD_FAILED\"}"}
cast_receiver.js:19
[214.171s] [cast.player.api.Player] error
media_player.js:23
Fatal Error - 1 index.html:61
[214.176s] [cast.player.api.Player] unload -3
Any idea???
Thanks!
Where did you find that URL? It's incomplete, the right one is http://playready.directtaps.net/smoothstreaming/TTLSS720VC1/To_The_Limit_720.ism/manifest, but it is unlikely to work. Other files have been posted at: Microsoft's SmoothStreaming samples files, that said, I don't believe that Microsoft is providing a CORS header that works for Chromecast.
We do provide some very simple media that you can host yourself. And we provide a very simple server, if you need that to host and provide a CORS header as well.

Resources