3
votes

I've created a lambda function handler in NodeJS with Serverless. When I use the command: serverless offline start, I don't get any error. However when I want to deploy the app with the command serverless deploy, it deploys fine. When I want go to the endpoint, I'm getting an internal server error, this happens only when I require camaro in my application. I need the camaro library to create a template from XML.

I tried using node 6.10 and remove camaro, and install it with node 6.10. This doesn't make a difference.

This is the error I can view in Cloud watch:

module initialization error: Error at Error (native) at Object.Module._extensions..node (module.js:597:18) at Module.load (module.js:487:32) at tryModuleLoad (module.js:446:12) at Function.Module._load (module.js:438:3) at Module.require (module.js:497:17) at require (internal/module.js:20:19) at Object. (/var/task/node_modules/camaro/index.js:4:16) at Module._compile (module.js:570:32) at Object.Module._extensions..js (module.js:579:10)

This is my index.js

const serverless = require('serverless-http');

///
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var index = require('./routes/index');
var users = require('./routes/users');

var app = express();



// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', index);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;
//serverless
module.exports.handler = serverless(app);

This is my serverless.yml

# serverless.yml
service: lambda-dashboardcb

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  region: eu-west-1

functions:
  app:
    handler: index.handler
    events:
      - http: ANY /
      - http: 'ANY {proxy+}'
plugins:
  -  serverless-offline

This is my package.json

package.json
{
  "name": "lambda-dashboardcb",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon ./bin/www"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.17.1",
    "body-parser": "^1.18.2",
    "camaro": "^2.2.2",
    "cookie-parser": "^1.4.3",
    "debug": "~2.6.9",
    "express": "^4.15.5",
    "google-oauth-jwt": "^0.2.0",
    "googleapis": "^23.0.0",
    "morgan": "^1.9.0",
    "pug": "^2.0.0-beta11",
    "serve-favicon": "^2.4.5",
    "serverless-http": "^1.5.2"
  },
  "devDependencies": {
    "nodemon": "^1.13.3",
    "serverless-offline": "^3.16.0"
  }
}

Router

var express = require('express');
var router = express.Router();
var metadata = require('../public/javascripts/metadata-onix.js');
var path = require("path");

var app = express();

/* GET home page. */
router.get('/', function(req, res, next) {
    res.sendFile(path.join(__dirname, '../public', 'index.html'));
});

router.param('isbn', function (req,res,next){
    next();
});

router.get('/metadata:isbn', function(req,res,next){
    /**
        - Get ISBN From URL
        - Get Metadata from ISBN, return as JSON
    **/
    var isbn = req.params.isbn;

    var info = metadata.getMetadataOnix(isbn).then(function(info) {
        res.json({ cover : info });
    });
});

module.exports = router;

Module which uses Camaro

const axios = require('axios');
const transform = require('camaro');

exports.getMetadataOnix = function (id) {
    /**
        - Create template
        - Get request to content CB API.
        - Transform data with template.
        - Return data.
    **/
    const template = {
       template stuff...
    }

    return axios({
        method: 'get',
        url: 'APIURL'+id,
        transformResponse: [function (data) {
            "use strict";
            data = transform(data, template);
            return data;
        }],
        // No .catch here
    }).then(function(resp){
        console.log('Cover from book:'+JSON.stringify(resp.data));
        return resp.data;
    });
}
1
Once you deploy to Lambda, use serverless invoke -f <function-name> -l to output a more useful error. In your case serverless invoke -f app -l. I had a similar issue with node-canvas, the binary was expecting packages in a specific directory, /var/task/lib, and I had them in the wrong place.ckundo

1 Answers

3
votes

camaro is a native module. you will need to install correct prebuilt binary for AWS Lambda.

Even though you switch to Node 6.10 locally but the binary installed on your machine is built for your platform only, which maybe different with the platform on AWS Lambda.

In order to use camaro on AWS Lambda, you should download a copy of prebuilt camaro from Releases and put to this folder path node_modules/camaro/lib/binding/camaro.node.

As of currently, AWS Lambda only supports node 6 on Linux so you're looking for camaro-v2.1.0-node-v48-linux-x64.tar.gz.

Lambda already supports Node 8 now so choose the prebuilt binary accordingly.