/**
 * Copyright 2018, OpenCensus Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ import { Func , HeaderGetter , HeaderSetter , MessageEventType , Span , SpanKind , TraceOptions , } from '@opencensus/core' ; import { HttpPlugin } from '@opencensus/instrumentation-http' ; import * as http2 from 'http2' ; import * as shimmer from 'shimmer' ; import * as url from 'url' ; export type Http2Module = typeof http2 ; export type ConnectFunction = typeof http2 . connect ; type RequestFunction = ( this : http2 . ClientHttp2Session , headers ? : http2 . OutgoingHttpHeaders , options ? : http2 . ClientSessionRequestOptions ) => http2 . ClientHttp2Stream ; export type CreateServerFunction = typeof http2 . createServer ; /** Http2 instrumentation plugin for Opencensus */ export class Http2Plugin extends HttpPlugin { /** Constructs a new Http2Plugin instance. */ constructor ( ) { super ( 'http2' ) ; } /**
     * Patches HTTP2 incoming and outcoming request functions.
     */ protected applyPatch ( ) { shimmer . wrap ( this . moduleExports , 'createServer' , this . getPatchCreateServerFunction ( ) ) ; shimmer . wrap ( this . moduleExports , 'createSecureServer' , this . getPatchCreateServerFunction ( ) ) ; shimmer . wrap ( this . moduleExports , 'connect' , this . getPatchConnectFunction ( ) ) ; return this . moduleExports ; } /** Unpatches all HTTP2 patched function. */ protected applyUnpatch ( ) { shimmer . unwrap ( this . moduleExports , 'createServer' ) ; shimmer . unwrap ( this . moduleExports , 'createSecureServer' ) ; shimmer . unwrap ( this . moduleExports , 'connect' ) ; } private getPatchConnectFunction ( ) { const plugin = this ; return ( original ) : Func < http2 . ClientHttp2Session > => { return function patchedConnect ( this , authority ) : http2 . ClientHttp2Session { const client = original . apply ( this , arguments ) ; shimmer . wrap ( client , 'request' , original => plugin . getPatchRequestFunction ( ) ( original , authority ) ) ; return client ; } ; } ; } private getPatchRequestFunction ( ) { const plugin = this ; return ( original , authority ) : Func < http2 . ClientHttp2Stream > => { return function patchedRequest ( this , headers ) : http2 . ClientHttp2Stream { if ( headers [ 'x-opencensus-outgoing-request' ] ) { return original . apply ( this , arguments ) ; } const request = original . apply ( this , arguments ) ; plugin . tracer . wrapEmitter ( request ) ; const traceOptions = { name : `${ headers [ ':method' ] || 'GET' } ${ headers [ ':path' ] }` , kind : SpanKind . CLIENT , } ; if ( ! plugin . tracer . currentRootSpan ) { return plugin . tracer . startRootSpan ( traceOptions , plugin . getMakeHttp2RequestTraceFunction ( request , headers , authority , plugin ) ) ; } else { const span = plugin . tracer . startChildSpan ( { name : traceOptions . name , kind : traceOptions . kind , } ) ; return plugin . getMakeHttp2RequestTraceFunction ( request , headers , authority , plugin ) ( span ) ; } } ; } ; } private getMakeHttp2RequestTraceFunction ( request , headers , authority , plugin ) { return ( span ) : http2 . ClientHttp2Stream => { if ( ! span ) return request ; const setter = { setHeader ( name , value ) { headers [ name ] = value ; } , } ; const propagation = plugin . tracer . propagation ; if ( propagation ) { propagation . inject ( setter , span . spanContext ) ; } request . on ( 'response' , ( responseHeaders ) => { const statusCode = responseHeaders [ ':status' ] || 200 ; span . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_STATUS_CODE , `${ statusCode }` ) ; span . setStatus ( Http2Plugin . parseResponseStatus ( + statusCode ) ) ; } ) ; request . on ( 'end' , ( ) => { const userAgent = headers [ 'user-agent' ] || headers [ 'User-Agent' ] || null ; const host = ( authority instanceof url . URL ? authority : url . parse ( authority ) ) . host ; if ( host ) { span . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_HOST , host ) ; } span . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_METHOD , `${ headers [ ':method' ] }` ) ; span . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_PATH , `${ headers [ ':path' ] }` ) ; span . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_ROUTE , `${ headers [ ':path' ] }` ) ; if ( userAgent ) { span . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_USER_AGENT , `${ userAgent }` ) ; } span . addMessageEvent ( MessageEventType . SENT , 1 ) ; span . end ( ) ; } ) ; request . on ( 'error' , ( err ) => { span . addAttribute ( HttpPlugin . ATTRIBUTE_HTTP_ERROR_NAME , err . name ) ; span . addAttribute ( HttpPlugin . ATTRIBUTE_HTTP_ERROR_MESSAGE , err . message ) ; span . end ( ) ; } ) ; return request ; } ; } private getPatchCreateServerFunction ( ) { const plugin = this ; return ( original ) : Func < http2 . Http2Server > => { return function patchedCreateServer ( this ) : http2 . Http2Server { const server = original . apply ( this , arguments ) ; shimmer . wrap ( server . constructor . prototype , 'emit' , plugin . getPatchEmitFunction ( ) ) ; shimmer . unwrap ( plugin . moduleExports , 'createServer' ) ; shimmer . unwrap ( plugin . moduleExports , 'createSecureServer' ) ; return server ; } ; } ; } private getPatchEmitFunction ( ) { const plugin = this ; return ( original ) : Func < http2 . ClientHttp2Stream > => { return function patchedEmit ( this , event , stream , headers ) : http2 . ClientHttp2Stream { if ( event !== 'stream' ) { return original . apply ( this , arguments ) ; } const propagation = plugin . tracer . propagation ; const getter = { getHeader ( name ) { return headers [ name ] ; } , } ; const traceOptions = { name : headers [ ':path' ] || '' , kind : SpanKind . SERVER , } ; if ( propagation ) { const spanContext = propagation . extract ( getter ) ; if ( spanContext ) { traceOptions . spanContext = spanContext ; } } let statusCode ; const originalRespond = stream . respond ; stream . respond = function ( this ) { stream . respond = originalRespond ; statusCode = arguments [ 0 ] [ ':status' ] ; return stream . respond . apply ( this , arguments ) ; } ; return plugin . tracer . startRootSpan ( traceOptions , rootSpan => { if ( ! rootSpan ) return original . apply ( this , arguments ) ; plugin . tracer . wrapEmitter ( stream ) ; const originalEnd = stream . end ; stream . end = function ( this ) { stream . end = originalEnd ; const returned = stream . end . apply ( this , arguments ) ; const userAgent = ( headers [ 'user-agent' ] || headers [ 'User-Agent' ] || null ) as string ; rootSpan . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_HOST , `${ headers [ ':authority' ] }` ) ; rootSpan . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_METHOD , `${ headers [ ':method' ] }` ) ; rootSpan . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_PATH , `${ headers [ ':path' ] }` ) ; rootSpan . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_ROUTE , `${ headers [ ':path' ] }` ) ; rootSpan . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_USER_AGENT , userAgent ) ; rootSpan . addAttribute ( Http2Plugin . ATTRIBUTE_HTTP_STATUS_CODE , `${ statusCode }` ) ; rootSpan . setStatus ( Http2Plugin . parseResponseStatus ( statusCode ) ) ; rootSpan . addMessageEvent ( MessageEventType . RECEIVED , 1 ) ; rootSpan . end ( ) ; return returned ; } ; return original . apply ( this , arguments ) ; } ) ; } ; } ; } } const plugin = new Http2Plugin ( ) ; export { plugin } ;