├── test
├── mocha.opts
├── fixtures
│ ├── httpauth-module.js
│ ├── object-module.js
│ ├── class-module.js
│ ├── payload-module.js
│ └── multipart-module.js
├── middleware
│ ├── httpauth-test.js
│ ├── payload-parser-test.js
│ └── multipart-parser-test.js
└── apiserver-test.js
├── .travis.yml
├── examples
├── account
│ ├── lib
│ │ └── api_modules
│ │ │ ├── index.js
│ │ │ └── users.js
│ ├── public
│ │ ├── img
│ │ │ ├── glyphicons-halflings.png
│ │ │ └── glyphicons-halflings-white.png
│ │ ├── css
│ │ │ └── main.css
│ │ ├── partials
│ │ │ └── user_row.html
│ │ ├── js
│ │ │ ├── utils.js
│ │ │ ├── main.js
│ │ │ ├── mootools-more-1.4.0.1.js
│ │ │ ├── async.min.js
│ │ │ └── handlebars-1.0.0.beta.6.js
│ │ └── index.html
│ ├── config
│ │ └── routes.json
│ ├── package.json
│ └── server.js
└── instagram
│ ├── lib
│ └── api_modules
│ │ ├── index.js
│ │ └── photos.js
│ ├── public
│ ├── css
│ │ └── main.css
│ ├── img
│ │ ├── glyphicons-halflings.png
│ │ └── glyphicons-halflings-white.png
│ ├── partials
│ │ └── photo.html
│ ├── index.html
│ └── js
│ │ ├── main.js
│ │ ├── utils.js
│ │ ├── mootools-more-1.4.0.1.js
│ │ └── async.min.js
│ ├── config
│ └── routes.json
│ ├── package.json
│ └── server.js
├── .gitignore
├── .npmignore
├── index.js
├── .jshint
├── lib
├── middleware
│ ├── index.js
│ ├── multipart-parser.js
│ ├── payload-parser.js
│ ├── httpauth.js
│ └── file-trasport.js
└── apiserver.js
├── Makefile
├── package.json
├── LICENSE
└── README.md
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --slow 1000
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 0.10
--------------------------------------------------------------------------------
/examples/account/lib/api_modules/index.js:
--------------------------------------------------------------------------------
1 | exports.Users = require("./users")
--------------------------------------------------------------------------------
/examples/instagram/lib/api_modules/index.js:
--------------------------------------------------------------------------------
1 | exports.Photos = require("./photos")
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | examples/instagram/public/uploads/
2 | examples/instagram/tmp/
3 | lib-cov/*
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | docs/
2 | examples/
3 | test/
4 | lib-cov/
5 | coverage.html
6 | README.md
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = process.env.APISERVER_COV
2 | ? require('./lib-cov/apiserver')
3 | : require('./lib/apiserver')
--------------------------------------------------------------------------------
/examples/instagram/public/css/main.css:
--------------------------------------------------------------------------------
1 | .thumbnail p { margin-top: 15px }
2 | input[type="file"] { position: absolute; top: -100px }
--------------------------------------------------------------------------------
/examples/instagram/config/routes.json:
--------------------------------------------------------------------------------
1 | [
2 | ["/photos", "1/photos#index", { "limit": 20, "page": 1 }],
3 | ["/photos/:id", "1/photos#photo"]
4 | ]
--------------------------------------------------------------------------------
/examples/account/public/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kilianc/node-apiserver/HEAD/examples/account/public/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/test/fixtures/httpauth-module.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | amAPrivateApi: function (request, response) {
3 | response.serveJSON({ success: true })
4 | }
5 | }
--------------------------------------------------------------------------------
/examples/instagram/public/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kilianc/node-apiserver/HEAD/examples/instagram/public/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/examples/account/public/css/main.css:
--------------------------------------------------------------------------------
1 | iframe { display: none }
2 | section { padding-top: 5px }
3 | .btn-success { margin-right: 10px }
4 | .btn-info { margin-right: 10px }
--------------------------------------------------------------------------------
/examples/account/public/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kilianc/node-apiserver/HEAD/examples/account/public/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/examples/instagram/public/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kilianc/node-apiserver/HEAD/examples/instagram/public/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/.jshint:
--------------------------------------------------------------------------------
1 | jshint indent:2, boss:true, browser:true, curly:true, debug:true, devel:true, eqeqeq: true, es5:true, expr:true, nodejs:true, newcap:true, passfail:true, triling:true, undef:true, white:true
2 |
--------------------------------------------------------------------------------
/examples/account/config/routes.json:
--------------------------------------------------------------------------------
1 | [
2 | ["/signin", "1/users#signin"],
3 | ["/signup", "1/users#signup"],
4 | ["/list/:limit/:page", "1/users#list", { "limit": 20, "page": 1 }],
5 | ["/users/:id", "1/users#get"]
6 | ]
--------------------------------------------------------------------------------
/lib/middleware/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | JSONTransport: require('json-transport'),
3 | httpAuth: require('./httpauth'),
4 | payloadParser: require('./payload-parser'),
5 | multipartParser: require('./multipart-parser')
6 | }
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | REPORTER = spec
2 |
3 | test:
4 | @NODE_ENV=test ./node_modules/.bin/mocha test/*-test.js test/*/*-test.js $(OPT) --reporter $(REPORTER)
5 |
6 | test-bail:
7 | @rm -rf lib-cov
8 | $(MAKE) test OPT=--bail
9 |
10 | test-cov:
11 | @APISERVER_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html
12 | @open -g coverage.html
13 |
14 | .PHONY: test test-bail test-cov
--------------------------------------------------------------------------------
/examples/account/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apiserver-example",
3 | "version": "0.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "console-trace": "https://github.com/LearnBoost/console-trace/tarball/master",
7 | "apiserver": "0.2.x",
8 | "mongodb": "1.0.x",
9 | "request": "2.9.x",
10 | "connect": "2.3.x",
11 | "colors": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/examples/instagram/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apiserver-example",
3 | "version": "0.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "console-trace": "https://github.com/LearnBoost/console-trace/tarball/master",
7 | "apiserver": "0.2.x",
8 | "mongodb": "1.0.x",
9 | "request": "2.9.x",
10 | "connect": "2.3.x",
11 | "colors": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/examples/account/public/partials/user_row.html:
--------------------------------------------------------------------------------
1 |
2 | | {{_id}} |
3 | {{email}} |
4 | {{password}} |
5 |
6 | signin
7 | info
8 | delete
9 | |
10 |
--------------------------------------------------------------------------------
/test/fixtures/object-module.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | successApi: function (request, response) {
3 | response.serveJSON({ success: true })
4 | },
5 | errorApi: function (request, response) {
6 | throw new Error('Aww')
7 | },
8 | 'get': function (request, response) {
9 | response.serveJSON(request.querystring)
10 | },
11 | 'timeout': {
12 | get: function (request, response) {
13 | // trigger timout
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/test/fixtures/class-module.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 |
3 | }
4 |
5 | module.exports.prototype = {
6 | successApi: function (request, response) {
7 | response.serveJSON({ success: true })
8 | },
9 | errorApi: function (request, response) {
10 | throw new Error('Aww')
11 | },
12 | 'get': function (request, response) {
13 | response.serveJSON(request.querystring)
14 | },
15 | 'timeout': {
16 | get: function (request, response) {
17 | // trigger timout
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/examples/instagram/public/partials/photo.html:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/lib/middleware/multipart-parser.js:
--------------------------------------------------------------------------------
1 | var formidable = require('formidable')
2 |
3 | module.exports = function () {
4 | return function (request, response, next) {
5 | if (!request.method.match(/(PUT|POST|OPTIONS)/)) {
6 | return next()
7 | }
8 | if (request.headers['content-type'].match(/multipart\/form-data/)) {
9 | request.form = new formidable.IncomingForm()
10 | request.form.parse(request, function (err, fields, files) {
11 | request.body = fields
12 | request.files = files
13 | request.parseError = err
14 | })
15 | return next()
16 | }
17 | next()
18 | }
19 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Kilian Ciuffolo (http://nailik.org)",
3 | "name": "apiserver",
4 | "description": "A ready to go, modular, JSON(P) API Server.",
5 | "keywords": [
6 | "api",
7 | "server",
8 | "rest",
9 | "json",
10 | "jsonp"
11 | ],
12 | "version": "0.3.1",
13 | "repository": {
14 | "type": "git",
15 | "url": "http://github.com/kilianc/node-apiserver.git"
16 | },
17 | "main": "./",
18 | "scripts": {
19 | "test": "make test"
20 | },
21 | "engines": {
22 | "node": ">= v0.6.x"
23 | },
24 | "dependencies": {
25 | "apiserver-router": "0.2.x",
26 | "bufferjoiner": "^0.1.3",
27 | "fnchain": "0.1.1",
28 | "formidable": "^1.0.15",
29 | "json-transport": "0.1.x",
30 | "mime": "^1.2.11",
31 | "qs": "^0.4.2"
32 | },
33 | "devDependencies": {
34 | "mocha": "1.0.x",
35 | "should": "0.6.1",
36 | "request": "2.9.x"
37 | }
38 | }
--------------------------------------------------------------------------------
/test/fixtures/payload-module.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'json': function (request, response) {
3 | request.resume()
4 | request.once('end', function () {
5 | response.serveJSON(request.body)
6 | })
7 | },
8 | 'form': function (request, response) {
9 | request.resume()
10 | request.once('end', function () {
11 | response.serveJSON(request.body)
12 | })
13 | },
14 | 'skip': function (request, response) {
15 | request.resume()
16 | request.once('end', function () {
17 | response.serveJSON(request.body)
18 | })
19 | },
20 | 'parse_error': function (request, response) {
21 | request.resume()
22 | request.once('end', function () {
23 | response.serveJSON(request.parseError)
24 | })
25 | },
26 | 'empty': function (request, response) {
27 | request.resume()
28 | request.once('end', function () {
29 | response.serveJSON(request.body)
30 | })
31 | }
32 | }
--------------------------------------------------------------------------------
/test/fixtures/multipart-module.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | module.exports = {
4 | 'multipart': function (request, response) {
5 | request.resume()
6 | var fields = Object.create(null)
7 | request.form.on('field', function (name, value) {
8 | fields[name] = value
9 | })
10 | request.form.on('file', function (name, file) {
11 | fields[name] = fs.readFileSync(file.path, 'utf8')
12 | })
13 | request.form.once('end', function () {
14 | response.serveJSON(fields)
15 | })
16 | },
17 | 'multipartEnd': function (request, response) {
18 | request.resume()
19 | request.form.once('end', function () {
20 | response.serveJSON({ fields: request.body, files: Object.keys(request.files), err: request.parseError })
21 | })
22 | },
23 | 'skip': function (request, response) {
24 | request.resume()
25 | request.once('end', function () {
26 | response.serveJSON(request.form)
27 | })
28 | }
29 | }
--------------------------------------------------------------------------------
/lib/middleware/payload-parser.js:
--------------------------------------------------------------------------------
1 | var querystring = require('querystring'),
2 | BufferJoiner = require('bufferjoiner')
3 |
4 | module.exports = function () {
5 | return function (request, response, next) {
6 | if (!request.method.match(/(PUT|POST|OPTIONS)/)) {
7 | return next()
8 | }
9 | request.rawBody = new BufferJoiner()
10 | request.on('data', function (chunk) {
11 | request.rawBody.add(chunk)
12 | })
13 | request.once('end', function () {
14 | request.rawBody = request.rawBody.join()
15 | if (!request.headers['content-type']) return
16 | if (request.headers['content-type'].match(/application\/x-www-form-urlencoded/)) {
17 | request.body = querystring.parse(request.rawBody.toString('utf8'))
18 | } else if (request.headers['content-type'].match(/application\/json/)) {
19 | try {
20 | request.body = JSON.parse(request.rawBody.toString('utf8'))
21 | } catch(e) {
22 | request.parseError = e
23 | request.body = Object.create(null)
24 | }
25 | }
26 | })
27 | next()
28 | }
29 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Kilian Ciuffolo, me@nailik.org
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/lib/middleware/httpauth.js:
--------------------------------------------------------------------------------
1 | module.exports = function (options) {
2 | options = (options !== null && options !== undefined && options.constructor === Object) ? options : {}
3 | options.realm = options.realm || 'Please signin.'
4 | options.credentials = options.credentials || []
5 |
6 | if (options.encode) {
7 | options.credentials.forEach(function (credential, i) {
8 | options.credentials[i] = 'Basic ' + new Buffer(credential, 'utf8').toString('base64')
9 | })
10 | }
11 |
12 | return function (request, response, next) {
13 | if (request.headers.authorization === undefined) {
14 | onAuthFailed(response, options.realm)
15 | return next(null, true)
16 | }
17 | var allowed = false
18 | for (var i = 0; i < options.credentials.length && allowed === false; i++) {
19 | allowed |= options.credentials[i] === request.headers.authorization
20 | }
21 | if (!allowed) {
22 | onAuthFailed(response, options.realm)
23 | }
24 | next(null, !allowed)
25 | }
26 | }
27 |
28 | function onAuthFailed(response, realm) {
29 | response.serveJSON(null, {
30 | httpStatusCode: 401,
31 | headers: { 'www-authenticate': 'Basic realm=\'' + realm + '\'' }
32 | })
33 | }
--------------------------------------------------------------------------------
/lib/middleware/file-trasport.js:
--------------------------------------------------------------------------------
1 | var url = require('url'),
2 | fs = require('fs'),
3 | mime = require('mime')
4 |
5 | module.exports = function () {
6 | }
7 |
8 | module.exports.prototype.attach = function (request, response) {
9 | response.serveFile = serveFile.bind(this, request, response)
10 | }
11 |
12 | // private
13 |
14 | function serveFile(request, response, path, params) {
15 | params = normalizeParams(params)
16 | fs.stat(path, function (err, stat) {
17 | if (err || !stats.isFile()) {
18 | response.writeHead(404)
19 | response.end()
20 | } else {
21 | fillObject(params.headers, this.standardHeaders)
22 | params.headers['content-legth'] = stat.size
23 | params.headers['content-type'] = mimetypes.getContentTypeFromPath(path)
24 | response.writeHead(params.httpStatusCode, params.httpStatusMessage, params.headers)
25 | fs.createReadStream(path, params).pipe(response)
26 | }
27 | })
28 | }
29 |
30 | function normalizeParams (params) {
31 | params = params || {}
32 | params.headers = params.headers || {}
33 | params.httpStatusCode = isNaN(params.httpStatusCode) ? 200 : params.httpStatusCode
34 | params.httpStatusMessage = params.httpStatusMessage || ''
35 | return params;
36 | }
37 |
38 | function fillObject (targetObj, fillObj) {
39 | Object.keys(fillObj).forEach(function (key) {
40 | if (!targetObj.hasOwnProperty(key)) {
41 | targetObj[key] = fillObj[key]
42 | }
43 | })
44 | }
--------------------------------------------------------------------------------
/examples/instagram/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ApiServer Examples / account
14 |
15 |
16 |
17 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/examples/instagram/public/js/main.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | document.domain = "localhost"
3 |
4 | var API_URL = 'http://localhost:8080'
5 | var templates = {}
6 |
7 | window.addEvent('domready', function () {
8 | configureUpload()
9 | configureFeed()
10 | async.waterfall([
11 | function (callback) {
12 | loadTemplate('partials/photo.html', function (err, template) {
13 | templates.photo = template
14 | callback()
15 | })
16 | },
17 | function (callback) {
18 | get(API_URL + '/photos', callback)
19 | }
20 | ], function (err, photos) {
21 | photos.each(function (photo) {
22 | templates.photo(photo).inject($('photo-feed'))
23 | })
24 | })
25 | })
26 |
27 | function configureUpload() {
28 | $('upload-photo-button').addEvent('click', function (e) {
29 | $('upload-photo-file').click()
30 | })
31 | $('upload-photo-form').addEvent('change:relay(#upload-photo-file)', function (e) {
32 | post(API_URL + '/photos', $('upload-photo-form'), function (err, response) {
33 | templates.photo(response.photo).inject($('photo-feed'), 'top')
34 | })
35 | })
36 | }
37 |
38 | function configureFeed() {
39 | $('photo-feed').addEvent('click:relay(.delete)', function (e, el) {
40 | e.preventDefault()
41 | post(API_URL + '/photos/' + el.get('data-id') + '?action=delete', function (err, response) {
42 | if (response && response.success) {
43 | $(el.get('data-id')).dispose().destroy()
44 | } else {
45 | alert('Something went wrong: ' + (response && response.error))
46 | }
47 | })
48 | }).addEvent('click:relay(.like)', function(e, el){
49 | e.preventDefault()
50 | post(API_URL + '/photos/' + el.get('data-id') + '?action=put', function (err, response) {
51 | if (response && response.success) {
52 | el.getLast().set('text', response.photo.likes)
53 | } else {
54 | alert('Something went wrong: ' + (response && response.error))
55 | }
56 | })
57 | })
58 | }
59 | })()
--------------------------------------------------------------------------------
/test/middleware/httpauth-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should'),
2 | jsonreq = require('request').defaults({ json: true }),
3 | ApiServer = require('../../'),
4 | testModule = require('../fixtures/httpauth-module')
5 |
6 | var apiserver
7 | var defaultPort = 8080
8 | var credentials = ['foo:bar']
9 |
10 | describe('middleware/HTTPAuth', function () {
11 | before(function (done) {
12 | apiserver = new ApiServer()
13 | apiserver.addModule('v1', 'auth', testModule)
14 | apiserver.use(/private/, ApiServer.httpAuth({ credentials: credentials, encode: true }))
15 | apiserver.listen(done)
16 | })
17 | after(function () {
18 | apiserver.close()
19 | })
20 | it('should use a custom realm if provided', function () {
21 | var realm = 'Stay Away.'
22 | var options = { realm: realm }
23 | var httpAuth = ApiServer.httpAuth(options)
24 | options.should.have.property('realm')
25 | options.realm.should.be.eql(realm)
26 | })
27 | it('should encode credentials if required', function () {
28 | credentials.should.be.eql(['Basic Zm9vOmJhcg=='])
29 | })
30 | it('should ask for auth', function (done) {
31 | jsonreq.get('http://localhost:' + defaultPort + '/v1/auth/am_a_private_api', function (err, response, body) {
32 | response.statusCode.should.be.equal(401)
33 | response.headers.should.have.property('www-authenticate')
34 | done(err)
35 | })
36 | })
37 | it('should accept credentials', function (done) {
38 | jsonreq.get({
39 | headers: { 'authorization': 'Basic ' + new Buffer('foo:bar', 'utf8').toString('base64') },
40 | uri: 'http://localhost:' + defaultPort + '/v1/auth/am_a_private_api'
41 | }, function (err, response, body) {
42 | response.statusCode.should.be.equal(200)
43 | body.should.be.eql({ success: true })
44 | done(err)
45 | })
46 | })
47 | it('should refuse credentials', function (done) {
48 | jsonreq.get({
49 | headers: { 'authorization': 'Basic ' + new Buffer('foo:wrong', 'utf8').toString('base64') },
50 | uri: 'http://localhost:' + defaultPort + '/v1/auth/am_a_private_api'
51 | }, function (err, response, body) {
52 | response.statusCode.should.be.equal(401)
53 | response.headers.should.have.property('www-authenticate')
54 | done(err)
55 | })
56 | })
57 | })
--------------------------------------------------------------------------------
/examples/account/server.js:
--------------------------------------------------------------------------------
1 | require('console-trace')({ always: true, right: true, colors: true })
2 |
3 | var ApiServer = require('apiserver'),
4 | ApiModules = require('./lib/api_modules'),
5 | routes = require('./config/routes'),
6 | jsonRequest = require('request'),
7 | colors = require('colors'),
8 | connect = require('connect'),
9 | mongodb = require('mongodb')
10 |
11 | var mongodbServer = new mongodb.Server('localhost', 27017, { auto_reconnect: true, poolSize: 5 })
12 | var mongodbDb = new mongodb.Db('apiserver-example-account', mongodbServer, { native_parser: false })
13 |
14 | mongodbDb.open(function (err, mongodbClient) {
15 | if (err) {
16 | console.error('\n ☹ Cannot connect to mongodb: %s\n'.red, err.message)
17 | return
18 | }
19 | var collections = {
20 | users: new mongodb.Collection(mongodbClient, 'users')
21 | }
22 |
23 | // Static server allocation
24 | connect().use(connect.static(__dirname + '/public')).listen(8000, function () {
25 | console.info('\n ✈ Static server listening at http://localhst:8000'.green)
26 | })
27 |
28 | // ApiServer allocation
29 | var apiServer = new ApiServer({
30 | timeout: 1000,
31 | domain: 'localhost'
32 | })
33 |
34 | // middleware
35 | apiServer.use(/^\/list$/, ApiServer.httpAuth({
36 | realm: 'ApiServer Example',
37 | encode: true,
38 | credentials: ['admin:apiserver']
39 | }))
40 | apiServer.use(/^\/(signin)|(signup)|(delete)$/, ApiServer.payloadParser())
41 | apiServer.use(/^\/(signin)|(signup)|(delete)$/, function (request, response, next) {
42 | // you can write inline middleware
43 | next()
44 | })
45 |
46 | // modules and routing
47 | apiServer.addModule('1', 'users', new ApiModules.Users({ collections: collections }))
48 | apiServer.router.addRoutes(routes)
49 |
50 | // events
51 | apiServer.on('requestStart', function (pathname, time) {
52 | console.info(' ☉ :: start :: %s'.grey, pathname)
53 | }).on('requestEnd', function (pathname, time) {
54 | console.info(' ☺ :: end :: %s in %dms'.grey, pathname, time)
55 | }).on('error', function (pathname, err) {
56 | console.info(' ☹ :: error :: %s (%s)'.red, pathname, err.message)
57 | }).on('timeout', function (pathname) {
58 | console.info(' ☂ :: timedout :: %s'.yellow, pathname)
59 | })
60 |
61 | apiServer.listen(8080, function () {
62 | console.info(' ✈ ApiServer listening at http://localhst:8080\n'.green)
63 | })
64 | })
--------------------------------------------------------------------------------
/examples/account/public/js/utils.js:
--------------------------------------------------------------------------------
1 | function loadTemplate(url, callback) {
2 | new Request({
3 | url: url,
4 | onComplete: function (html) {
5 | var template = Handlebars.compile(html)
6 | var tabular = html.match(//g)
7 | callback(null, function (data) {
8 | return new Element(tabular ? 'tbody' : 'div', { html: template(data || {}) }).getFirst()
9 | })
10 | }
11 | }).get()
12 | }
13 |
14 | function get(url, data, callback) {
15 | var args = Array.prototype.slice.call(arguments)
16 | callback = args.pop()
17 | new Request.JSONP({
18 | url: url,
19 | data: data,
20 | onComplete: function (response) {
21 | callback(null, response)
22 | },
23 | onTimeout: function () {
24 | callback(new Error('Request timed out: ' + url))
25 | }
26 | }).send()
27 | }
28 |
29 | var __requestsMap__ = {}
30 | function post(url, fields, callback) {
31 | var requestId = new Date().getTime()
32 | var args = Array.prototype.slice.call(arguments)
33 | callback = args.pop()
34 |
35 | if ($(fields)) {
36 | var enctype = fields.get('enctype')
37 | if (enctype != 'multipart/form-data') {
38 | enctype = 'x-www-form-urlencoded'
39 | }
40 | var inputs = fields.getElements('input').map(function (input) {
41 | input.clone(true, true).inject(input.erase('id'), 'before')
42 | return input.dispose()
43 | })
44 | } else {
45 | var inputs = []
46 | Object.keys(fields).forEach(function (fieldName) {
47 | inputs.push(new Element('input', {
48 | name: fieldName,
49 | value: data[fieldName]
50 | }))
51 | })
52 | }
53 |
54 | if (url.match(/\?/)) {
55 | url += '&callback=__requestsMap__["' + requestId + '"]'
56 | } else {
57 | url += '?callback=__requestsMap__["' + requestId + '"]'
58 | }
59 |
60 | var form = new Element('form', {
61 | 'enctype': enctype,
62 | 'method': 'post',
63 | 'action': url,
64 | 'target': requestId,
65 | 'style': 'display: none'
66 | }).adopt(inputs).inject(document.body)
67 |
68 | var iframe = new Element('iframe', {
69 | id: requestId,
70 | name: requestId,
71 | styles: { display: 'none' }
72 | }).inject(document.body)
73 |
74 | __requestsMap__[requestId] = function (response) {
75 | callback(null, response)
76 | // cleanup
77 | delete __requestsMap__[requestId]
78 | iframe.dispose().destroy()
79 | form.dispose().destroy()
80 | }
81 |
82 | form.submit()
83 | }
--------------------------------------------------------------------------------
/examples/account/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ApiServer Examples / account
14 |
15 |
16 |
17 |
18 |
21 |
39 |
40 |
43 |
44 |
45 |
46 | | id |
47 | email |
48 | password |
49 | |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/examples/instagram/public/js/utils.js:
--------------------------------------------------------------------------------
1 | function loadTemplate(url, callback) {
2 | new Request({
3 | url: url,
4 | onComplete: function (html) {
5 | var template = Handlebars.compile(html)
6 | var tabular = html.match(/
/g)
7 | callback(null, function (data) {
8 | return new Element(tabular ? 'tbody' : 'div', { html: template(data || {}) }).getFirst()
9 | })
10 | }
11 | }).get()
12 | }
13 |
14 | function get(url, data, callback) {
15 | var args = Array.prototype.slice.call(arguments)
16 | callback = args.pop()
17 | new Request.JSONP({
18 | url: url,
19 | data: data,
20 | onComplete: function (response) {
21 | callback(null, response)
22 | },
23 | onTimeout: function () {
24 | callback(new Error('Request timed out: ' + url))
25 | }
26 | }).send()
27 | }
28 |
29 | var __requestsMap__ = {}
30 | function post(url, fields, callback) {
31 | var requestId = new Date().getTime()
32 | var args = Array.prototype.slice.call(arguments)
33 | callback = args.pop()
34 |
35 | if ($(fields)) {
36 | var enctype = fields.get('enctype')
37 | if (enctype != 'multipart/form-data') {
38 | enctype = 'x-www-form-urlencoded'
39 | }
40 | var inputs = fields.getElements('input').map(function (input) {
41 | input.clone(true, true).inject(input.erase('id'), 'before')
42 | return input.dispose()
43 | })
44 | } else {
45 | var inputs = []
46 | Object.keys(fields).forEach(function (fieldName) {
47 | inputs.push(new Element('input', {
48 | name: fieldName,
49 | value: data[fieldName]
50 | }))
51 | })
52 | }
53 |
54 | if (url.match(/\?/)) {
55 | url += '&callback=__requestsMap__["' + requestId + '"]'
56 | } else {
57 | url += '?callback=__requestsMap__["' + requestId + '"]'
58 | }
59 |
60 | var form = new Element('form', {
61 | 'enctype': enctype,
62 | 'method': 'post',
63 | 'action': url,
64 | 'target': requestId,
65 | 'style': 'display: none'
66 | }).adopt(inputs).inject(document.body)
67 |
68 | var iframe = new Element('iframe', {
69 | id: requestId,
70 | name: requestId,
71 | styles: { display: 'none' }
72 | }).inject(document.body)
73 |
74 | __requestsMap__[requestId] = function (response) {
75 | callback(null, response)
76 | // cleanup
77 | delete __requestsMap__[requestId]
78 | iframe.dispose().destroy()
79 | form.dispose().destroy()
80 | }
81 |
82 | form.submit()
83 | }
--------------------------------------------------------------------------------
/examples/instagram/server.js:
--------------------------------------------------------------------------------
1 | require('console-trace')({ always: true, right: true, colors: true })
2 |
3 | var ApiServer = require('apiserver'),
4 | ApiModules = require('./lib/api_modules'),
5 | routes = require('./config/routes'),
6 | jsonRequest = require('request'),
7 | colors = require('colors'),
8 | connect = require('connect'),
9 | mongodb = require('mongodb')
10 |
11 | var mongodbServer = new mongodb.Server('localhost', 27017, { auto_reconnect: true, poolSize: 5 })
12 | var mongodbDb = new mongodb.Db('apiserver-example-instagram', mongodbServer, { native_parser: false })
13 |
14 | mongodbDb.open(function (err, mongodbClient) {
15 | if (err) {
16 | console.error('\n ☹ Cannot connect to mongodb: %s\n'.red, err.message)
17 | return
18 | }
19 | var collections = {
20 | photos: new mongodb.Collection(mongodbClient, 'photos')
21 | }
22 |
23 | // Static server allocation
24 | connect().use(connect.static(__dirname + '/public')).listen(8000, function () {
25 | console.info('\n ✈ Static server listening at http://localhst:8000'.green)
26 | })
27 |
28 | // ApiServer allocation
29 | var apiServer = new ApiServer({
30 | timeout: 1000,
31 | domain: 'localhost'
32 | })
33 |
34 | // middleware
35 | apiServer.use(/^\/list$/, ApiServer.httpAuth({
36 | realm: 'ApiServer Example',
37 | encode: true,
38 | credentials: ['admin:apiserver']
39 | }))
40 | apiServer.use(ApiServer.payloadParser())
41 | apiServer.use(ApiServer.multipartParser())
42 |
43 | // modules and routing
44 | apiServer.addModule('1', 'photos', new ApiModules.Photos({
45 | collections: collections,
46 | // in my case /tmp is on a different partition
47 | // so I moved the tmp folder in order to avoid files moving.
48 | // cleaning tmp folder is up to you
49 | uploadTempPath: process.cwd() + '/tmp',
50 | uploadPath: process.cwd() + '/public/uploads'
51 | }))
52 | apiServer.router.addRoutes(routes)
53 |
54 | // events
55 | apiServer.on('requestStart', function (pathname, time) {
56 | console.info(' ☉ :: start :: %s'.grey, pathname)
57 | }).on('requestEnd', function (pathname, time) {
58 | console.info(' ☺ :: end :: %s in %dms'.grey, pathname, time)
59 | }).on('error', function (pathname, err) {
60 | console.info(' ☹ :: error :: %s (%s)'.red, pathname, err.message)
61 | }).on('timeout', function (pathname) {
62 | console.info(' ☂ :: timedout :: %s'.yellow, pathname)
63 | })
64 |
65 | apiServer.listen(8080, function () {
66 | console.info(' ✈ ApiServer listening at http://localhst:8080\n'.green)
67 | })
68 | })
--------------------------------------------------------------------------------
/test/middleware/payload-parser-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should'),
2 | Assertion = should.Assertion,
3 | request = require('request'),
4 | jsonreq = require('request').defaults({ json: true }),
5 | ApiServer = require('../../'),
6 | testModule = require('../fixtures/payload-module')
7 |
8 | var apiserver
9 | var defaultPort = 9000
10 | var customPort = 8080
11 |
12 | describe('middleware/PayloadParser', function () {
13 | describe('should skip', function () {
14 | before(function (done) {
15 | apiserver = new ApiServer()
16 | apiserver.addModule('v1', 'test', testModule)
17 | apiserver.use(/./, ApiServer.payloadParser())
18 | apiserver.listen(defaultPort, done)
19 | })
20 | after(function () {
21 | apiserver.close()
22 | })
23 | ;['GET','HEAD','DELETE', 'TRACE',/* 'CONNECT',*/ 'PATCH'].forEach(function (httpMethod) {
24 | it(httpMethod, function (done) {
25 | jsonreq({
26 | method: httpMethod,
27 | uri: 'http://localhost:' + defaultPort + '/v1/test/skip',
28 | json: false,
29 | }, function (err, response, body) {
30 | should.not.exist(body)
31 | done(err)
32 | })
33 | })
34 | })
35 | })
36 | describe('should attach parseError', function () {
37 | before(function (done) {
38 | apiserver = new ApiServer()
39 | apiserver.addModule('v1', 'test', testModule)
40 | apiserver.use(/./, ApiServer.payloadParser())
41 | apiserver.listen(defaultPort, done)
42 | })
43 | after(function () {
44 | apiserver.close()
45 | })
46 | it('on application/json', function (done) {
47 | jsonreq.post({
48 | uri: 'http://localhost:' + defaultPort + '/v1/test/parse_error',
49 | headers: { 'content-type': 'application/json' },
50 | json: false,
51 | body: '=:'
52 | }, function (err, response, body) {
53 | should.exist(body)
54 | done(err)
55 | })
56 | })
57 | })
58 | describe('should handle', function () {
59 | before(function (done) {
60 | apiserver = new ApiServer()
61 | apiserver.addModule('v1', 'test', testModule)
62 | apiserver.use(/./, ApiServer.payloadParser())
63 | apiserver.listen(defaultPort, done)
64 | })
65 | after(function () {
66 | apiserver.close()
67 | })
68 | it('application/x-www-form-urlencoded', function (done) {
69 | jsonreq.post({
70 | uri: 'http://localhost:' + defaultPort + '/v1/test/form',
71 | json: false,
72 | form: { foo: 'bar', bar: 'foo' }
73 | }, function (err, response, body) {
74 | body = JSON.parse(body)
75 | body.should.be.eql({ foo: 'bar', bar: 'foo' })
76 | done(err)
77 | })
78 | })
79 | it('application/json', function (done) {
80 | jsonreq.post({
81 | uri: 'http://localhost:' + defaultPort + '/v1/test/json',
82 | json: { foo: 'bar', bar: 'foo' }
83 | }, function (err, response, body) {
84 | body.should.be.eql({ foo: 'bar', bar: 'foo' })
85 | done(err)
86 | })
87 | })
88 | it('missing content-type', function (done) {
89 | request.post({
90 | uri: 'http://localhost:' + defaultPort + '/v1/test/empty'
91 | }, function (err, response, body) {
92 | should.exist(response)
93 | done(err)
94 | })
95 | })
96 | })
97 | })
--------------------------------------------------------------------------------
/examples/account/public/js/main.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | document.domain = "localhost"
3 |
4 | var API_URL = 'http://localhost:8080'
5 | var templates = {}
6 |
7 | window.addEvent('domready', function () {
8 | configureSignup()
9 | configureTable()
10 | async.waterfall([
11 | function (callback) {
12 | loadTemplate('partials/user_row.html', function (err, template) {
13 | templates.userRow = template
14 | callback()
15 | })
16 | },
17 | function (callback) {
18 | get(API_URL + '/list/0', callback)
19 | }
20 | ], function (err, users) {
21 | users.each(function (user) {
22 | templates.userRow(user).inject($('table-list'))
23 | })
24 | })
25 | })
26 |
27 | function configureSignup() {
28 | $('signup-form').addEvent('submit', function (e) {
29 | e.preventDefault()
30 | post(API_URL + '/signup', this, function (err, response) {
31 | if (response && response.success) {
32 | templates.userRow(response.user).inject($('table-list'))
33 | } else {
34 | alert('Error: ' + response.error)
35 | }
36 | })
37 | })
38 | }
39 |
40 | function configureTable() {
41 | $('table-list').addEvent('click:relay(.btn-danger)', function(e, el){
42 | e.preventDefault()
43 | post(API_URL + '/1/users/delete', { id: el.get('data-id') }, function (err, response) {
44 | if (response && response.success) {
45 | $(el.get('data-id')).dispose().destroy()
46 | } else {
47 | alert('Something went wrong: ' + (response && response.error))
48 | }
49 | })
50 | }).addEvent('click:relay(.btn-success)', function(e, el){
51 | e.preventDefault()
52 | post(API_URL + '/signin', {
53 | email: el.get('data-email'),
54 | password: el.get('data-password')
55 | }, function (err, response) {
56 | if (response && response.success) {
57 | alert('Signin success!')
58 | } else {
59 | alert('Something went wrong: ' + (response && response.error))
60 | }
61 | })
62 | }).addEvent('click:relay(.btn-info)', function(e, el){
63 | e.preventDefault()
64 | get(API_URL + '/users/' + el.get('data-id'), function (err, response) {
65 | if (response && response.success) {
66 | alert(JSON.encode(response.user))
67 | } else {
68 | alert(response && response.error)
69 | }
70 | })
71 | })
72 | }
73 |
74 | function addUserRow(user) {
75 | var buttonSignin = new Element('a', {
76 | 'class': 'btn btn-mini btn-success',
77 | 'text': 'signin',
78 | 'data-email': user.email,
79 | 'data-password': user.password
80 | })
81 | var buttonRemove = new Element('a', {
82 | 'class': 'btn btn-mini btn-danger',
83 | 'text': 'delete',
84 | 'data-id': user._id
85 | })
86 | var buttonInfo = new Element('a', {
87 | 'class': 'btn btn-mini btn-info',
88 | 'text': 'info',
89 | 'data-id': user._id
90 | })
91 | $$('#table-list tbody').adopt(new Element('tr', { id: user._id }).adopt(
92 | new Element('td', { text: user._id }),
93 | new Element('td', { text: user.email }),
94 | new Element('td', { text: user.password }),
95 | new Element('td').adopt(buttonSignin).adopt(buttonInfo).adopt(buttonRemove)
96 | ), 'top')
97 | }
98 | })()
--------------------------------------------------------------------------------
/examples/account/lib/api_modules/users.js:
--------------------------------------------------------------------------------
1 | var ObjectId = require('mongodb').ObjectID
2 |
3 | var Users = module.exports = function (options) {
4 | var self = this
5 | options = (options !== null && options !== undefined && options.constructor === Object) ? options : {}
6 | Object.keys(options).forEach(function (key) {
7 | if (!self.__proto__.hasOwnProperty(key)) {
8 | self[key] = options[key]
9 | }
10 | })
11 | }
12 |
13 | Users.prototype.signup = {
14 | post: function (request, response) {
15 | var self = this
16 | request.resume()
17 | request.once('end', function () {
18 | if (!self._isEmail(request.body.email)) {
19 | response.serveJSON({ success: false, error: 'invalid email ' + request.body.email })
20 | return
21 | }
22 | if (!self._isPassword(request.body.password)) {
23 | response.serveJSON({ success: false, error: 'invalid password' })
24 | return
25 | }
26 | self.collections.users.save({
27 | email: request.body.email,
28 | password: request.body.password
29 | }, function (err, document) {
30 | response.serveJSON({ success: true, user: document })
31 | })
32 | })
33 | }
34 | }
35 |
36 | Users.prototype.signin = {
37 | post: function (request, response) {
38 | var self = this
39 | request.resume()
40 | request.once('end', function () {
41 | self.collections.users.findOne({ email: request.body.email, password: request.body.password }, function (err, document) {
42 | if (err) {
43 | response.serveJSON({ success: false, error: err.message }, { httpStatusCode: 404 })
44 | return
45 | }
46 | if (!document) {
47 | response.serveJSON({ success: false, error: 'user not found' }, { httpStatusCode: 404 })
48 | return
49 | }
50 | response.serveJSON({ success: true }, {
51 | headers: {
52 | 'set-cookie': 'session_id=' + document._id.toHexString()
53 | }
54 | })
55 | })
56 | })
57 | }
58 | }
59 |
60 | Users.prototype.list = {
61 | get: function (request, response) {
62 | var limit = Number(request.querystring.limit)
63 | var page = Number(request.querystring.page)
64 | var stream = this.collections.users.find({}, { limit: limit, skip: (page-1) * limit }).stream()
65 | stream.on('data', function(item) {
66 | response.streamJSON(item)
67 | })
68 | stream.on('close', function() {
69 | response.streamJSON()
70 | })
71 | }
72 | }
73 |
74 | Users.prototype.get = {
75 | get: function (request, response) {
76 | this.collections.users.findOne({ _id: new ObjectId(request.querystring.id) }, function (err, document) {
77 | if (err || !document) {
78 | response.serveJSON({ success: false, error: err.message })
79 | return
80 | }
81 | response.serveJSON({ success: true, user: document })
82 | })
83 | }
84 | }
85 |
86 | Users.prototype.delete = {
87 | post: function (request, response) {
88 | var self = this
89 | request.resume()
90 | request.once('end', function () {
91 | self.collections.users.remove({ _id: new ObjectId(request.body.id) }, { safe: true }, function (err, document) {
92 | if (err) {
93 | response.serveJSON({ success: false, error: err.message })
94 | return
95 | }
96 | response.serveJSON({ success: true, id: request.body.id })
97 | })
98 | })
99 | }
100 | }
101 | Users.prototype.delete.delete = Users.prototype.delete.post
102 |
103 | Users.prototype._isEmail = function (email) {
104 | return /^([a-z0-9_\.\-])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,4})+$/.test(email)
105 | }
106 |
107 | Users.prototype._isPassword = function (email) {
108 | return /^.{6,22}$/.test(email)
109 | }
--------------------------------------------------------------------------------
/examples/instagram/lib/api_modules/photos.js:
--------------------------------------------------------------------------------
1 | var ObjectId = require('mongodb').ObjectID,
2 | fs = require('fs'),
3 | crypto = require('crypto'),
4 | path = require('path')
5 |
6 | var Photos = module.exports = function (options) {
7 | var self = this
8 | options = (options !== null && options !== undefined && options.constructor === Object) ? options : {}
9 | Object.keys(options).forEach(function (key) {
10 | if (!self.__proto__.hasOwnProperty(key)) {
11 | self[key] = options[key]
12 | }
13 | })
14 | }
15 |
16 | Photos.prototype.index = {
17 | get: function (request, response) {
18 | var limit = Number(request.querystring.limit)
19 | var page = Number(request.querystring.page)
20 | this.collections.photos.find({}, { sort: { _id: -1 }, limit: limit, skip: (page-1) * limit }).stream().on('data', function(item) {
21 | response.streamJSON(item)
22 | }).once('close', function() {
23 | response.streamJSON()
24 | })
25 | },
26 | post: function (request, response) {
27 | var self = this
28 | var photo = {}
29 | request.resume()
30 | request.form.uploadDir = self.uploadTempPath
31 | request.form.once('end', function () {
32 | var file = request.files.photo
33 | photo.filename = getUniqueFilename(file.name)
34 | photo.title = request.body.title
35 | photo.caption = request.body.caption
36 | photo.likes = 0
37 | fs.rename(file.path, self.uploadPath + '/' + photo.filename, function (err) {
38 | if (err) {
39 | throw err
40 | }
41 | self.collections.photos.save(photo, function (err, document) {
42 | response.serveJSON({ success: true, photo: photo })
43 | })
44 | })
45 | })
46 | }
47 | }
48 |
49 | Photos.prototype.photo = {
50 | get: function (request, response) {
51 | this.collections.photos.findOne({ _id: new ObjectId(request.querystring.id) }, function (err, document) {
52 | if (!err && !document) {
53 | err = new Error('document ' + request.querystring.id + ' not found')
54 | }
55 | if (err) {
56 | response.serveJSON({ success: false, error: err.msg || err.message })
57 | return
58 | }
59 | response.serveJSON({ success: true, user: document })
60 | })
61 | },
62 | delete: function (request, response) {
63 | var self = this
64 | request.resume()
65 | request.once('end', function () {
66 | self.collections.photos.findAndRemove({ _id: new ObjectId(request.querystring.id) }, [], { safe: true }, function (err, document) {
67 | if (!err && !document) {
68 | err = new Error('document ' + request.querystring.id + ' not found')
69 | }
70 | if (err) {
71 | response.serveJSON({ success: false, error: err.msg || err.message })
72 | return
73 | }
74 | fs.unlink(self.uploadPath + '/' + document.filename, function () {
75 | response.serveJSON({ success: true, id: request.body.id })
76 | })
77 | })
78 | })
79 | },
80 | put: function (request, response) {
81 | var self = this
82 | request.resume()
83 | request.once('end', function () {
84 | self.collections.photos.findAndModify({ _id: new ObjectId(request.querystring.id) }, [], { $inc: { likes: 1 } }, { new: true }, function (err, document) {
85 | if (!err && !document) {
86 | err = new Error('document ' + request.querystring.id + ' not found')
87 | }
88 | if (err) {
89 | response.serveJSON({ success: false, error: err.msg || err.message })
90 | return
91 | }
92 | response.serveJSON({ success: true, photo: document })
93 | })
94 | })
95 | },
96 | // fix for browsers that only allow GET / POST
97 | post: function (request, response) {
98 | if (request.querystring.action === 'delete') {
99 | this.photo.delete.apply(this, arguments)
100 | } else if (request.querystring.action === 'put') {
101 | this.photo.put.apply(this, arguments)
102 | } else {
103 | response.serveJSON()
104 | }
105 | }
106 | }
107 |
108 | function getUniqueFilename(filename) {
109 | var extename = path.extname(filename)
110 | var hash = crypto.createHash('sha1')
111 | hash.update(new Buffer(new Date().getTime().toString()))
112 | return hash.digest('hex') + extename
113 | }
--------------------------------------------------------------------------------
/test/middleware/multipart-parser-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should'),
2 | jsonreq = require('request'),
3 | ApiServer = require('../../'),
4 | testModule = require('../fixtures/multipart-module')
5 |
6 | var apiserver
7 | var defaultPort = 9000
8 | var customPort = 8080
9 | var requestBody = '--AaB03x\r\n'+
10 | 'content-disposition: form-data; name="foo"\r\n'+
11 | '\r\n'+
12 | 'bar\r\n'+
13 | '--AaB03x\r\n'+
14 | 'content-disposition: form-data; name="bar"\r\n'+
15 | '\r\n'+
16 | 'foo\r\n'+
17 | '--AaB03x\r\n'+
18 | 'content-disposition: form-data; name="quote"; filename="quote.txt"\r\n'+
19 | 'Content-Type: text/plain\r\n'+
20 | '\r\n'+
21 | 'no detail is too small\r\n'+
22 | '--AaB03x--'
23 |
24 | describe('middleware/MultipartParser', function () {
25 | describe('should skip', function () {
26 | before(function (done) {
27 | apiserver = new ApiServer()
28 | apiserver.addModule('v1', 'test', testModule)
29 | apiserver.use(/./, ApiServer.multipartParser())
30 | apiserver.listen(defaultPort, done)
31 | })
32 | after(function () {
33 | apiserver.close()
34 | })
35 | ;['GET','HEAD','DELETE', 'TRACE',/* 'CONNECT',*/ 'PATCH'].forEach(function (httpMethod) {
36 | it(httpMethod, function (done) {
37 | jsonreq({
38 | method: httpMethod,
39 | uri: 'http://localhost:' + defaultPort + '/v1/test/skip',
40 | json: false,
41 | }, function (err, response, body) {
42 | should.not.exist(body)
43 | done(err)
44 | })
45 | })
46 | })
47 | it('!multipart/form-data', function (done) {
48 | jsonreq.post({
49 | uri: 'http://localhost:' + defaultPort + '/v1/test/skip',
50 | headers: { 'content-type': 'multipart/x-foo-bar; boundary=AaB03x' },
51 | body:
52 | '--AaB03x\r\n'+
53 | 'content-disposition: form-data; name="foo"\r\n'+
54 | '\r\n'+
55 | 'bar\r\n'+
56 | '--AaB03x\r\n'+
57 | 'content-disposition: form-data; name="bar"\r\n'+
58 | '\r\n'+
59 | 'foo\r\n'+
60 | '--AaB03x\r\n'+
61 | 'content-disposition: form-data; name="pics"; filename="file.txt"\r\n'+
62 | 'Content-Type: text/plain\r\n'+
63 | '\r\n'+
64 | 'no detail is too small\r\n'+
65 | '--AaB03x--'
66 | }, function (err, response, body) {
67 | should.not.exist(body)
68 | done(err)
69 | })
70 | })
71 | })
72 | describe('should handle', function () {
73 | before(function (done) {
74 | apiserver = new ApiServer()
75 | apiserver.addModule('v1', 'test', testModule)
76 | apiserver.use(/./, ApiServer.multipartParser())
77 | apiserver.listen(defaultPort, done)
78 | })
79 | after(function () {
80 | apiserver.close()
81 | })
82 | it('multipart/form-data', function (done) {
83 | jsonreq.post({
84 | uri: 'http://localhost:' + defaultPort + '/v1/test/multipart',
85 | headers: { 'content-type': 'multipart/form-data; boundary=AaB03x' },
86 | body: requestBody
87 | }, function (err, response, body) {
88 | body = JSON.parse(body)
89 | body.should.be.eql({ foo: 'bar', bar: 'foo', quote: 'no detail is too small' })
90 | done(err)
91 | })
92 | })
93 | })
94 | describe('end listener', function () {
95 | before(function (done) {
96 | apiserver = new ApiServer()
97 | apiserver.addModule('v1', 'test', testModule)
98 | apiserver.use(/./, ApiServer.multipartParser())
99 | apiserver.listen(defaultPort, done)
100 | })
101 | after(function () {
102 | apiserver.close()
103 | })
104 | it('should attach files and fields to the request', function (done) {
105 | jsonreq.post({
106 | uri: 'http://localhost:' + defaultPort + '/v1/test/multipart_end',
107 | headers: { 'content-type': 'multipart/form-data; boundary=AaB03x' },
108 | body: requestBody
109 | }, function (err, response, body) {
110 | body = JSON.parse(body)
111 | body.fields.should.be.eql({ foo: 'bar', bar: 'foo' })
112 | body.files.should.be.eql(['quote'])
113 | should.not.exist(body.parseError)
114 | done(err)
115 | })
116 | })
117 | })
118 | })
--------------------------------------------------------------------------------
/examples/account/public/js/mootools-more-1.4.0.1.js:
--------------------------------------------------------------------------------
1 | // MooTools: the javascript framework.
2 | // Load this file's selection again by visiting: http://mootools.net/more/f12d0039101d8e6fba6fbb21b7f9a5de
3 | // Or build this file again with packager using: packager build More/Element.Event.Pseudos More/Request.JSONP
4 | /*
5 | ---
6 | copyrights:
7 | - [MooTools](http://mootools.net)
8 |
9 | licenses:
10 | - [MIT License](http://mootools.net/license.txt)
11 | ...
12 | */
13 | MooTools.More={version:"1.4.0.1",build:"a4244edf2aa97ac8a196fc96082dd35af1abab87"};(function(){Events.Pseudos=function(h,e,f){var d="_monitorEvents:";var c=function(i){return{store:i.store?function(j,k){i.store(d+j,k);
14 | }:function(j,k){(i._monitorEvents||(i._monitorEvents={}))[j]=k;},retrieve:i.retrieve?function(j,k){return i.retrieve(d+j,k);}:function(j,k){if(!i._monitorEvents){return k;
15 | }return i._monitorEvents[j]||k;}};};var g=function(k){if(k.indexOf(":")==-1||!h){return null;}var j=Slick.parse(k).expressions[0][0],p=j.pseudos,i=p.length,o=[];
16 | while(i--){var n=p[i].key,m=h[n];if(m!=null){o.push({event:j.tag,value:p[i].value,pseudo:n,original:k,listener:m});}}return o.length?o:null;};return{addEvent:function(m,p,j){var n=g(m);
17 | if(!n){return e.call(this,m,p,j);}var k=c(this),r=k.retrieve(m,[]),i=n[0].event,l=Array.slice(arguments,2),o=p,q=this;n.each(function(s){var t=s.listener,u=o;
18 | if(t==false){i+=":"+s.pseudo+"("+s.value+")";}else{o=function(){t.call(q,s,u,arguments,o);};}});r.include({type:i,event:p,monitor:o});k.store(m,r);if(m!=i){e.apply(this,[m,p].concat(l));
19 | }return e.apply(this,[i,o].concat(l));},removeEvent:function(m,l){var k=g(m);if(!k){return f.call(this,m,l);}var n=c(this),j=n.retrieve(m);if(!j){return this;
20 | }var i=Array.slice(arguments,2);f.apply(this,[m,l].concat(i));j.each(function(o,p){if(!l||o.event==l){f.apply(this,[o.type,o.monitor].concat(i));}delete j[p];
21 | },this);n.store(m,j);return this;}};};var b={once:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f);},throttle:function(d,e,c){if(!e._throttled){e.apply(this,c);
22 | e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}},pause:function(d,e,c){clearTimeout(e._pause);e._pause=e.delay(d.value||250,this,c);
23 | }};Events.definePseudo=function(c,d){b[c]=d;return this;};Events.lookupPseudo=function(c){return b[c];};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));
24 | ["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype);}});})();(function(){var d={relay:false},c=["once","throttle","pause"],b=c.length;
25 | while(b--){d[c[b]]=Events.lookupPseudo(c[b]);}DOMEvent.definePseudo=function(e,f){d[e]=f;return this;};var a=Element.prototype;[Element,Window,Document].invoke("implement",Events.Pseudos(d,a.addEvent,a.removeEvent));
26 | })();Request.JSONP=new Class({Implements:[Chain,Events,Options],options:{onRequest:function(a){if(this.options.log&&window.console&&console.log){console.log("JSONP retrieving script with url:"+a);
27 | }},onError:function(a){if(this.options.log&&window.console&&console.warn){console.warn("JSONP "+a+" will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs");
28 | }},url:"",callbackKey:"callback",injectScript:document.head,data:"",link:"ignore",timeout:0,log:false},initialize:function(a){this.setOptions(a);},send:function(c){if(!Request.prototype.check.call(this,c)){return this;
29 | }this.running=true;var d=typeOf(c);if(d=="string"||d=="element"){c={data:c};}c=Object.merge(this.options,c||{});var e=c.data;switch(typeOf(e)){case"element":e=document.id(e).toQueryString();
30 | break;case"object":case"hash":e=Object.toQueryString(e);}var b=this.index=Request.JSONP.counter++;var f=c.url+(c.url.test("\\?")?"&":"?")+(c.callbackKey)+"=Request.JSONP.request_map.request_"+b+(e?"&"+e:"");
31 | if(f.length>2083){this.fireEvent("error",f);}Request.JSONP.request_map["request_"+b]=function(){this.success(arguments,b);}.bind(this);var a=this.getScript(f).inject(c.injectScript);
32 | this.fireEvent("request",[f,a]);if(c.timeout){this.timeout.delay(c.timeout,this);}return this;},getScript:function(a){if(!this.script){this.script=new Element("script",{type:"text/javascript",async:true,src:a});
33 | }return this.script;},success:function(b,a){if(!this.running){return;}this.clear().fireEvent("complete",b).fireEvent("success",b).callChain();},cancel:function(){if(this.running){this.clear().fireEvent("cancel");
34 | }return this;},isRunning:function(){return !!this.running;},clear:function(){this.running=false;if(this.script){this.script.destroy();this.script=null;
35 | }return this;},timeout:function(){if(this.running){this.running=false;this.fireEvent("timeout",[this.script.get("src"),this.script]).fireEvent("failure").cancel();
36 | }return this;}});Request.JSONP.counter=0;Request.JSONP.request_map={};
--------------------------------------------------------------------------------
/examples/instagram/public/js/mootools-more-1.4.0.1.js:
--------------------------------------------------------------------------------
1 | // MooTools: the javascript framework.
2 | // Load this file's selection again by visiting: http://mootools.net/more/f12d0039101d8e6fba6fbb21b7f9a5de
3 | // Or build this file again with packager using: packager build More/Element.Event.Pseudos More/Request.JSONP
4 | /*
5 | ---
6 | copyrights:
7 | - [MooTools](http://mootools.net)
8 |
9 | licenses:
10 | - [MIT License](http://mootools.net/license.txt)
11 | ...
12 | */
13 | MooTools.More={version:"1.4.0.1",build:"a4244edf2aa97ac8a196fc96082dd35af1abab87"};(function(){Events.Pseudos=function(h,e,f){var d="_monitorEvents:";var c=function(i){return{store:i.store?function(j,k){i.store(d+j,k);
14 | }:function(j,k){(i._monitorEvents||(i._monitorEvents={}))[j]=k;},retrieve:i.retrieve?function(j,k){return i.retrieve(d+j,k);}:function(j,k){if(!i._monitorEvents){return k;
15 | }return i._monitorEvents[j]||k;}};};var g=function(k){if(k.indexOf(":")==-1||!h){return null;}var j=Slick.parse(k).expressions[0][0],p=j.pseudos,i=p.length,o=[];
16 | while(i--){var n=p[i].key,m=h[n];if(m!=null){o.push({event:j.tag,value:p[i].value,pseudo:n,original:k,listener:m});}}return o.length?o:null;};return{addEvent:function(m,p,j){var n=g(m);
17 | if(!n){return e.call(this,m,p,j);}var k=c(this),r=k.retrieve(m,[]),i=n[0].event,l=Array.slice(arguments,2),o=p,q=this;n.each(function(s){var t=s.listener,u=o;
18 | if(t==false){i+=":"+s.pseudo+"("+s.value+")";}else{o=function(){t.call(q,s,u,arguments,o);};}});r.include({type:i,event:p,monitor:o});k.store(m,r);if(m!=i){e.apply(this,[m,p].concat(l));
19 | }return e.apply(this,[i,o].concat(l));},removeEvent:function(m,l){var k=g(m);if(!k){return f.call(this,m,l);}var n=c(this),j=n.retrieve(m);if(!j){return this;
20 | }var i=Array.slice(arguments,2);f.apply(this,[m,l].concat(i));j.each(function(o,p){if(!l||o.event==l){f.apply(this,[o.type,o.monitor].concat(i));}delete j[p];
21 | },this);n.store(m,j);return this;}};};var b={once:function(e,f,d,c){f.apply(this,d);this.removeEvent(e.event,c).removeEvent(e.original,f);},throttle:function(d,e,c){if(!e._throttled){e.apply(this,c);
22 | e._throttled=setTimeout(function(){e._throttled=false;},d.value||250);}},pause:function(d,e,c){clearTimeout(e._pause);e._pause=e.delay(d.value||250,this,c);
23 | }};Events.definePseudo=function(c,d){b[c]=d;return this;};Events.lookupPseudo=function(c){return b[c];};var a=Events.prototype;Events.implement(Events.Pseudos(b,a.addEvent,a.removeEvent));
24 | ["Request","Fx"].each(function(c){if(this[c]){this[c].implement(Events.prototype);}});})();(function(){var d={relay:false},c=["once","throttle","pause"],b=c.length;
25 | while(b--){d[c[b]]=Events.lookupPseudo(c[b]);}DOMEvent.definePseudo=function(e,f){d[e]=f;return this;};var a=Element.prototype;[Element,Window,Document].invoke("implement",Events.Pseudos(d,a.addEvent,a.removeEvent));
26 | })();Request.JSONP=new Class({Implements:[Chain,Events,Options],options:{onRequest:function(a){if(this.options.log&&window.console&&console.log){console.log("JSONP retrieving script with url:"+a);
27 | }},onError:function(a){if(this.options.log&&window.console&&console.warn){console.warn("JSONP "+a+" will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs");
28 | }},url:"",callbackKey:"callback",injectScript:document.head,data:"",link:"ignore",timeout:0,log:false},initialize:function(a){this.setOptions(a);},send:function(c){if(!Request.prototype.check.call(this,c)){return this;
29 | }this.running=true;var d=typeOf(c);if(d=="string"||d=="element"){c={data:c};}c=Object.merge(this.options,c||{});var e=c.data;switch(typeOf(e)){case"element":e=document.id(e).toQueryString();
30 | break;case"object":case"hash":e=Object.toQueryString(e);}var b=this.index=Request.JSONP.counter++;var f=c.url+(c.url.test("\\?")?"&":"?")+(c.callbackKey)+"=Request.JSONP.request_map.request_"+b+(e?"&"+e:"");
31 | if(f.length>2083){this.fireEvent("error",f);}Request.JSONP.request_map["request_"+b]=function(){this.success(arguments,b);}.bind(this);var a=this.getScript(f).inject(c.injectScript);
32 | this.fireEvent("request",[f,a]);if(c.timeout){this.timeout.delay(c.timeout,this);}return this;},getScript:function(a){if(!this.script){this.script=new Element("script",{type:"text/javascript",async:true,src:a});
33 | }return this.script;},success:function(b,a){if(!this.running){return;}this.clear().fireEvent("complete",b).fireEvent("success",b).callChain();},cancel:function(){if(this.running){this.clear().fireEvent("cancel");
34 | }return this;},isRunning:function(){return !!this.running;},clear:function(){this.running=false;if(this.script){this.script.destroy();this.script=null;
35 | }return this;},timeout:function(){if(this.running){this.running=false;this.fireEvent("timeout",[this.script.get("src"),this.script]).fireEvent("failure").cancel();
36 | }return this;}});Request.JSONP.counter=0;Request.JSONP.request_map={};
--------------------------------------------------------------------------------
/lib/apiserver.js:
--------------------------------------------------------------------------------
1 | var http = require('http'),
2 | parse = require('url').parse,
3 | qs = require('qs'),
4 | util = require('util'),
5 | events = require('events'),
6 | middleware = require('./middleware'),
7 | Router = require('apiserver-router'),
8 | Chain = require('fnchain')
9 |
10 | var ApiServer = module.exports = function (options) {
11 | var self = this
12 | ApiServer.super_.call(this)
13 |
14 | options = (options !== null && options !== undefined && options.constructor === Object) ? options : {}
15 | options.timeout = !options.timeout || options.timeout < 0 ? 15000 : options.timeout
16 | options.standardHeaders = options.standardHeaders || {
17 | 'cache-control': 'max-age=0, no-cache, no-store, must-revalidate',
18 | 'expires': 0,
19 | 'pragma': 'no-cache',
20 | 'x-server': 'ApiServer v' + ApiServer.version + ' raging on nodejs ' + process.version
21 | }
22 | options.port = options.port || 8080
23 | options.server = options.server || http.createServer()
24 | options.router = options.router || new Router(options)
25 |
26 | Object.keys(options).forEach(function (key) {
27 | if (!self.__proto__.hasOwnProperty(key)) {
28 | self[key] = options[key]
29 | }
30 | })
31 |
32 | this.JSONTransport = middleware.JSONTransport(this, options)
33 | this.middlewareList = []
34 | this.activeApiModules = {}
35 | this.server.on('request', onRequest.bind(this))
36 | }
37 |
38 | module.exports.version = require('../package').version
39 |
40 | util.inherits(module.exports, events.EventEmitter)
41 |
42 | ApiServer.prototype.addModule = function (apiVersion, moduleName, module) {
43 | if (this.activeApiModules[apiVersion] === undefined) {
44 | this.activeApiModules[apiVersion] = Object.create(null)
45 | }
46 | this.activeApiModules[apiVersion][moduleName] = module
47 | this.router.update(this.activeApiModules, this.middlewareList)
48 | return this
49 | }
50 |
51 | ApiServer.prototype.use = function () {
52 | var args = Array.prototype.slice.call(arguments)
53 | var middleware = args.pop()
54 | var route = args.pop() || /./
55 | this.middlewareList.push({
56 | route: route,
57 | handle: middleware
58 | })
59 | this.router.update(this.activeApiModules, this.middlewareList)
60 | return this
61 | }
62 |
63 | ApiServer.prototype.listen = function () {
64 | var arguments = Array.prototype.slice.call(arguments)
65 | var port = this.port, hostname, callback
66 | arguments.forEach(function (arg) {
67 | if (typeof arg === 'function') {
68 | callback = arg
69 | return
70 | }
71 | if (typeof arg === 'string' && isNaN(Number(arg))) {
72 | hostname = arg
73 | return
74 | }
75 | if (!isNaN(Number(arg))) {
76 | port = arg
77 | return
78 | }
79 | })
80 | this.server.listen(port, hostname, callback)
81 | }
82 |
83 | ApiServer.prototype.close = function (callback) {
84 | callback && this.server.once('close', callback)
85 | this.server.close()
86 | }
87 |
88 | // export middleware
89 | Object.keys(middleware).forEach(function (middlewareName) {
90 | module.exports[middlewareName] = middleware[middlewareName]
91 | })
92 |
93 | // private
94 | function onRequest(request, response) {
95 | var self = this
96 | var end = response.end
97 | var parsed = parse(request.url, true)
98 |
99 | request.requestedAt = request.at = new Date().getTime()
100 | request.parsedUrl = parse(request.url, true)
101 | request.pathname = request.path = request.parsedUrl.pathname.replace(/\/\/+/g, '/')
102 | request.querystring = request.qs = qs.parse(request.parsedUrl.search.replace(/^\?/, ''))
103 | request.pause()
104 | request.timeout = setInterval(function () {
105 | response.writeHead(408)
106 | end.call(response)
107 | clearInterval(request.timeout)
108 | self.emit('timeout', request.url)
109 | }, this.timeout)
110 |
111 | response.end = function () {
112 | clearInterval(request.timeout)
113 | end.apply(this, arguments)
114 | self.emit('requestEnd', request.url, new Date().getTime() - request.requestedAt)
115 | }
116 |
117 | this.emit('requestStart', request.url, request.requestedAt)
118 | this.JSONTransport(request, response)
119 |
120 | var executionChain = this.router.get(request)
121 |
122 | if (!executionChain) {
123 | return response.serveJSON({
124 | success: false,
125 | reason: request.pathname + ' api not found'
126 | }, {
127 | httpStatusCode: 404,
128 | })
129 | }
130 |
131 | new Chain(executionChain, function (err) {
132 | if (err) {
133 | response.serveJSON({
134 | success: false,
135 | reason: 'something went wrong: ' + err,
136 | stack: err.stack
137 | }, {
138 | httpStatusCode: 500
139 | })
140 | self.emit('error', request.url, err)
141 | }
142 | }).call(request, response)
143 | }
144 |
--------------------------------------------------------------------------------
/examples/account/public/js/async.min.js:
--------------------------------------------------------------------------------
1 | /*global setTimeout: false, console: false */(function(){var a={},b=this,c=b.async;typeof module!="undefined"&&module.exports?module.exports=a:b.async=a,a.noConflict=function(){return b.async=c,a};var d=function(a,b){if(a.forEach)return a.forEach(b);for(var c=0;cd?1:0};d(null,e(b.sort(c),function(a){return a.value}))})},a.auto=function(a,b){b=b||function(){};var c=g(a);if(!c.length)return b(null);var e={},h=[],i=function(a){h.unshift(a)},j=function(a){for(var b=0;bd?1:0};d(null,e(b.sort(c),function(a){return a.value}))})},a.auto=function(a,b){b=b||function(){};var c=g(a);if(!c.length)return b(null);var e={},h=[],i=function(a){h.unshift(a)},j=function(a){for(var b=0;b(request, response, data, options) {
557 | response.writeHead(200, {
558 | 'content-type': 'application/'
559 | })
560 | response.end(.stringify(data))
561 | }
562 | return function (request, response) {
563 | // attach some new method to the response
564 | response.serve = serve.bind(this, request, response)
565 | }
566 | }
567 | ```
568 |
569 | where `` is the formatting method of your data.
570 |
571 | # Router
572 |
573 | Apiserver uses [apiserver-router](https://github.com/kilianc/node-apiserver-router) as default router, a fast routing system with integrated caching. It basically translates your API methods names in routes, doing some convenient case conversion. Also, it supports [(rails like)](http://guides.rubyonrails.org/routing.html) custom routes and implicit route parameters.
574 |
575 | You can change the default behavior passing a custom router as `router` option in the ApiServer [constructor](#class-method-constructor).
576 |
577 | ## Example
578 |
579 | ```js
580 | function UserModule(options) {
581 | this.options = options
582 | }
583 |
584 | // will be translated into /1/random_photo_module/create_album
585 | UserModule.prototype.createAlbum = function (request, response) { ... }
586 |
587 | // will be translated into /1/random_photo_module/upload_photo
588 | // in this case we will overwrite the default path with a custom one
589 | UserModule.prototype.uploadPhoto = {
590 | post: function (request, response) { ... }
591 | }
592 |
593 | // private method, skipped by the router
594 | UserModule.prototype._checkFileExtension = function (request, response) { ... }
595 |
596 | ```
597 |
598 | ```js
599 | apiserver.addModule('1', 'randomPhotoModule', new UserModule())
600 | apiserver.router.addRoute('/photo/:caption', '1/randomPhotoModule#uploadPhoto')
601 | ```
602 |
603 | N.B. the `moduleName` also will be translated
604 |
605 | ## Router Interface
606 |
607 | Your custom router must implement the following interface.
608 |
609 | ```js
610 | function Router () {
611 | ...
612 | }
613 |
614 | Router.prototype.update = function (modules, middlewareList) {
615 | ...
616 | }
617 |
618 | Router.prototype.get = function (request) {
619 | ...
620 | }
621 | ```
622 |
623 | The `get` method must return the the [middleware chain](#middleware-chain) associated with the `request` parameter, and eventually extend the `request` with new data (ex. implicit route parameters).
624 |
625 | # Bundled Middleware
626 |
627 | ## JSONTransport
628 |
629 | [JSONTransport](https://github.com/kilianc/node-json-transport) is the default transport bundled with ApiServer and we can call it the real __killer feature__.
630 |
631 | It provides JSON and JSONP that work with both GET / POST methods.
632 |
633 | ### Examples
634 |
635 | ```js
636 | // decontextualized API method
637 | function (request, response) {
638 | response.serveJSON({ foo: 'bar' })
639 | })
640 | ```
641 |
642 | ```js
643 | // decontextualized API method
644 | function (request, response) {
645 | response.serveJSON(['foo','bar', ...], {
646 | httpStatusCode: 404,
647 | httpStatusMessage: 'maybe.. you\'re lost',
648 | headers: {
649 | 'x-value': 'foo'
650 | }
651 | })
652 | })
653 | ```
654 | ```js
655 | // decontextualized API method
656 | function (request, response) {
657 | var count = 3
658 | var interval = setInterval(function () {
659 | if (count === 0) {
660 | clearInterval(interval)
661 | response.streamJSON()
662 | } else {
663 | count--
664 | response.streamJSON({ foo: 'bar' })
665 | }
666 | }, 200)
667 | })
668 | ```
669 |
670 | yields
671 |
672 | ```js
673 | [
674 | { "foo": "bar" },
675 | { "foo": "bar" },
676 | { "foo": "bar" }
677 | ]
678 | ```
679 |
680 | Read the full docs [here](https://github.com/kilianc/node-json-transport)
681 |
682 | ## payloadParser
683 |
684 | The payload parser automatically __buffers__ the payload and parse it. It only works with __PUT POST OPTIONS__ http methods, because they are the only that can carryout a payload by specs definition.
685 |
686 | Two kinds of payload can be parsed:
687 |
688 | * `application/x-www-form-urlencoded`
689 | * `application/json`
690 |
691 | The following attributes will be attached to the request object:
692 |
693 | * __body__: an object containing the parsed data
694 | * __rawBody__: the raw payload as binary [buffer](http://nodejs.org/api/all.html#all_buffer)
695 | * __parseError__: can be `null` or `Error` in case of parse error
696 |
697 | ### Syntax
698 |
699 | ```js
700 | ApiServer.payloadParser()
701 | ```
702 |
703 | ### Example
704 |
705 | ```js
706 | var apiserver = new ApiServer()
707 | apiserver.use(/1\/my_module\/my_method_api$/, ApiServer.payloadParser())
708 | apiserver.addModule('1', 'myModule', {
709 | 'my_method_api': function (request, response) {
710 | request.resume()
711 | request.once('end', function () {
712 | if (request.parseError) {
713 | // :(
714 | console.error(request.parseError.message)
715 | } else {
716 | request.body // an object containing the parsed data
717 | request.rawBody // contains a binary buffer with your payload
718 | }
719 | })
720 | }
721 | })
722 | ```
723 |
724 | ## multipartParser
725 |
726 | The multipart-parser the attach the payload to a [felixge/node-formidable](https://github.com/felixge/node-formidable) `IncomingForm` object. It only works with __PUT POST OPTIONS__ http methods, because they are the only that can carryout a payload by specs definition.
727 |
728 | Only a `multipart/form-data` payload is parsed and the following attribute will be attached to the request object:
729 |
730 | * __form__ an IncomingForm object, [read how to deal with it](https://github.com/felixge/node-formidable#formidablefile)
731 |
732 | The following attributes will be attached to the request object, *after the IncomingForm end event*:
733 |
734 | * __body__: an object containing the parsed data
735 | * __files__: array of uploaded files [instaceof formidable.File](https://github.com/felixge/node-formidable#formidablefile)
736 | * __parseError__: can be `null` or `Error` in case of parse error
737 |
738 | ### Syntax
739 |
740 | ```js
741 | ApiServer.multipartParser()
742 | ```
743 |
744 | ### Example
745 |
746 | ```js
747 | var apiserver = new ApiServer()
748 | apiserver.use(/1\/my_module\/my_method_api$/, ApiServer.multipartParser())
749 | apiserver.addModule('1', 'myModule', {
750 | 'my_method_api': function (request, response) {
751 | var fields = Object.create(null)
752 | request.resume()
753 | request.form.on('field', function (name, value) {
754 | fields[name] = value
755 | })
756 | request.form.on('file', function (name, file) {
757 | fields[name] = fs.readFileSync(file.path, 'utf8')
758 | })
759 | request.form.once('end', function () {
760 | // do something with your data
761 | })
762 | },
763 | 'my_smarter_api': function (request, response) {
764 | request.resume()
765 | request.form.once('end', function () {
766 | // do something with your data
767 | request.body // fields
768 | request.files // files
769 | request.parseError // error
770 | })
771 | }
772 | })
773 | ```
774 |
775 | ## httpAuth
776 |
777 | The httpauth middleware acts as an auth precondition, checking the `authorization` headers sent with the request.
778 |
779 | If the request doesn't pass the authorization check, httpAuth [will close the response](https://github.com/kilianc/node-apiserver/blob/refactor/lib/middleware/httpauth.js#L34) using the standard JSONTransport:
780 |
781 | ```js
782 | response.serveJSON(null, {
783 | httpStatusCode: 401,
784 | headers: { 'www-authenticate': 'Basic realm=\'' + realm + '\'' }
785 | })
786 | ```
787 |
788 | This will trigger a user/password prompt in your browser
789 |
790 | ### Syntax
791 |
792 | ```js
793 | ApiServer.httpAuth([options])
794 | ```
795 |
796 | ### Options
797 | * __realm__: (`String`) the name of your service, this is used by the browser when it prompts for username and password
798 | * __credentials__ - (`Array`) a list of strings (credentials), if your client is a browser you must use the form _username:password_
799 | * __encode__: (`Boolean`: defaults to false) set to true if your client is a browser (will base64 encode)
800 |
801 | ### Example
802 |
803 | ```js
804 | var apiserver = new ApiServer()
805 | apiserver.use(/1\/admin\/.+/, ApiServer.httpAuth({
806 | realm: 'signin please',
807 | credentials: ['foo:password','bar:password', ...],
808 | encode: true // we suppose that at the other end of the wire we have a browser
809 | }))
810 | apiserver.addModule('1', 'admin', {
811 | 'protectedApi': function (request, response) {
812 | // this will executed only if you provide valid credentials
813 | }
814 | })
815 | ```
816 |
817 | # How to contribute
818 |
819 | __ApiServer__ follows the awesome [Vincent Driessen](http://nvie.com/about/) [branching model](http://nvie.com/posts/a-successful-git-branching-model/).
820 |
821 | * You must add a new feature on his own topic branch
822 | * You must contribute to hot-fixing directly into the master branch (and pull-request to it)
823 |
824 | ApiServer follows (more or less) the [Felix's Node.js Style Guide](http://nodeguide.com/style.html), your contribution must be consistent with this style.
825 |
826 | The test suite is written on top of [visionmedia/mocha](http://visionmedia.github.com/mocha/) and it took hours of hard work. Please use the tests to check if your contribution is breaking some part of the library and add new tests for each new feature.
827 |
828 | ⚡ npm test
829 |
830 | and for your test coverage
831 |
832 | ⚡ make test-cov
833 |
834 | ## License
835 |
836 | _This software is released under the MIT license cited below_.
837 |
838 | Copyright (c) 2010 Kilian Ciuffolo, me@nailik.org. All Rights Reserved.
839 |
840 | Permission is hereby granted, free of charge, to any person
841 | obtaining a copy of this software and associated documentation
842 | files (the 'Software'), to deal in the Software without
843 | restriction, including without limitation the rights to use,
844 | copy, modify, merge, publish, distribute, sublicense, and/or sell
845 | copies of the Software, and to permit persons to whom the
846 | Software is furnished to do so, subject to the following
847 | conditions:
848 |
849 | The above copyright notice and this permission notice shall be
850 | included in all copies or substantial portions of the Software.
851 |
852 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
853 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
854 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
855 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
856 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
857 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
858 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
859 | OTHER DEALINGS IN THE SOFTWARE.
860 |
--------------------------------------------------------------------------------
/examples/account/public/js/handlebars-1.0.0.beta.6.js:
--------------------------------------------------------------------------------
1 | // lib/handlebars/base.js
2 | var Handlebars = {};
3 |
4 | Handlebars.VERSION = "1.0.beta.6";
5 |
6 | Handlebars.helpers = {};
7 | Handlebars.partials = {};
8 |
9 | Handlebars.registerHelper = function(name, fn, inverse) {
10 | if(inverse) { fn.not = inverse; }
11 | this.helpers[name] = fn;
12 | };
13 |
14 | Handlebars.registerPartial = function(name, str) {
15 | this.partials[name] = str;
16 | };
17 |
18 | Handlebars.registerHelper('helperMissing', function(arg) {
19 | if(arguments.length === 2) {
20 | return undefined;
21 | } else {
22 | throw new Error("Could not find property '" + arg + "'");
23 | }
24 | });
25 |
26 | var toString = Object.prototype.toString, functionType = "[object Function]";
27 |
28 | Handlebars.registerHelper('blockHelperMissing', function(context, options) {
29 | var inverse = options.inverse || function() {}, fn = options.fn;
30 |
31 |
32 | var ret = "";
33 | var type = toString.call(context);
34 |
35 | if(type === functionType) { context = context.call(this); }
36 |
37 | if(context === true) {
38 | return fn(this);
39 | } else if(context === false || context == null) {
40 | return inverse(this);
41 | } else if(type === "[object Array]") {
42 | if(context.length > 0) {
43 | for(var i=0, j=context.length; i 0) {
60 | for(var i=0, j=context.length; i 2) {
235 | expected.push("'" + this.terminals_[p] + "'");
236 | }
237 | var errStr = "";
238 | if (this.lexer.showPosition) {
239 | errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + this.terminals_[symbol] + "'";
240 | } else {
241 | errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
242 | }
243 | this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
244 | }
245 | }
246 | if (action[0] instanceof Array && action.length > 1) {
247 | throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
248 | }
249 | switch (action[0]) {
250 | case 1:
251 | stack.push(symbol);
252 | vstack.push(this.lexer.yytext);
253 | lstack.push(this.lexer.yylloc);
254 | stack.push(action[1]);
255 | symbol = null;
256 | if (!preErrorSymbol) {
257 | yyleng = this.lexer.yyleng;
258 | yytext = this.lexer.yytext;
259 | yylineno = this.lexer.yylineno;
260 | yyloc = this.lexer.yylloc;
261 | if (recovering > 0)
262 | recovering--;
263 | } else {
264 | symbol = preErrorSymbol;
265 | preErrorSymbol = null;
266 | }
267 | break;
268 | case 2:
269 | len = this.productions_[action[1]][1];
270 | yyval.$ = vstack[vstack.length - len];
271 | yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
272 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
273 | if (typeof r !== "undefined") {
274 | return r;
275 | }
276 | if (len) {
277 | stack = stack.slice(0, -1 * len * 2);
278 | vstack = vstack.slice(0, -1 * len);
279 | lstack = lstack.slice(0, -1 * len);
280 | }
281 | stack.push(this.productions_[action[1]][0]);
282 | vstack.push(yyval.$);
283 | lstack.push(yyval._$);
284 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
285 | stack.push(newState);
286 | break;
287 | case 3:
288 | return true;
289 | }
290 | }
291 | return true;
292 | }
293 | };/* Jison generated lexer */
294 | var lexer = (function(){
295 |
296 | var lexer = ({EOF:1,
297 | parseError:function parseError(str, hash) {
298 | if (this.yy.parseError) {
299 | this.yy.parseError(str, hash);
300 | } else {
301 | throw new Error(str);
302 | }
303 | },
304 | setInput:function (input) {
305 | this._input = input;
306 | this._more = this._less = this.done = false;
307 | this.yylineno = this.yyleng = 0;
308 | this.yytext = this.matched = this.match = '';
309 | this.conditionStack = ['INITIAL'];
310 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
311 | return this;
312 | },
313 | input:function () {
314 | var ch = this._input[0];
315 | this.yytext+=ch;
316 | this.yyleng++;
317 | this.match+=ch;
318 | this.matched+=ch;
319 | var lines = ch.match(/\n/);
320 | if (lines) this.yylineno++;
321 | this._input = this._input.slice(1);
322 | return ch;
323 | },
324 | unput:function (ch) {
325 | this._input = ch + this._input;
326 | return this;
327 | },
328 | more:function () {
329 | this._more = true;
330 | return this;
331 | },
332 | pastInput:function () {
333 | var past = this.matched.substr(0, this.matched.length - this.match.length);
334 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
335 | },
336 | upcomingInput:function () {
337 | var next = this.match;
338 | if (next.length < 20) {
339 | next += this._input.substr(0, 20-next.length);
340 | }
341 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
342 | },
343 | showPosition:function () {
344 | var pre = this.pastInput();
345 | var c = new Array(pre.length + 1).join("-");
346 | return pre + this.upcomingInput() + "\n" + c+"^";
347 | },
348 | next:function () {
349 | if (this.done) {
350 | return this.EOF;
351 | }
352 | if (!this._input) this.done = true;
353 |
354 | var token,
355 | match,
356 | col,
357 | lines;
358 | if (!this._more) {
359 | this.yytext = '';
360 | this.match = '';
361 | }
362 | var rules = this._currentRules();
363 | for (var i=0;i < rules.length; i++) {
364 | match = this._input.match(this.rules[rules[i]]);
365 | if (match) {
366 | lines = match[0].match(/\n.*/g);
367 | if (lines) this.yylineno += lines.length;
368 | this.yylloc = {first_line: this.yylloc.last_line,
369 | last_line: this.yylineno+1,
370 | first_column: this.yylloc.last_column,
371 | last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
372 | this.yytext += match[0];
373 | this.match += match[0];
374 | this.matches = match;
375 | this.yyleng = this.yytext.length;
376 | this._more = false;
377 | this._input = this._input.slice(match[0].length);
378 | this.matched += match[0];
379 | token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
380 | if (token) return token;
381 | else return;
382 | }
383 | }
384 | if (this._input === "") {
385 | return this.EOF;
386 | } else {
387 | this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
388 | {text: "", token: null, line: this.yylineno});
389 | }
390 | },
391 | lex:function lex() {
392 | var r = this.next();
393 | if (typeof r !== 'undefined') {
394 | return r;
395 | } else {
396 | return this.lex();
397 | }
398 | },
399 | begin:function begin(condition) {
400 | this.conditionStack.push(condition);
401 | },
402 | popState:function popState() {
403 | return this.conditionStack.pop();
404 | },
405 | _currentRules:function _currentRules() {
406 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
407 | },
408 | topState:function () {
409 | return this.conditionStack[this.conditionStack.length-2];
410 | },
411 | pushState:function begin(condition) {
412 | this.begin(condition);
413 | }});
414 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
415 |
416 | var YYSTATE=YY_START
417 | switch($avoiding_name_collisions) {
418 | case 0:
419 | if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
420 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
421 | if(yy_.yytext) return 14;
422 |
423 | break;
424 | case 1: return 14;
425 | break;
426 | case 2: this.popState(); return 14;
427 | break;
428 | case 3: return 24;
429 | break;
430 | case 4: return 16;
431 | break;
432 | case 5: return 20;
433 | break;
434 | case 6: return 19;
435 | break;
436 | case 7: return 19;
437 | break;
438 | case 8: return 23;
439 | break;
440 | case 9: return 23;
441 | break;
442 | case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
443 | break;
444 | case 11: return 22;
445 | break;
446 | case 12: return 34;
447 | break;
448 | case 13: return 33;
449 | break;
450 | case 14: return 33;
451 | break;
452 | case 15: return 36;
453 | break;
454 | case 16: /*ignore whitespace*/
455 | break;
456 | case 17: this.popState(); return 18;
457 | break;
458 | case 18: this.popState(); return 18;
459 | break;
460 | case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28;
461 | break;
462 | case 20: return 30;
463 | break;
464 | case 21: return 30;
465 | break;
466 | case 22: return 29;
467 | break;
468 | case 23: return 33;
469 | break;
470 | case 24: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 33;
471 | break;
472 | case 25: return 'INVALID';
473 | break;
474 | case 26: return 5;
475 | break;
476 | }
477 | };
478 | lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^[^\x00]{2,}?(?=(\{\{))/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[\/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s\/.])/,/^\[[^\]]*\]/,/^./,/^$/];
479 | lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,26],"inclusive":true}};return lexer;})()
480 | parser.lexer = lexer;
481 | return parser;
482 | })();
483 | if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
484 | exports.parser = handlebars;
485 | exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); }
486 | exports.main = function commonjsMain(args) {
487 | if (!args[1])
488 | throw new Error('Usage: '+args[0]+' FILE');
489 | if (typeof process !== 'undefined') {
490 | var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8");
491 | } else {
492 | var cwd = require("file").path(require("file").cwd());
493 | var source = cwd.join(args[1]).read({charset: "utf-8"});
494 | }
495 | return exports.parser.parse(source);
496 | }
497 | if (typeof module !== 'undefined' && require.main === module) {
498 | exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args);
499 | }
500 | };
501 | ;
502 | // lib/handlebars/compiler/base.js
503 | Handlebars.Parser = handlebars;
504 |
505 | Handlebars.parse = function(string) {
506 | Handlebars.Parser.yy = Handlebars.AST;
507 | return Handlebars.Parser.parse(string);
508 | };
509 |
510 | Handlebars.print = function(ast) {
511 | return new Handlebars.PrintVisitor().accept(ast);
512 | };
513 |
514 | Handlebars.logger = {
515 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
516 |
517 | // override in the host environment
518 | log: function(level, str) {}
519 | };
520 |
521 | Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
522 | ;
523 | // lib/handlebars/compiler/ast.js
524 | (function() {
525 |
526 | Handlebars.AST = {};
527 |
528 | Handlebars.AST.ProgramNode = function(statements, inverse) {
529 | this.type = "program";
530 | this.statements = statements;
531 | if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
532 | };
533 |
534 | Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
535 | this.type = "mustache";
536 | this.id = params[0];
537 | this.params = params.slice(1);
538 | this.hash = hash;
539 | this.escaped = !unescaped;
540 | };
541 |
542 | Handlebars.AST.PartialNode = function(id, context) {
543 | this.type = "partial";
544 |
545 | // TODO: disallow complex IDs
546 |
547 | this.id = id;
548 | this.context = context;
549 | };
550 |
551 | var verifyMatch = function(open, close) {
552 | if(open.original !== close.original) {
553 | throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
554 | }
555 | };
556 |
557 | Handlebars.AST.BlockNode = function(mustache, program, close) {
558 | verifyMatch(mustache.id, close);
559 | this.type = "block";
560 | this.mustache = mustache;
561 | this.program = program;
562 | };
563 |
564 | Handlebars.AST.InverseNode = function(mustache, program, close) {
565 | verifyMatch(mustache.id, close);
566 | this.type = "inverse";
567 | this.mustache = mustache;
568 | this.program = program;
569 | };
570 |
571 | Handlebars.AST.ContentNode = function(string) {
572 | this.type = "content";
573 | this.string = string;
574 | };
575 |
576 | Handlebars.AST.HashNode = function(pairs) {
577 | this.type = "hash";
578 | this.pairs = pairs;
579 | };
580 |
581 | Handlebars.AST.IdNode = function(parts) {
582 | this.type = "ID";
583 | this.original = parts.join(".");
584 |
585 | var dig = [], depth = 0;
586 |
587 | for(var i=0,l=parts.length; i": ">",
646 | '"': """,
647 | "'": "'",
648 | "`": "`"
649 | };
650 |
651 | var badChars = /&(?!\w+;)|[<>"'`]/g;
652 | var possible = /[&<>"'`]/;
653 |
654 | var escapeChar = function(chr) {
655 | return escape[chr] || "&";
656 | };
657 |
658 | Handlebars.Utils = {
659 | escapeExpression: function(string) {
660 | // don't escape SafeStrings, since they're already safe
661 | if (string instanceof Handlebars.SafeString) {
662 | return string.toString();
663 | } else if (string == null || string === false) {
664 | return "";
665 | }
666 |
667 | if(!possible.test(string)) { return string; }
668 | return string.replace(badChars, escapeChar);
669 | },
670 |
671 | isEmpty: function(value) {
672 | if (typeof value === "undefined") {
673 | return true;
674 | } else if (value === null) {
675 | return true;
676 | } else if (value === false) {
677 | return true;
678 | } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
679 | return true;
680 | } else {
681 | return false;
682 | }
683 | }
684 | };
685 | })();;
686 | // lib/handlebars/compiler/compiler.js
687 | Handlebars.Compiler = function() {};
688 | Handlebars.JavaScriptCompiler = function() {};
689 |
690 | (function(Compiler, JavaScriptCompiler) {
691 | Compiler.OPCODE_MAP = {
692 | appendContent: 1,
693 | getContext: 2,
694 | lookupWithHelpers: 3,
695 | lookup: 4,
696 | append: 5,
697 | invokeMustache: 6,
698 | appendEscaped: 7,
699 | pushString: 8,
700 | truthyOrFallback: 9,
701 | functionOrFallback: 10,
702 | invokeProgram: 11,
703 | invokePartial: 12,
704 | push: 13,
705 | assignToHash: 15,
706 | pushStringParam: 16
707 | };
708 |
709 | Compiler.MULTI_PARAM_OPCODES = {
710 | appendContent: 1,
711 | getContext: 1,
712 | lookupWithHelpers: 2,
713 | lookup: 1,
714 | invokeMustache: 3,
715 | pushString: 1,
716 | truthyOrFallback: 1,
717 | functionOrFallback: 1,
718 | invokeProgram: 3,
719 | invokePartial: 1,
720 | push: 1,
721 | assignToHash: 1,
722 | pushStringParam: 1
723 | };
724 |
725 | Compiler.DISASSEMBLE_MAP = {};
726 |
727 | for(var prop in Compiler.OPCODE_MAP) {
728 | var value = Compiler.OPCODE_MAP[prop];
729 | Compiler.DISASSEMBLE_MAP[value] = prop;
730 | }
731 |
732 | Compiler.multiParamSize = function(code) {
733 | return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
734 | };
735 |
736 | Compiler.prototype = {
737 | compiler: Compiler,
738 |
739 | disassemble: function() {
740 | var opcodes = this.opcodes, opcode, nextCode;
741 | var out = [], str, name, value;
742 |
743 | for(var i=0, l=opcodes.length; i 0) {
1128 | this.source[1] = this.source[1] + ", " + locals.join(", ");
1129 | }
1130 |
1131 | // Generate minimizer alias mappings
1132 | if (!this.isChild) {
1133 | var aliases = []
1134 | for (var alias in this.context.aliases) {
1135 | this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
1136 | }
1137 | }
1138 |
1139 | if (this.source[1]) {
1140 | this.source[1] = "var " + this.source[1].substring(2) + ";";
1141 | }
1142 |
1143 | // Merge children
1144 | if (!this.isChild) {
1145 | this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
1146 | }
1147 |
1148 | if (!this.environment.isSimple) {
1149 | this.source.push("return buffer;");
1150 | }
1151 |
1152 | var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
1153 |
1154 | for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
1406 | return "stack" + this.stackSlot;
1407 | },
1408 |
1409 | popStack: function() {
1410 | return "stack" + this.stackSlot--;
1411 | },
1412 |
1413 | topStack: function() {
1414 | return "stack" + this.stackSlot;
1415 | },
1416 |
1417 | quotedString: function(str) {
1418 | return '"' + str
1419 | .replace(/\\/g, '\\\\')
1420 | .replace(/"/g, '\\"')
1421 | .replace(/\n/g, '\\n')
1422 | .replace(/\r/g, '\\r') + '"';
1423 | }
1424 | };
1425 |
1426 | var reservedWords = (
1427 | "break else new var" +
1428 | " case finally return void" +
1429 | " catch for switch while" +
1430 | " continue function this with" +
1431 | " default if throw" +
1432 | " delete in try" +
1433 | " do instanceof typeof" +
1434 | " abstract enum int short" +
1435 | " boolean export interface static" +
1436 | " byte extends long super" +
1437 | " char final native synchronized" +
1438 | " class float package throws" +
1439 | " const goto private transient" +
1440 | " debugger implements protected volatile" +
1441 | " double import public let yield"
1442 | ).split(" ");
1443 |
1444 | var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
1445 |
1446 | for(var i=0, l=reservedWords.length; i