Actionhero is a realtime multi-transport nodejs API Server with integrated cluster capabilities and delayed tasks
APACHE-2.0 License
Bot releases are hidden (Show)
Published by evantahler over 6 years ago
actionhero generate
Fixes a bug introduced in v19.0.0 in which actionhero generate
does not create a .gitignore
file.
Published by evantahler over 6 years ago
We've updated to Node-Resque v5.3.0, which includes support for automatically pruning old stuck or crashed worker processes. The setting which controls how long a worker has to be non-response for in actionhero is api.config.tasks.stuckWorkerTimeout
Lear more here: https://github.com/taskrabbit/node-resque/releases/tag/v5.3.0
We noticed that the way that we were sharing session between WS and WEB connections (headers) was unlikely to work with some primus/websocket transports. We've changed to using query params which is what Priums recommends.
ActionHeroWebSocket
option (generated automatically) cookieKey
. This must now be added to your config files to share sessions between WEB and WEBSOCKET connections in the same browser.ActionHero now uses jest
for testing. We've removed mocha
and and chai
. You can continue to use these older tools in your projects, but all new projects will be generated with a jest-friendly layout.
Jest has a number of features which make it better than mocha:
The project changes include:
__tests__
, per Jest conventionmocha
, chai
, dirty-chai
, and cross-env
jest
, jest-environment-webdriver
actionhero generate
to use jasmine in new projectschromedriver
as a dev dependency; test cookies and sessions in a real browser with selenium
process.env.JEST_WORKER_ID
in tests to allow parallel testing without cross-pollution of redis, servers, etc.api.config.general.paths.pid
path directiveconfig/tasks.js
add api.config.tasks.stuckWorkerTimeout = 3600000
. This will be a 1 hour timeout for stuck/crashed worker processesconfig/servers/websocket.js
add api.config.servers.websocket.client.cookieKey = api.config.servers.web.fingerprintOptions.cookieKey
. This will instruct the ActionHero Websocket Clients to share the same cookie as the web server to share a fingerprint, which can be used to share session information.process.env.JEST_WORKER_ID
. Please view config/api.js
, config/redis.js
, config/servers/socket.js
, and config/servers/web.js
for more informationPublished by evantahler over 6 years ago
config/errors.js
) to handle this case. The default is a no-op. This allows you to customize how you handle throw
-ing errors from your actions.api.routes.registerRoute
routes.js
config file!ws
and standard
. However, these changes produced no warnings in the ActionHero test suite, so we do not believe that a major version change to ActionHero is required.We now test ActionHero with Circle.CI v2.0. We use the new workflow features. Thanks for supporting Open Source, Circle.CI!
Published by evantahler over 6 years ago
We now properly look for x-forward-for
when looking for a websocket connection's remoteIp
solves https://github.com/actionhero/actionhero/issues/1142.
const {Action} = require('actionhero')
module.exports = class StringResponse extends Action {
constructor () {
super()
this.name = 'stringResponse'
this.description = 'I send a string to the client as if it were a file'
}
async run (data) {
data.toRun = false
data.connection.pipe('my response', {'content-type': 'text/plain'})
}
}
Adds connection.pipe(string-or-buffer, headers)
which can be used in your actions for web clients. This is a helper method which is the same as:
data.connection.setHeader('Content-Type', 'application/xml; charset=utf-8')
data.connection.rawConnection.res.end(fileBuffer)
data.toRender = false
is still needed within the Action's run method.
Published by evantahler almost 7 years ago
This release brings the changes from ActionHero v18.1.1 back to v17, adding support for node.js v9.
Published by evantahler almost 7 years ago
engines
directive in ActionHero's package.json to allow any node.js version >=8.0.0If the web server sends a status code 304 response, then the fileStream handle may not have been closed.
Published by evantahler almost 7 years ago
ActionHero now support node.js v9.x, and we are running testing all new releases against it. The only change required was to support a change in how node handled parsedURL.search
( https://github.com/actionhero/actionhero/pull/1152).
ActionHero changed the behavior of action's param validators to only mark a param as invalid if they return false
or throw an error. Previously, validators which did not return (or returned null) would be marked as failing.
Fro example, these validators which return null (they don't return) on a passing param would mark the email and password {email: '[email protected]', password: 'password123'}
valid:
const {Action} = require('actionhero')
class ValidatedAction extends Action {
constructor () {
super()
this.inputs = {
email: {
required: true,
validator: this.emailValidator
},
password: {
required: true,
validator: this.passwordValidator
}
}
}
emailValidator (param) {
if (param.indexOf('@') < 0) {
throw new Error('that is not a valid email address')
}
}
passwordValidator (param) {
if (param.length < 4) {
throw new Error('password should be at least 3 letters long')
}
}
run (data) {
// ...
}
}
by @evantahler via https://github.com/actionhero/actionhero/pull/1158
connection.setStatusCode
for web clientsweb
type of connection to more easily set a custom status code in your actions. Remember that if you don't want to sent data back to the client, to use data.toRender = false
:// actions/custom404.js
const {Action} = require('./../index.js')
module.exports = class Custom404 extends Action {
constructor () {
super()
this.name = 'custom 404 message'
this.description = 'I return a 404 with a sassy message'
this.outputExample = {}
}
async run ({connection, response}) {
response.message = 'nope.'
connection.setStatusCode(404)
}
}
If there was a fatal exception loading an initializer, you can now see what file & line number caused the problem
Published by evantahler almost 7 years ago
To simplify the developer experience, any Action's valuator which returns false
will now fail, and return an error to the client.
You can now have simple "1-liner" validators like:
const {Action} = require('actionhero')
module.exports = class ValidationTest extends Action {
constructor () {
super()
this.name = 'validationTest'
this.description = 'I will test action input validators.'
this.outputExample = { string: 'imAString!' }
}
inputs () {
return {
string: {
required: true,
validator: param => { return typeof param === 'string' }
}
}
}
run ({params, response}) {
response.string = params.string
}
}
Published by evantahler almost 7 years ago
As promised, we now have documented all the public methods of ActionHero v17 using JSdoc. You can view these on docs.actionherojs.com (just choose the version at the bottom of the page), and in your own ActionHero projects. Just run npm run docs
to build the documentation pages locally. This replaces the old documentation which used to be on the www.actionherojs.com website.
A huge thank you to @gcoonrod for this work (via https://github.com/actionhero/actionhero/pull/1126 and https://github.com/actionhero/actionhero/pull/1144)!
Fixes a v17 bug in which a websocket connection could send 2 parallel requests .... one action request and one "verb" (like 'roomAdd', 'roomLeave' or 'roomView') in the following constellation:
The response payloads would be assigned the wrong response ID. This is now fixed.
Published by evantahler about 7 years ago
this
scope in Task#run.Prior to this update, this
in the run method of a task was a node-resque worker rather than the task itself. Now that it is updated, you can do things like the following:
module.exports = class SomeTask extends ActionHero.Task {
constructor () {
super()
this.name = 'someTask'
this.description = '...'
this.frequency = 0
this.queue = 'default'
this.middleware = []
}
async stepOne (params) {
...
}
async stepTwo (params) {
...
}
async run (params) {
let resOne = await this.stepOne()
let resTwo = await this.stepTwo()
...
}
}
While this is might be a breaking change for some exotic uses of tasks, the previous scope of a task was never documented. Now, we've defined and tested this behavior to ensure that it persists.
This is a fixes a problem running unit-tests with a non-standard redis configuration. E.g.
REDIS_HOST=192.168.99.100 npm test -- test/core/tasks.js
Various updates to the documentation have been added, and mistakes corrected. The docs site also looks much nicer now! Thanks to @gcoonrod, @rakhnin, and @evantahler
Published by evantahler about 7 years ago
ActionHero has been entirely re-written to use the new async/await
features available in Node.JS version 8.
If you don't know about writing javascript code in the async/await
style, there are many resources online, but this is my favorite: Explaining Async/Await in 7 seconds (you have time for this one!). There are no more callbacks and no more promise chains. You use try/catch to deal with errors. You can use normal for
and while
loops to work on async methods. The world is so much more pleasant! Code is more readable, and bugs are far easier to find and test.
With the newer versions of node, we also get access to real class methods, which make extending and sharing code much easier.
For example, the run method of an action using api.cache
used to look like:
exports.cacheTest = {
name: 'cacheTest',
description: 'I will test the internal cache functions of the API',
inputs: {
key: {
required: true,
formatter: function (s) { return String(s) }
},
value: {
required: true,
formatter: function (s) { return String(s) },
validator: function (s) {
if (s.length < 3) {
return '`value` should be at least 3 letters long'
} else { return true }
}
}
},
run: function (api, data, next) {
const key = 'cacheTest_' + data.params.key
const value = data.params.value
data.response.cacheTestResults = {}
api.cache.save(key, value, 5000, function (error, resp) {
if (error) { return next(error) }
data.response.cacheTestResults.saveResp = resp
api.cache.size(function (error, numberOfCacheObjects) {
if (error) { return next(error) }
data.response.cacheTestResults.sizeResp = numberOfCacheObjects
api.cache.load(key, function (error, resp, expireTimestamp, createdAt, readAt) {
if (error) { return next(error) }
data.response.cacheTestResults.loadResp = {
key: key,
value: resp,
expireTimestamp: expireTimestamp,
createdAt: createdAt,
readAt: readAt
}
api.cache.destroy(key, function (error, resp) {
data.response.cacheTestResults.deleteResp = resp
next(error)
})
})
})
})
}
}
But now, can be written simply as:
const {Action, api} = require('actionhero')
module.exports = class CacheTest extends Action {
constructor () {
super()
this.name = 'cacheTest'
this.description = 'I will test the internal cache functions of the API'
}
inputs () {
return {
key: {
required: true,
formatter: this.stringFormatter,
validator: this.stringValidator
},
value: {
required: true,
formatter: this.stringFormatter,
validator: this.stringValidator
}
}
}
stringFormatter (s) {
return String(s)
}
stringValidator (s) {
if (s.length < 3) {
throw new Error('inputs should be at least 3 letters long')
} else {
return true
}
}
async run ({params, response}) {
const key = 'cacheTest_' + params.key
const value = params.value
response.cacheTestResults = {
saveResp: await api.cache.save(key, value, 5000),
sizeResp: await api.cache.size(),
loadResp: await api.cache.load(key),
deleteResp: await api.cache.destroy(key)
}
}
}
The ActionHero Core Team had to make a hard decision with this release. This marks the first version we've released that does not work with all active LTS versions of Node.JS. Until now, this was our policy. However, We felt the gains in legibility, productivity, and debugging were so important that leaving 'legacy' users behind was the correct tradeoff.
However, to continue to support ActionHero users on v17, we will break with our other policy of only supporting "master". We've cut a v17 branch, and will continue to accept patches and updates to it until March of 2018. We will also port any security fixes from master back to v17. We know that upgrading to v18 (and perhaps a new version of Node.JS) will be the most difficult ActionHero migration to date, but I assure you it will be worth it!
I've also discussed these thoughts on the first "Always bet on Node podcast" with @dshaw and @mikeal and in this blog post.
To ease the upgrade process (and help new users), we have annotated all public APIs, methods and classes within the ActionHero codebase with jsDOC. This allows for a few wonderful things to happen:
@priavte
or not documetned at all).@gcoonrod has offered to back-port the new JSdoc documentation to the v17 branch of ActionHero, which is one of the ways we have committed to supporting this version of the project.
In a nutshell, the API changes can be described as follows:
Using these new features requires node V8.x.x and later. ActionHero will no longer be supporting node v4.x.x and v6.x.x. In the future, we can investigate using Babel to transpile for earlier versions, but today, that is not supported.
Anything that used to have a callback, is now an async
method which returns a response, and throws and error. This includes the run
method within actions and tasks.
Example:
//old
await api.cache.load('myKey', (error, value) => {
if (error) { return handleError(error) }
//...
})
// new
try {
let value = await api.cache.load('myKey')
} catch (error) {
api.log(error)
}
ll modules of ActionHero (Actions
, Tasks
, Initializers
, Servers
and, CLI
commands) are all now classes which extend some a similarly named module from ActionHero.
const {Action, api} = require('actionhero')
module.exports = class MyAction extends Action {
constructor () {
super()
this.name = 'myAction'
this.description = 'myAction'
this.outputExample = {}
}
async run (data) {
api.log('yay')
// your logic here
}
}
const {Task, api} = require('actionhero')
module.exports = class MyTask extends Task {
constructor () {
super()
this.name = 'myTask'
this.description = 'myTask'
this.frequency = 0
}
async run (data) {
api.log('yay')
// your logic here
}
}
This allows you to create your own classes which might share common inputs, middleware, or helper functions, ie: MyAction extends AuthenticatedAction
, where AuthenticatedAction extends ActionHero.Action
.
api
objectEvery method which used to supply the api
object as an argument no longer does. You now const api = require('actionhero').api
wherever you need it. This is helpful for a few reasons:
this.api = api
).fakeredis
Support fakeredis is dropped. In fact, the maintainer has stoped supporting it. ioredis
is now a required dependent package. That said, if you don't need any of the redis features (cache, chat, pub/sub, tasks), you can disable them all with api.config.redis.enabled = false
configuration option, and you can still boot an ActionHero server without redis.
actionhero link
(and actionhero unlink
) in favor of config/plugins.js
Using linkfiles was brittle. It didn't work with namespaced NPM packages, and struggled on windows computers. We are returning to using a configuration file to define plugins which your application will load.
// config/plugins.js
// If you want to use plugins in your application, include them here:
return {
'myPlugin': { path: __dirname + '/../node_modules/myPlugin' }
}
// You can also toggle on or off sections of a plugin to include (default true for all sections):
return {
'myPlugin': {
path: __dirname + '/../node_modules/myPlugin',
actions: true,
tasks: true,
initializers: true,
servers: true,
public: true
}
}
This also makes testing plugins much easier, as you can boot up an ActionHero server from within your plugin (if actionhero
is a devDependancy) with the following:
const path = require('path')
process.env.PROJECT_ROOT = path.join(__dirname, '..', 'node_modules', 'actionhero')
const ActionHero = require('actionhero')
const actionhero = new ActionHero.Process()
let api
describe('My Plugin', () => {
before(async () => {
let configChanges = {
plugins: {
'testPlugin': { path: path.join(__dirname, '..') }
}
}
api = await actionhero.start({configChanges})
})
after(async () => { await actionhero.stop() })
it('does stuff', async () => {
//...
})
})
actionhero generate plugin
A helper which you can use in an empty directory which will create a template plugin project
api.utils.recursiveDirectoryGlob
in favor of the nom glob
package.We can use the standard package now that we no longer need to traverse custom ActionHero link files
ActionheroClient
(the included client library for browser websocket clients) as been named a more clear ActionheroWebsocketClient
to avoid ambiguity. The node sever-sever package has been renamed actionhero-node-client
to help clear up any confusion.Thank you to everyone who helped make this release possible, especially @gcoonrod and @crrobinson14.
Published by evantahler about 7 years ago
connection.rawConnection.params.rawBody
on web connectionsSome APIs ask you to compare checksums of the body of a request (https://github.com/actionhero/actionhero/issues/1081) to ensure their origin. Until now, ActionHero did not allow you access to the raw POST/PUT body of an http request. A new config option, api.config.servers.web.saveRawBody
can be toggled to true
to save the body of your requests as a Buffer
.
Please note that enabling this feature may increase the memory requirements of each of your actions.
dot-prop
package with api.config.general.filteredParams
to allow for filtering of JSON paramsUses the dot-prop package to allow for filtering parameters using a dot notation.
Added to api.utils.filterObjectForLogging
since I wanted the filter for my own logging messages as well (not just action logs)
//example, as filled from config/api.js
filteredParams: [ 'p4', 'o1.p3' ],
//example, as used in initializers/actionProcessor.js
var params = { p1 : 1, o1 : { p2: 'ok', p3: 'secret' }, p4: 'long string that clogs logs' }
var filteredParams = api.utils.filterObjectForLogging( params)
//filteredParams --> { p1 : 1, o1 : { p2: 'ok', p3: '[FILTERED]' }, p4: '[FILTERED]' }
[ worker ]
(rather than worker:
) to match [ action ]
Published by evantahler over 7 years ago
connection.setHeader
, which is a proxy for connection.rawConnection.res.setHeader
//Initializer
module.exports = {
startPriority: 1000,
start: function (api, next) {
let webServer = api.servers.servers.web
webServer.connectionCustomMethods = webServer.connectionCustomMethods || {}
webServer.connectionCustomMethods.requestHeaders = function (connection) {
return connection.rawConnection.req.headers
}
}
}
//Action
module.exports = {
name: 'logHeaders',
description 'Log Web Request Headers',
run: function (api, data, next) {
let headers = data.connection.requestHeaders()
api.log('Headers:', 'debug', headers)
next()
}
}
connection.setHeader
only works for web connections.api.config.general.cliIncludeInternal
option, you can prevent use of the ActionHero CLI commands in your project from including the internal methods, like generate
and generateAction
data.toRender: false
package-lock.json
Published by evantahler over 7 years ago
stop
priority to be lastThe Reids initializer is likely to be relied on by many other parts of the system. To that end, the 'stop' priority of redis is now 99999
cross-env
now at v5.0.xws
now at v3.0.xioredis
now at v3.0.xPublished by evantahler over 7 years ago
We have reverted to bring back api.utils.recursiveDirectoryGlob
for link following. Perviously, v17.0.0 did not follow ActionHero's link files properly
via @S3bb1 via https://github.com/actionhero/actionhero/pull/1045
Published by evantahler over 7 years ago
connection.localize
by converting all localizations to use mustache-style strings. IE: data.connection.localize(['hello {{name}}', {name: 'Evan'}])
rather than data.connection.localize(['hello %s', 'Evan'])
a log ${message}
. Now that we required node v4+, this is OKactionhero
translation namespace: https://github.com/actionhero/actionhero/blob/mustache_i18n/locales/en.json
locales/en.json
is now checked into the projectactionhero generate
welcomeMessage
and goodbyeMessage
are removed from config into localeapi.i18n.localize
or connection.localize
on an unknown string will not cause an error, and this string will be added to the locale file properlyAllow actionhero developers to create new files in ./bin
which can be run via the CLI. These commands will have access to a the ActionHero api
and CLI arguments object within a run
method.
For example, a file in ./bin/redis/keys
would be run via ./node_modules/.bin/actionhero redis keys --prefix actionhero
'use strict'
module.exports = {
name: 'redis keys',
description: 'I list all the keys in redis',
example: 'actionhero keys --prefix actionhero',
inputs: {
prefix: {
requried: true,
default: 'actionhero',
note: 'the redis prefix for searching keys'
}
},
run: function (api, data, next) {
api.redis.clients.client.keys(data.params.prefix, (error, keys) => {
if (error) { throw error }
api.log(`Found ${keys.length} keys:`)
keys.forEach((k) => { api.log(k) })
return next(null, true)
})
}
}
./bin
directory../config/api.js
now defines api.config.general.paths.cli
, which defaults to path.join(__dirname, '/../bin')
. This is where the actionhero script runner sources its files.ActionHero CLI commands have:
Inputs for CLI commands have
These are sourced by actionhero help
, and the example above would return:
* redis keys
description: I list all the keys in redis
example: actionhero keys --prefix actionhero
inputs:
[prefix] (optional)
note: the redis prefix for searching keys
default: actionhero
This PR includes a refactor of all CLI commands
api.utils.recursiveDirectoryGlob
with the glob
packageBreaking Changes and How to Overcome Them:
Published by evantahler over 7 years ago
This minor release ensures that connections return error objects internally when attempting to use a not-found verb. This also ensure that the socket server responds with the error string in the same manner it used to maintain backwards compatibility.
by @evantahler via https://github.com/actionhero/actionhero/commit/dbef734459212635c5838d11009cff4ac9adfb01