mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
custom-server-actionhero (#3905)
This commit is contained in:
parent
ba226c2d26
commit
a39e54c675
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -7,8 +7,9 @@ node_modules
|
|||
package-lock.json
|
||||
test/node_modules
|
||||
|
||||
# logs
|
||||
# logs & pids
|
||||
*.log
|
||||
pids
|
||||
|
||||
# coverage
|
||||
.nyc_output
|
||||
|
|
99
examples/custom-server-actionhero/README.md
Normal file
99
examples/custom-server-actionhero/README.md
Normal file
|
@ -0,0 +1,99 @@
|
|||
# Running Next.JS and React /inside/ of ActionHero
|
||||
|
||||
This server will render dynamic next.js/react pages on some routes, and normal ActionHero API requests on others.
|
||||
This configuration works with both Next and ActionHero hot reloading of code.
|
||||
|
||||
A more detailed example showcasing how to use fetch and web sockets to interact with your API can be found here: https://github.com/actionhero/next-in-actionhero
|
||||
|
||||
## To install:
|
||||
(assuming you have [node](http://nodejs.org/) and NPM installed)
|
||||
|
||||
`npm install`
|
||||
|
||||
## To Run:
|
||||
`npm start`
|
||||
|
||||
|
||||
## How does this work?
|
||||
|
||||
1. Create an initializer to load next.js and create a handler that can extract the normal node `req` and `res` from the connection
|
||||
|
||||
```js
|
||||
// initializers/next.js
|
||||
|
||||
const {Initializer, api} = require('actionhero')
|
||||
const next = require('next')
|
||||
|
||||
module.exports = class NextInitializer extends Initializer {
|
||||
constructor () {
|
||||
super()
|
||||
this.name = 'next'
|
||||
}
|
||||
|
||||
async initialize () {
|
||||
api.next = {
|
||||
render: async (connection) => {
|
||||
if (connection.type !== 'web') { throw new Error('Connections for NEXT apps must be of type "web"') }
|
||||
const req = connection.rawConnection.req
|
||||
const res = connection.rawConnection.res
|
||||
return api.next.handle(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
api.next.dev = (api.env === 'development')
|
||||
if (api.next.dev) { api.log('Running next in development mode...') }
|
||||
|
||||
api.next.app = next({dev: api.next.dev})
|
||||
api.next.handle = api.next.app.getRequestHandler()
|
||||
await api.next.app.prepare()
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await api.next.app.close()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Create an action which will run the above `api.next.render(connection)`. Note that we will not be relying on ActionHero to respond to the client's request in this case, and leave that up to next (via: `data.toRender = false`)
|
||||
|
||||
```js
|
||||
// actions/next.js
|
||||
|
||||
const {Action, api} = require('actionhero')
|
||||
|
||||
module.exports = class CreateChatRoom extends Action {
|
||||
constructor () {
|
||||
super()
|
||||
this.name = 'render'
|
||||
this.description = 'I render the next.js react website'
|
||||
}
|
||||
|
||||
async run (data) {
|
||||
data.toRender = false
|
||||
return api.next.render(data.connection)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
3. Tell ActionHero to use the api rather than the file server as the top-level route in `api.config.servers.web.rootEndpointType = 'api'`. This will allows "/" to listen to API requests. Also update `api.config.general.paths.public = [ path.join(__dirname, '/../static') ]`. In this configuration, the next 'static' renderer will take priority over the ActionHero 'public file' api. Note that any static assets (CSS, fonts, etc) will need to be in "./static" rather than "./public".
|
||||
|
||||
Note that this is where the websocket server, if you enable it, will place the `ActionheroWebsocketClient` libraray.
|
||||
|
||||
4. Configure a wild-card route at the lowest priority of your GET handler to catch all web requests that aren't caught by other actions:
|
||||
|
||||
```js
|
||||
// config/routes.js
|
||||
|
||||
exports['default'] = {
|
||||
routes: (api) => {
|
||||
return {
|
||||
get: [
|
||||
{ path: '/time', action: 'time' },
|
||||
|
||||
{ path: '/', matchTrailingPathParts: true, action: 'render' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
15
examples/custom-server-actionhero/actions/render.js
Normal file
15
examples/custom-server-actionhero/actions/render.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
'use strict'
|
||||
const {Action, api} = require('actionhero')
|
||||
|
||||
module.exports = class CreateChatRoom extends Action {
|
||||
constructor () {
|
||||
super()
|
||||
this.name = 'render'
|
||||
this.description = 'I render the next.js react website'
|
||||
}
|
||||
|
||||
async run (data) {
|
||||
data.toRender = false
|
||||
return api.next.render(data.connection)
|
||||
}
|
||||
}
|
97
examples/custom-server-actionhero/config/api.js
Normal file
97
examples/custom-server-actionhero/config/api.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
exports['default'] = {
|
||||
general: (api) => {
|
||||
const packageJSON = require(api.projectRoot + path.sep + 'package.json')
|
||||
|
||||
return {
|
||||
apiVersion: packageJSON.version,
|
||||
serverName: packageJSON.name,
|
||||
// id can be set here, or it will be generated dynamically.
|
||||
// Be sure that every server you run has a unique ID (which will happen when generated dynamically)
|
||||
// id: 'myActionHeroServer',
|
||||
// A unique token to your application that servers will use to authenticate to each other
|
||||
serverToken: 'change-me',
|
||||
// the redis prefix for actionhero's cache objects
|
||||
cachePrefix: 'actionhero:cache:',
|
||||
// the redis prefix for actionhero's cache/lock objects
|
||||
lockPrefix: 'actionhero:lock:',
|
||||
// how long will a lock last before it exipres (ms)?
|
||||
lockDuration: 1000 * 10, // 10 seconds
|
||||
// Watch for changes in actions and tasks, and reload/restart them on the fly
|
||||
developmentMode: true,
|
||||
// How many pending actions can a single connection be working on
|
||||
simultaneousActions: 5,
|
||||
// allow connections to be created without remoteIp and remotePort (they will be set to 0)
|
||||
enforceConnectionProperties: true,
|
||||
// disables the whitelisting of client params
|
||||
disableParamScrubbing: false,
|
||||
// params you would like hidden from any logs
|
||||
filteredParams: [],
|
||||
// values that signify missing params
|
||||
missingParamChecks: [null, '', undefined],
|
||||
// The default filetype to server when a user requests a directory
|
||||
directoryFileType: 'index.html',
|
||||
// What log-level should we use for file requests?
|
||||
fileRequestLogLevel: 'info',
|
||||
// The default priority level given to middleware of all types (action, connection, say, and task)
|
||||
defaultMiddlewarePriority: 100,
|
||||
// Which channel to use on redis pub/sub for RPC communication
|
||||
channel: 'actionhero',
|
||||
// How long to wait for an RPC call before considering it a failure
|
||||
rpcTimeout: 5000,
|
||||
// should CLI methods and help include internal ActionHero CLI methods?
|
||||
cliIncludeInternal: true,
|
||||
// configuration for your actionhero project structure
|
||||
paths: {
|
||||
'action': [path.join(__dirname, '/../actions')],
|
||||
'task': [path.join(__dirname, '/../tasks')],
|
||||
'public': [path.join(__dirname, '/../static')],
|
||||
'pid': [path.join(__dirname, '/../pids')],
|
||||
'log': [path.join(__dirname, '/../log')],
|
||||
'server': [path.join(__dirname, '/../servers')],
|
||||
'cli': [path.join(__dirname, '/../bin')],
|
||||
'initializer': [path.join(__dirname, '/../initializers')],
|
||||
'plugin': [path.join(__dirname, '/../node_modules')],
|
||||
'locale': [path.join(__dirname, '/../locales')]
|
||||
},
|
||||
// hash containing chat rooms you wish to be created at server boot
|
||||
startingChatRooms: {
|
||||
// format is {roomName: {authKey, authValue}}
|
||||
// 'secureRoom': {authorized: true},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
general: (api) => {
|
||||
return {
|
||||
id: 'test-server-' + process.pid,
|
||||
serverToken: 'serverToken-' + process.pid,
|
||||
developmentMode: true,
|
||||
startingChatRooms: {
|
||||
'defaultRoom': {},
|
||||
'otherRoom': {}
|
||||
},
|
||||
paths: {
|
||||
'locale': [
|
||||
// require('os').tmpdir() + require('path').sep + 'locales',
|
||||
path.join(__dirname, '/../locales')
|
||||
]
|
||||
},
|
||||
rpcTimeout: 3000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.production = {
|
||||
general: (api) => {
|
||||
return {
|
||||
fileRequestLogLevel: 'debug',
|
||||
developmentMode: false
|
||||
}
|
||||
}
|
||||
}
|
148
examples/custom-server-actionhero/config/errors.js
Normal file
148
examples/custom-server-actionhero/config/errors.js
Normal file
|
@ -0,0 +1,148 @@
|
|||
'use strict'
|
||||
|
||||
// error messages can be strings of objects
|
||||
exports['default'] = {
|
||||
errors: (api) => {
|
||||
return {
|
||||
'_toExpand': false,
|
||||
|
||||
// ///////////////
|
||||
// SERIALIZERS //
|
||||
// ///////////////
|
||||
|
||||
serializers: {
|
||||
servers: {
|
||||
web: (error) => {
|
||||
if (error.message) {
|
||||
return String(error.message)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
},
|
||||
websocket: (error) => {
|
||||
if (error.message) {
|
||||
return String(error.message)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
},
|
||||
socket: (error) => {
|
||||
if (error.message) {
|
||||
return String(error.message)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
},
|
||||
specHelper: (error) => {
|
||||
if (error.message) {
|
||||
return 'Error: ' + String(error.message)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ///////////
|
||||
// ACTIONS //
|
||||
// ///////////
|
||||
|
||||
// When a params for an action is invalid
|
||||
invalidParams: (data, validationErrors) => {
|
||||
if (validationErrors.length >= 0) { return validationErrors[0] }
|
||||
return data.connection.localize('actionhero.errors.invalidParams')
|
||||
},
|
||||
|
||||
// When a required param for an action is not provided
|
||||
missingParams: (data, missingParams) => {
|
||||
return data.connection.localize(['actionhero.errors.missingParams', {param: missingParams[0]}])
|
||||
},
|
||||
|
||||
// user requested an unknown action
|
||||
unknownAction: (data) => {
|
||||
return data.connection.localize('actionhero.errors.unknownAction')
|
||||
},
|
||||
|
||||
// action not useable by this client/server type
|
||||
unsupportedServerType: (data) => {
|
||||
return data.connection.localize(['actionhero.errors.unsupportedServerType', {type: data.connection.type}])
|
||||
},
|
||||
|
||||
// action failed because server is mid-shutdown
|
||||
serverShuttingDown: (data) => {
|
||||
return data.connection.localize('actionhero.errors.serverShuttingDown')
|
||||
},
|
||||
|
||||
// action failed because this client already has too many pending acitons
|
||||
// limit defined in api.config.general.simultaneousActions
|
||||
tooManyPendingActions: (data) => {
|
||||
return data.connection.localize('actionhero.errors.tooManyPendingActions')
|
||||
},
|
||||
|
||||
dataLengthTooLarge: (maxLength, receivedLength) => {
|
||||
return api.i18n.localize(['actionhero.errors.dataLengthTooLarge', {maxLength: maxLength, receivedLength: receivedLength}])
|
||||
},
|
||||
|
||||
// ///////////////
|
||||
// FILE SERVER //
|
||||
// ///////////////
|
||||
|
||||
// The body message to accompany 404 (file not found) errors regarding flat files
|
||||
// You may want to load in the contnet of 404.html or similar
|
||||
fileNotFound: (connection) => {
|
||||
return connection.localize(['actionhero.errors.fileNotFound'])
|
||||
},
|
||||
|
||||
// user didn't request a file
|
||||
fileNotProvided: (connection) => {
|
||||
return connection.localize('actionhero.errors.fileNotProvided')
|
||||
},
|
||||
|
||||
// something went wrong trying to read the file
|
||||
fileReadError: (connection, error) => {
|
||||
return connection.localize(['actionhero.errors.fileReadError', {error: String(error)}])
|
||||
},
|
||||
|
||||
// ///////////////
|
||||
// CONNECTIONS //
|
||||
// ///////////////
|
||||
|
||||
verbNotFound: (connection, verb) => {
|
||||
return connection.localize(['actionhero.errors.verbNotFound', {verb: verb}])
|
||||
},
|
||||
|
||||
verbNotAllowed: (connection, verb) => {
|
||||
return connection.localize(['actionhero.errors.verbNotAllowed', {verb: verb}])
|
||||
},
|
||||
|
||||
connectionRoomAndMessage: (connection) => {
|
||||
return connection.localize('actionhero.errors.connectionRoomAndMessage')
|
||||
},
|
||||
|
||||
connectionNotInRoom: (connection, room) => {
|
||||
return connection.localize(['actionhero.errors.connectionNotInRoom', {room: room}])
|
||||
},
|
||||
|
||||
connectionAlreadyInRoom: (connection, room) => {
|
||||
return connection.localize(['actionhero.errors.connectionAlreadyInRoom', {room: room}])
|
||||
},
|
||||
|
||||
connectionRoomHasBeenDeleted: (room) => {
|
||||
return api.i18n.localize('actionhero.errors.connectionRoomHasBeenDeleted')
|
||||
},
|
||||
|
||||
connectionRoomNotExist: (room) => {
|
||||
return api.i18n.localize('actionhero.errors.connectionRoomNotExist')
|
||||
},
|
||||
|
||||
connectionRoomExists: (room) => {
|
||||
return api.i18n.localize('actionhero.errors.connectionRoomExists')
|
||||
},
|
||||
|
||||
connectionRoomRequired: (room) => {
|
||||
return api.i18n.localize('actionhero.errors.connectionRoomRequired')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
36
examples/custom-server-actionhero/config/i18n.js
Normal file
36
examples/custom-server-actionhero/config/i18n.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
exports['default'] = {
|
||||
i18n: (api) => {
|
||||
return {
|
||||
// visit https://github.com/mashpie/i18n-node to see all configuration options
|
||||
// locale path can be configired from within ./config/api.js
|
||||
locales: ['en'],
|
||||
|
||||
// how would you like your lanaguages to fall back if a translation string is missing?
|
||||
fallbacks: {
|
||||
// 'es': 'en'
|
||||
},
|
||||
|
||||
// configure i18n to allow for object-style key lookup
|
||||
objectNotation: true,
|
||||
|
||||
// should actionhero append any missing translations to the locale file?
|
||||
updateFiles: true,
|
||||
|
||||
// this will configure logging and error messages in the log(s)
|
||||
defaultLocale: 'en',
|
||||
|
||||
// the name of the method by which to determine the connection's locale
|
||||
// by default, every request will be in the 'en' locale
|
||||
// this method will be called witin the localiazation middleware on all requests
|
||||
determineConnectionLocale: 'api.i18n.determineConnectionLocale'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
i18n: (api) => {
|
||||
return {
|
||||
updateFiles: true
|
||||
}
|
||||
}
|
||||
}
|
60
examples/custom-server-actionhero/config/logger.js
Normal file
60
examples/custom-server-actionhero/config/logger.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const cluster = require('cluster')
|
||||
|
||||
exports['default'] = {
|
||||
logger: (api) => {
|
||||
let logger = {transports: []}
|
||||
|
||||
// console logger
|
||||
if (cluster.isMaster) {
|
||||
logger.transports.push(function (api, winston) {
|
||||
return new (winston.transports.Console)({
|
||||
colorize: true,
|
||||
level: 'info',
|
||||
timestamp: function () { return api.id + ' @ ' + new Date().toISOString() }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// file logger
|
||||
logger.transports.push(function (api, winston) {
|
||||
if (api.config.general.paths.log.length === 1) {
|
||||
const logDirectory = api.config.general.paths.log[0]
|
||||
try {
|
||||
fs.mkdirSync(logDirectory)
|
||||
} catch (e) {
|
||||
if (e.code !== 'EEXIST') {
|
||||
throw (new Error('Cannot create log directory @ ' + logDirectory))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new (winston.transports.File)({
|
||||
filename: api.config.general.paths.log[0] + '/' + api.pids.title + '.log',
|
||||
level: 'info',
|
||||
timestamp: function () { return api.id + ' @ ' + new Date().toISOString() }
|
||||
})
|
||||
})
|
||||
|
||||
// the maximum length of param to log (we will truncate)
|
||||
logger.maxLogStringLength = 100
|
||||
|
||||
// you can optionally set custom log levels
|
||||
// logger.levels = {good: 0, bad: 1};
|
||||
|
||||
// you can optionally set custom log colors
|
||||
// logger.colors = {good: 'blue', bad: 'red'};
|
||||
|
||||
return logger
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
logger: (api) => {
|
||||
return {
|
||||
transports: null
|
||||
}
|
||||
}
|
||||
}
|
27
examples/custom-server-actionhero/config/plugins.js
Normal file
27
examples/custom-server-actionhero/config/plugins.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
exports['default'] = {
|
||||
plugins: (api) => {
|
||||
/*
|
||||
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,
|
||||
cli: true
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return {}
|
||||
}
|
||||
}
|
49
examples/custom-server-actionhero/config/redis.js
Normal file
49
examples/custom-server-actionhero/config/redis.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
let host = process.env.REDIS_HOST || '127.0.0.1'
|
||||
let port = process.env.REDIS_PORT || 6379
|
||||
let db = process.env.REDIS_DB || 0
|
||||
let password = process.env.REDIS_PASSWORD || null
|
||||
const maxBackoff = 1000
|
||||
|
||||
if (process.env.REDIS_URL) {
|
||||
password = process.env.REDIS_URL.match(/redis:\/\/.*:(.*)@.*:\d*$/i)[1]
|
||||
host = process.env.REDIS_URL.match(/redis:\/\/.*:.*@(.*):\d*$/i)[1]
|
||||
port = parseInt(process.env.REDIS_URL.match(/redis:\/\/.*:.*@.*:(\d*)$/i)[1])
|
||||
}
|
||||
|
||||
exports['default'] = {
|
||||
redis: (api) => {
|
||||
// konstructor: The redis client constructor method. All redis methods must be promises
|
||||
// args: The arguments to pass to the constructor
|
||||
// buildNew: is it `new konstructor()` or just `konstructor()`?
|
||||
|
||||
function retryStrategy (times) {
|
||||
if (times === 1) {
|
||||
const error = 'Unable to connect to Redis - please check your Redis config!'
|
||||
if (process.env.NODE_ENV === 'test') { console.error(error) } else { api.log(error, 'error') }
|
||||
return 5000
|
||||
}
|
||||
return Math.min(times * 50, maxBackoff)
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: true,
|
||||
|
||||
'_toExpand': false,
|
||||
client: {
|
||||
konstructor: require('ioredis'),
|
||||
args: [{ port: port, host: host, password: password, db: db, retryStrategy: retryStrategy }],
|
||||
buildNew: true
|
||||
},
|
||||
subscriber: {
|
||||
konstructor: require('ioredis'),
|
||||
args: [{ port: port, host: host, password: password, db: db, retryStrategy: retryStrategy }],
|
||||
buildNew: true
|
||||
},
|
||||
tasks: {
|
||||
konstructor: require('ioredis'),
|
||||
args: [{ port: port, host: host, password: password, db: db, retryStrategy: retryStrategy }],
|
||||
buildNew: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
examples/custom-server-actionhero/config/routes.js
Normal file
9
examples/custom-server-actionhero/config/routes.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
exports['default'] = {
|
||||
routes: (api) => {
|
||||
return {
|
||||
get: [
|
||||
{ path: '/', matchTrailingPathParts: true, action: 'render' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
37
examples/custom-server-actionhero/config/servers/socket.js
Normal file
37
examples/custom-server-actionhero/config/servers/socket.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
'use strict'
|
||||
|
||||
exports['default'] = {
|
||||
servers: {
|
||||
socket: (api) => {
|
||||
return {
|
||||
enabled: (process.env.ENABLE_TCP_SERVER !== undefined),
|
||||
// TCP or TLS?
|
||||
secure: false,
|
||||
// Passed to tls.createServer if secure=true. Should contain SSL certificates
|
||||
serverOptions: {},
|
||||
// Port or Socket
|
||||
port: 5000,
|
||||
// Which IP to listen on (use 0.0.0.0 for all)
|
||||
bindIP: '0.0.0.0',
|
||||
// Enable TCP KeepAlive pings on each connection?
|
||||
setKeepAlive: false,
|
||||
// Delimiter string for incoming messages
|
||||
delimiter: '\n',
|
||||
// Maximum incoming message string length in Bytes (use 0 for Infinite)
|
||||
maxDataLength: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
servers: {
|
||||
socket: (api) => {
|
||||
return {
|
||||
enabled: true,
|
||||
port: 1001 + (process.pid % 64535),
|
||||
secure: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
121
examples/custom-server-actionhero/config/servers/web.js
Normal file
121
examples/custom-server-actionhero/config/servers/web.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
|
||||
exports['default'] = {
|
||||
servers: {
|
||||
web: (api) => {
|
||||
return {
|
||||
enabled: true,
|
||||
// HTTP or HTTPS?
|
||||
secure: false,
|
||||
// Passed to https.createServer if secure=true. Should contain SSL certificates
|
||||
serverOptions: {},
|
||||
// Should we redirect all traffic to the first host in this array if hte request header doesn't match?
|
||||
// i.e.: [ 'https://www.site.com' ]
|
||||
allowedRequestHosts: process.env.ALLOWED_HOSTS ? process.env.ALLOWED_HOSTS.split(',') : [],
|
||||
// Port or Socket Path
|
||||
port: process.env.PORT || 8080,
|
||||
// Which IP to listen on (use '0.0.0.0' for all; '::' for all on ipv4 and ipv6)
|
||||
// Set to `null` when listening to socket
|
||||
bindIP: '0.0.0.0',
|
||||
// Any additional headers you want actionhero to respond with
|
||||
httpHeaders: {
|
||||
'X-Powered-By': api.config.general.serverName,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS, TRACE',
|
||||
'Access-Control-Allow-Headers': 'Content-Type'
|
||||
},
|
||||
// Route that actions will be served from; secondary route against this route will be treated as actions,
|
||||
// IE: /api/?action=test == /api/test/
|
||||
urlPathForActions: 'api',
|
||||
// Route that static files will be served from;
|
||||
// path (relative to your project root) to serve static content from
|
||||
// set to `null` to disable the file server entirely
|
||||
urlPathForFiles: 'public',
|
||||
// When visiting the root URL, should visitors see 'api' or 'file'?
|
||||
// Visitors can always visit /api and /public as normal
|
||||
rootEndpointType: 'api',
|
||||
// simple routing also adds an 'all' route which matches /api/:action for all actions
|
||||
simpleRouting: true,
|
||||
// queryRouting allows an action to be defined via a URL param, ie: /api?action=:action
|
||||
queryRouting: true,
|
||||
// The cache or (if etags are enabled) next-revalidation time to be returned for all flat files served from /public; defined in seconds
|
||||
flatFileCacheDuration: 60,
|
||||
// Add an etag header to requested flat files which acts as fingerprint that changes when the file is updated;
|
||||
// Client will revalidate the fingerprint at latest after flatFileCacheDuration and reload it if the etag (and therfore the file) changed
|
||||
// or continue to use the cached file if it's still valid
|
||||
enableEtag: true,
|
||||
// should we save the un-parsed HTTP POST/PUT payload to connection.rawConnection.params.rawBody?
|
||||
saveRawBody: false,
|
||||
// How many times should we try to boot the server?
|
||||
// This might happen if the port is in use by another process or the socketfile is claimed
|
||||
bootAttempts: 1,
|
||||
// Settings for determining the id of an http(s) request (browser-fingerprint)
|
||||
fingerprintOptions: {
|
||||
cookieKey: 'sessionID',
|
||||
toSetCookie: true,
|
||||
onlyStaticElements: false,
|
||||
settings: {
|
||||
path: '/',
|
||||
expires: 3600000
|
||||
}
|
||||
},
|
||||
// Options to be applied to incoming file uploads.
|
||||
// More options and details at https://github.com/felixge/node-formidable
|
||||
formOptions: {
|
||||
uploadDir: os.tmpdir(),
|
||||
keepExtensions: false,
|
||||
maxFieldsSize: 1024 * 1024 * 100
|
||||
},
|
||||
// Should we pad JSON responses with whitespace to make them more human-readable?
|
||||
// set to null to disable
|
||||
padding: 2,
|
||||
// Options to configure metadata in responses
|
||||
metadataOptions: {
|
||||
serverInformation: true,
|
||||
requesterInformation: true
|
||||
},
|
||||
// When true, returnErrorCodes will modify the response header for http(s) clients if connection.error is not null.
|
||||
// You can also set connection.rawConnection.responseHttpCode to specify a code per request.
|
||||
returnErrorCodes: true,
|
||||
// should this node server attempt to gzip responses if the client can accept them?
|
||||
// this will slow down the performance of actionhero, and if you need this funcionality, it is recommended that you do this upstream with nginx or your load balancer
|
||||
compress: false,
|
||||
// options to pass to the query parser
|
||||
// learn more about the options @ https://github.com/hapijs/qs
|
||||
queryParseOptions: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.production = {
|
||||
servers: {
|
||||
web: (api) => {
|
||||
return {
|
||||
padding: null,
|
||||
metadataOptions: {
|
||||
serverInformation: false,
|
||||
requesterInformation: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
servers: {
|
||||
web: (api) => {
|
||||
return {
|
||||
secure: false,
|
||||
port: process.env.PORT || 1000 + (process.pid % 64535),
|
||||
matchExtensionMime: true,
|
||||
metadataOptions: {
|
||||
serverInformation: true,
|
||||
requesterInformation: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
'use strict'
|
||||
|
||||
// Note that to use the websocket server, you also need the web server enabled
|
||||
|
||||
exports['default'] = {
|
||||
servers: {
|
||||
websocket: (api) => {
|
||||
return {
|
||||
enabled: true,
|
||||
// you can pass a FQDN (string) here or 'window.location.origin'
|
||||
clientUrl: 'window.location.origin',
|
||||
// Directory to render client-side JS.
|
||||
// Path should start with "/" and will be built starting from api.config..general.paths.public
|
||||
clientJsPath: 'javascript/',
|
||||
// the name of the client-side JS file to render. Both `.js` and `.min.js` versions will be created
|
||||
// do not include the file exension
|
||||
// set to `undefined` to not render the client-side JS on boot
|
||||
clientJsName: 'ActionheroWebsocketClient',
|
||||
// should the server signal clients to not reconnect when the server is shutdown/reboot
|
||||
destroyClientsOnShutdown: false,
|
||||
|
||||
// websocket Server Options:
|
||||
server: {
|
||||
// authorization: null,
|
||||
// pathname: '/primus',
|
||||
// parser: 'JSON',
|
||||
// transformer: 'websockets',
|
||||
// plugin: {},
|
||||
// timeout: 35000,
|
||||
// origins: '*',
|
||||
// methods: ['GET','HEAD','PUT','POST','DELETE','OPTIONS'],
|
||||
// credentials: true,
|
||||
// maxAge: '30 days',
|
||||
// exposed: false,
|
||||
},
|
||||
|
||||
// websocket Client Options:
|
||||
client: {
|
||||
apiPath: '/api' // the api base endpoint on your actionhero server
|
||||
// reconnect: {},
|
||||
// timeout: 10000,
|
||||
// ping: 25000,
|
||||
// pong: 10000,
|
||||
// strategy: "online",
|
||||
// manual: false,
|
||||
// websockets: true,
|
||||
// network: true,
|
||||
// transport: {},
|
||||
// queueSize: Infinity,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports['test'] = {
|
||||
servers: {
|
||||
websocket: (api) => {
|
||||
return { clientUrl: null }
|
||||
}
|
||||
}
|
||||
}
|
61
examples/custom-server-actionhero/config/tasks.js
Normal file
61
examples/custom-server-actionhero/config/tasks.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
exports['default'] = {
|
||||
tasks: (api) => {
|
||||
return {
|
||||
// Should this node run a scheduler to promote delayed tasks?
|
||||
scheduler: false,
|
||||
// what queues should the taskProcessors work?
|
||||
queues: ['*'],
|
||||
// Logging levels of task workers
|
||||
workerLogging: {
|
||||
failure: 'error', // task failure
|
||||
success: 'info', // task success
|
||||
start: 'info',
|
||||
end: 'info',
|
||||
cleaning_worker: 'info',
|
||||
poll: 'debug',
|
||||
job: 'debug',
|
||||
pause: 'debug',
|
||||
internalError: 'error',
|
||||
multiWorkerAction: 'debug'
|
||||
},
|
||||
// Logging levels of the task scheduler
|
||||
schedulerLogging: {
|
||||
start: 'info',
|
||||
end: 'info',
|
||||
poll: 'debug',
|
||||
enqueue: 'debug',
|
||||
reEnqueue: 'debug',
|
||||
working_timestamp: 'debug',
|
||||
transferred_job: 'debug'
|
||||
},
|
||||
// how long to sleep between jobs / scheduler checks
|
||||
timeout: 5000,
|
||||
// at minimum, how many parallel taskProcessors should this node spawn?
|
||||
// (have number > 0 to enable, and < 1 to disable)
|
||||
minTaskProcessors: 0,
|
||||
// at maximum, how many parallel taskProcessors should this node spawn?
|
||||
maxTaskProcessors: 0,
|
||||
// how often should we check the event loop to spawn more taskProcessors?
|
||||
checkTimeout: 500,
|
||||
// how many ms would constitue an event loop delay to halt taskProcessors spawning?
|
||||
maxEventLoopDelay: 5,
|
||||
// When we kill off a taskProcessor, should we disconnect that local redis connection?
|
||||
toDisconnectProcessors: true,
|
||||
// Customize Resque primitives, replace null with required replacement.
|
||||
resque_overrides: {
|
||||
queue: null,
|
||||
multiWorker: null,
|
||||
scheduler: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
tasks: (api) => {
|
||||
return {
|
||||
timeout: 100,
|
||||
checkTimeout: 50
|
||||
}
|
||||
}
|
||||
}
|
32
examples/custom-server-actionhero/initializers/next.js
Normal file
32
examples/custom-server-actionhero/initializers/next.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
'use strict'
|
||||
const {Initializer, api} = require('actionhero')
|
||||
const next = require('next')
|
||||
|
||||
module.exports = class NextInitializer extends Initializer {
|
||||
constructor () {
|
||||
super()
|
||||
this.name = 'next'
|
||||
}
|
||||
|
||||
async initialize () {
|
||||
api.next = {
|
||||
render: async (connection) => {
|
||||
if (connection.type !== 'web') { throw new Error('Connections for NEXT apps must be of type "web"') }
|
||||
const req = connection.rawConnection.req
|
||||
const res = connection.rawConnection.res
|
||||
return api.next.handle(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
api.next.dev = (api.env === 'development')
|
||||
if (api.next.dev) { api.log('Running next in development mode...') }
|
||||
|
||||
api.next.app = next({dev: api.next.dev})
|
||||
api.next.handle = api.next.app.getRequestHandler()
|
||||
await api.next.app.prepare()
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await api.next.app.close()
|
||||
}
|
||||
}
|
34
examples/custom-server-actionhero/locales/en.json
Normal file
34
examples/custom-server-actionhero/locales/en.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"actionhero": {
|
||||
"welcomeMessage": "Hello! Welcome to the actionhero api",
|
||||
"goodbyeMessage": "Bye!",
|
||||
"cache": {
|
||||
"objectNotFound": "Object not found",
|
||||
"objectLocked": "Object locked",
|
||||
"objectExpired": "Object expired"
|
||||
},
|
||||
"errors": {
|
||||
"invalidParams": "validation error",
|
||||
"missingParams": "{{param}} is a required parameter for this action",
|
||||
"unknownAction": "unknown action or invalid apiVersion",
|
||||
"unsupportedServerType": "this action does not support the {{type}} connection type",
|
||||
"serverShuttingDown": "the server is shutting down",
|
||||
"tooManyPendingActions": "you have too many pending requests",
|
||||
"dataLengthTooLarge": "data length is too big ({{maxLength}} received/{{receivedLength}} max)",
|
||||
"fileNotFound": "That file is not found",
|
||||
"fileNotProvided": "file is a required param to send a file",
|
||||
"fileReadError": "error reading file: {{error}}",
|
||||
"verbNotFound": "I do not know know to perform this verb ({{verb}})",
|
||||
"verbNotAllowed": "verb not found or not allowed ({{verb}})",
|
||||
"connectionRoomAndMessage": "both room and message are required",
|
||||
"connectionNotInRoom": "connection not in this room ({{room}})",
|
||||
"connectionAlreadyInRoom": "connection already in this room ({{room}})",
|
||||
"connectionRoomHasBeenDeleted": "this room has been deleted",
|
||||
"connectionRoomNotExist": "room does not exist",
|
||||
"connectionRoomExists": "room exists",
|
||||
"connectionRoomRequired": "a room is required"
|
||||
}
|
||||
},
|
||||
"Your random number is {{number}}": "Your random number is {{number}}",
|
||||
"Node Healthy": "Node Healthy"
|
||||
}
|
17
examples/custom-server-actionhero/package.json
Normal file
17
examples/custom-server-actionhero/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"actionhero": "^18.1.2",
|
||||
"ioredis": "^3.2.2",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"next": "^5.0.1-canary.9",
|
||||
"react": "^16.1.1",
|
||||
"react-dom": "^16.1.1",
|
||||
"ws": "^4.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "actionhero start"
|
||||
}
|
||||
}
|
1
examples/custom-server-actionhero/pages/a.js
Normal file
1
examples/custom-server-actionhero/pages/a.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default () => <div>a</div>
|
1
examples/custom-server-actionhero/pages/b.js
Normal file
1
examples/custom-server-actionhero/pages/b.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default () => <div>b</div>
|
9
examples/custom-server-actionhero/pages/index.js
Normal file
9
examples/custom-server-actionhero/pages/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default () => (
|
||||
<ul>
|
||||
<li><Link href='/b' as='/a'><a>a</a></Link></li>
|
||||
<li><Link href='/a' as='/b'><a>b</a></Link></li>
|
||||
</ul>
|
||||
)
|
Loading…
Reference in a new issue