UNPKG

newrelic

Version:
277 lines (240 loc) 8.31 kB
/* * Copyright 2020 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ 'use strict' var shared = require('./hapi/shared') module.exports = function initialize(agent, hapi, moduleName, shim) { if (!agent || !hapi || !shim) { shim && shim.logger.debug( 'Hapi instrumentation function called with incorrect arguments, not instrumenting.' ) return false } shim.setFramework(shim.HAPI) shim.setErrorPredicate(function hapiErrorPredicate(err) { return (err instanceof Error) }) if (hapi.createServer) { wrapCreateServer(shim, hapi) } else if (hapi.Server) { // Server.connection was removed in v17 if (!hapi.Server.prototype.connection) { return require('./hapi/hapi-17')(agent, hapi, moduleName, shim) } // See if we can find the plugin class. This should be the super class of // Server and will cover more scenarios. var Plugin = hapi.Server.super_ if (_isPluginClass(Plugin)) { wrapServer(shim, Plugin) } else { wrapServer(shim, hapi.Server) } } } function wrapServer(shim, Server) { // wrap server.handler function that registers a new handler type // the second argument is expected to be a function that generates the handler function shim.wrap(Server.prototype, 'handler', function wrapHandler(shim, original) { return function wrappedHandler() { var args = shim.argsToArray.apply(shim, arguments) var handlerGenerator = args[1] if (typeof handlerGenerator === 'function') { args[1] = wrapGenerator(handlerGenerator) } return original.apply(this, args) function wrapGenerator(generator) { function wrappedGenerator() { var generatorArgs = shim.argsToArray.apply(shim, arguments) var handler = generator.apply(this, generatorArgs) if (typeof handler === 'function') { var route = generatorArgs[0] return wrapRouteHandler(shim, handler, route && route.path) } return handler } wrappedGenerator.defaults = generator.defaults return wrappedGenerator } } }) shim.wrap(Server.prototype, 'route', function wrapRoute(shim, original) { return function wrappedRoute() { var args = shim.argsToArray.apply(shim, arguments) // first argument is expected to be the route configuration object if (!shim.isObject(args[0])) { return original.apply(this, args) } // If route is created via a plugin, pull prefix if it exists const prefix = this.realm && this.realm.modifiers && this.realm.modifiers.route && this.realm.modifiers.route.prefix || '' _wrapRoute(shim, args[0]) return original.apply(this, args) function _wrapRoute(shim, route) { const routePath = prefix + route.path // handler function could be on the route object, or on a nested config object if (shim.isArray(route)) { for (var i = 0; i < route.length; ++i) { _wrapRoute(shim, route[i]) } return } else if (route.config) { if (route.config.pre) { // config objects can also contain multiple OTHER handlers in a `pre` array route.config.pre = wrapPreHandlers(shim, route.config.pre, routePath) } if (route.config.handler) { wrapRouteHandler(shim, route.config, routePath) return } } wrapRouteHandler(shim, route, routePath) } } }) shim.wrap(Server.prototype, 'ext', function wrapExt(shim, original) { return function wrappedExt(event, method) { var args = shim.argsToArray.apply(shim, arguments) if (shim.isArray(event)) { for (var i = 0; i < event.length; i++) { event[i].method = wrapMiddleware(shim, event[i].method, event[i].type) } } else if (shim.isObject(event)) { event.method = wrapMiddleware(shim, event.method, event.type) } else if (shim.isString(event)) { args[1] = wrapMiddleware(shim, method, event) } else { shim.logger.debug('Unsupported event type %j', event) return } return original.apply(this, args) } }) } function wrapCreateServer(shim, hapi) { shim.wrap(hapi, 'createServer', function getWrapper(shim, createServer) { return function createServerWrapper() { var server = createServer.apply(this, arguments) wrapServer(shim, server.constructor) shim.unwrap(hapi, 'createServer') return server } }) } function wrapPreHandlers(shim, container, path) { if (shim.isArray(container)) { for (var i = 0; i < container.length; ++i) { container[i] = wrapPreHandlers(shim, container[i], path) } return container } else if (shim.isFunction(container)) { return _wrapPreHandler(container) } else if (container.method && shim.isFunction(container.method)) { return shim.wrap(container, 'method', function wrapHandler(shim, handler) { return _wrapPreHandler(handler) }) } // The 'pre' option also allows strings pointing to methods registered via // server.method() (ie: 'methodName(args)'). For the most part, these should // be simple utility functions, but may be something we want to wrap in the future. return container function _wrapPreHandler(handler) { return shim.recordMiddleware( wrapHapiHandler(shim, handler), buildMiddlewareSpec(shim, path, true) ) } } function wrapRouteHandler(shim, container, path) { if (shim.isFunction(container)) { return _wrapRouteHandler(container) } else if (container.handler && shim.isFunction(container.handler)) { return shim.wrap(container, 'handler', function wrapHandler(shim, handler) { return _wrapRouteHandler(handler) }) } function _wrapRouteHandler(handler) { return shim.recordMiddleware( wrapHapiHandler(shim, handler), buildMiddlewareSpec(shim, path) ) } } function buildMiddlewareSpec(shim, path, isPreHandler) { return { route: path, req: function getReq(shim, fn, fnName, args) { var request = args[0] if (request && request.raw) { return request.raw.req } }, next: function wrapNext(shim, fn, fnName, args, wrap) { var reply = args[1] if (!shim.isFunction(reply)) { return } wrapReply(wrap, reply, isPreHandler) }, params: function getParams(shim, fn, fnName, args) { var req = args[0] return req && req.params } } } function wrapHapiHandler(shim, handler) { return shim.wrap(handler, function wrapHandler(shim, original) { return function wrapped() { var reply = arguments[1] if (reply) { shim.recordRender(reply, 'view') } return original.apply(this, arguments) } }) } function wrapMiddleware(shim, middleware, event) { if (!shared.ROUTE_EVENTS[event]) { return middleware } var spec = { route: event, type: event === 'onPreResponse' ? shim.ERRORWARE : shim.MIDDLEWARE, next: function wrapNext(shim, fn, fnName, args, wrap) { var reply = args[1] if (!reply || !shim.isFunction(reply.continue)) return wrap(reply, 'continue') }, req: function getReq(shim, fn, fnName, args) { var request = args[0] if (request && request.raw) { return request.raw.req } } } return shim.recordMiddleware(middleware, spec) } function _isPluginClass(Plugin) { if (typeof Plugin !== 'function' || !Plugin.prototype) { return false } var proto = Plugin.prototype return ( typeof proto.handler === 'function' && typeof proto.route === 'function' && typeof proto.ext === 'function' ) } function wrapReply(wrap, reply, isPreHandler) { var isFinal = !isPreHandler // The only reply method that is actually used by pre-handlers is `response`. All // other methods still exist, but don't function as they do in normal route handlers. // Since they can still be referenced by users, they still need to be wrapped. wrap(reply, 'continue', isFinal) wrap(reply, 'redirect', isFinal) wrap(reply, 'close', isFinal) wrap(reply, 'response', isFinal) }