Search in sources :

Example 1 with TracingHandler

use of org.eclipse.hono.service.http.TracingHandler in project hono by eclipse.

the class AbstractVertxBasedHttpProtocolAdapter method doUploadMessage.

private void doUploadMessage(final HttpContext ctx, final String tenant, final String deviceId, final Buffer payload, final String contentType, final MetricsTags.EndpointType endpoint) {
    if (!ctx.hasValidQoS()) {
        HttpUtils.badRequest(ctx.getRoutingContext(), "unsupported QoS-Level header value");
        return;
    }
    if (!isPayloadOfIndicatedType(payload, contentType)) {
        HttpUtils.badRequest(ctx.getRoutingContext(), String.format("content type [%s] does not match payload", contentType));
        return;
    }
    final MetricsTags.QoS qos = getQoSLevel(endpoint, ctx.getRequestedQos());
    final Device authenticatedDevice = ctx.getAuthenticatedDevice();
    final String gatewayId = authenticatedDevice != null && !deviceId.equals(authenticatedDevice.getDeviceId()) ? authenticatedDevice.getDeviceId() : null;
    final Span currentSpan = TracingHelper.buildChildSpan(tracer, TracingHandler.serverSpanContext(ctx.getRoutingContext()), "upload " + endpoint.getCanonicalName(), getTypeName()).withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).withTag(TracingHelper.TAG_TENANT_ID, tenant).withTag(TracingHelper.TAG_DEVICE_ID, deviceId).withTag(TracingHelper.TAG_AUTHENTICATED.getKey(), authenticatedDevice != null).withTag(TracingHelper.TAG_QOS, qos.name()).start();
    final Promise<Void> responseReady = Promise.promise();
    final Future<RegistrationAssertion> tokenTracker = getRegistrationAssertion(tenant, deviceId, authenticatedDevice, currentSpan.context());
    final int payloadSize = Optional.ofNullable(payload).map(ok -> payload.length()).orElse(0);
    final Future<TenantObject> tenantTracker = getTenantConfiguration(tenant, currentSpan.context());
    final Future<TenantObject> tenantValidationTracker = tenantTracker.compose(tenantObject -> CompositeFuture.all(isAdapterEnabled(tenantObject), checkMessageLimit(tenantObject, payloadSize, currentSpan.context())).map(success -> tenantObject));
    // we only need to consider TTD if the device and tenant are enabled and the adapter
    // is enabled for the tenant
    final Future<Integer> ttdTracker = CompositeFuture.all(tenantValidationTracker, tokenTracker).compose(ok -> {
        final Integer ttdParam = getTimeUntilDisconnectFromRequest(ctx);
        return getTimeUntilDisconnect(tenantTracker.result(), ttdParam).map(effectiveTtd -> {
            if (effectiveTtd != null) {
                currentSpan.setTag(MessageHelper.APP_PROPERTY_DEVICE_TTD, effectiveTtd);
            }
            return effectiveTtd;
        });
    });
    final Future<CommandConsumer> commandConsumerTracker = ttdTracker.compose(ttd -> createCommandConsumer(ttd, tenantTracker.result(), deviceId, gatewayId, ctx.getRoutingContext(), responseReady, currentSpan));
    commandConsumerTracker.compose(ok -> {
        final Map<String, Object> props = getDownstreamMessageProperties(ctx);
        Optional.ofNullable(commandConsumerTracker.result()).map(c -> ttdTracker.result()).ifPresent(ttd -> props.put(MessageHelper.APP_PROPERTY_DEVICE_TTD, ttd));
        props.put(MessageHelper.APP_PROPERTY_QOS, ctx.getRequestedQos().ordinal());
        customizeDownstreamMessageProperties(props, ctx);
        setTtdRequestConnectionCloseHandler(ctx.getRoutingContext(), commandConsumerTracker.result(), tenant, deviceId, currentSpan);
        if (EndpointType.EVENT.equals(endpoint)) {
            ctx.getTimeToLive().ifPresent(ttl -> props.put(MessageHelper.SYS_HEADER_PROPERTY_TTL, ttl.toSeconds()));
            return CompositeFuture.all(getEventSender(tenantValidationTracker.result()).sendEvent(tenantTracker.result(), tokenTracker.result(), contentType, payload, props, currentSpan.context()), responseReady.future()).map(s -> (Void) null);
        } else {
            // unsettled
            return CompositeFuture.all(getTelemetrySender(tenantValidationTracker.result()).sendTelemetry(tenantTracker.result(), tokenTracker.result(), ctx.getRequestedQos(), contentType, payload, props, currentSpan.context()), responseReady.future()).map(s -> (Void) null);
        }
    }).compose(proceed -> {
        // request and the CommandConsumer from the current request has not been closed yet
        return Optional.ofNullable(commandConsumerTracker.result()).map(consumer -> consumer.close(currentSpan.context()).otherwise(thr -> {
            TracingHelper.logError(currentSpan, thr);
            return (Void) null;
        })).orElseGet(Future::succeededFuture);
    }).map(proceed -> {
        if (ctx.response().closed()) {
            log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]: response already closed", endpoint, tenant, deviceId);
            TracingHelper.logError(currentSpan, "failed to send HTTP response to device: response already closed");
            currentSpan.finish();
            // close the response here, ensuring that the TracingHandler bodyEndHandler gets called
            ctx.response().end();
        } else {
            final CommandContext commandContext = ctx.get(CommandContext.KEY_COMMAND_CONTEXT);
            setResponsePayload(ctx.response(), commandContext, currentSpan);
            ctx.getRoutingContext().addBodyEndHandler(ok -> {
                log.trace("successfully processed [{}] message for device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId);
                if (commandContext != null) {
                    commandContext.getTracingSpan().log("forwarded command to device in HTTP response body");
                    commandContext.accept();
                    metrics.reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), ProcessingOutcome.FORWARDED, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
                }
                metrics.reportTelemetry(endpoint, tenant, tenantTracker.result(), ProcessingOutcome.FORWARDED, qos, payloadSize, ctx.getTtdStatus(), getMicrometerSample(ctx.getRoutingContext()));
                currentSpan.finish();
            });
            ctx.response().exceptionHandler(t -> {
                log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId, t);
                if (commandContext != null) {
                    TracingHelper.logError(commandContext.getTracingSpan(), "failed to forward command to device in HTTP response body", t);
                    commandContext.release(t);
                    metrics.reportCommand(commandContext.getCommand().isOneWay() ? Direction.ONE_WAY : Direction.REQUEST, tenant, tenantTracker.result(), ProcessingOutcome.UNDELIVERABLE, commandContext.getCommand().getPayloadSize(), getMicrometerSample(commandContext));
                }
                currentSpan.log("failed to send HTTP response to device");
                TracingHelper.logError(currentSpan, t);
                currentSpan.finish();
            });
            ctx.response().end();
        }
        return proceed;
    }).recover(t -> {
        log.debug("cannot process [{}] message from device [tenantId: {}, deviceId: {}]", endpoint, tenant, deviceId, t);
        final boolean responseClosedPrematurely = ctx.response().closed();
        final Future<Void> commandConsumerClosedTracker = Optional.ofNullable(commandConsumerTracker.result()).map(consumer -> consumer.close(currentSpan.context()).onFailure(thr -> TracingHelper.logError(currentSpan, thr))).orElseGet(Future::succeededFuture);
        final CommandContext commandContext = ctx.get(CommandContext.KEY_COMMAND_CONTEXT);
        if (commandContext != null) {
            TracingHelper.logError(commandContext.getTracingSpan(), "command won't be forwarded to device in HTTP response body, HTTP request handling failed", t);
            commandContext.release(t);
            currentSpan.log("released command for device");
        }
        final ProcessingOutcome outcome;
        if (ClientErrorException.class.isInstance(t)) {
            outcome = ProcessingOutcome.UNPROCESSABLE;
            ctx.fail(t);
        } else {
            outcome = ProcessingOutcome.UNDELIVERABLE;
            final String errorMessage = t instanceof ServerErrorException ? ((ServerErrorException) t).getClientFacingMessage() : null;
            HttpUtils.serviceUnavailable(ctx.getRoutingContext(), 2, Strings.isNullOrEmpty(errorMessage) ? "temporarily unavailable" : errorMessage);
        }
        if (responseClosedPrematurely) {
            log.debug("failed to send http response for [{}] message from device [tenantId: {}, deviceId: {}]: response already closed", endpoint, tenant, deviceId);
            TracingHelper.logError(currentSpan, "failed to send HTTP response to device: response already closed");
        }
        metrics.reportTelemetry(endpoint, tenant, tenantTracker.result(), outcome, qos, payloadSize, ctx.getTtdStatus(), getMicrometerSample(ctx.getRoutingContext()));
        TracingHelper.logError(currentSpan, t);
        commandConsumerClosedTracker.onComplete(res -> currentSpan.finish());
        return Future.failedFuture(t);
    });
}
Also used : HttpURLConnection(java.net.HttpURLConnection) HttpServer(io.vertx.core.http.HttpServer) Router(io.vertx.ext.web.Router) RoutingContext(io.vertx.ext.web.RoutingContext) BodyHandler(io.vertx.ext.web.handler.BodyHandler) Tags(io.opentracing.tag.Tags) ProcessingOutcome(org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome) EndpointType(org.eclipse.hono.service.metric.MetricsTags.EndpointType) TtdStatus(org.eclipse.hono.service.metric.MetricsTags.TtdStatus) DeviceCredentials(org.eclipse.hono.adapter.auth.device.DeviceCredentials) References(io.opentracing.References) Duration(java.time.Duration) Map(java.util.Map) WebSpanDecorator(org.eclipse.hono.service.http.WebSpanDecorator) TracingHelper(org.eclipse.hono.tracing.TracingHelper) CommandContext(org.eclipse.hono.client.command.CommandContext) MetricsTags(org.eclipse.hono.service.metric.MetricsTags) RegistrationAssertion(org.eclipse.hono.util.RegistrationAssertion) MessageHelper(org.eclipse.hono.util.MessageHelper) Future(io.vertx.core.Future) Device(org.eclipse.hono.auth.Device) Objects(java.util.Objects) List(java.util.List) ComponentMetaDataDecorator(org.eclipse.hono.service.http.ComponentMetaDataDecorator) TenantTraceSamplingHelper(org.eclipse.hono.tracing.TenantTraceSamplingHelper) Buffer(io.vertx.core.buffer.Buffer) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) HttpServerResponse(io.vertx.core.http.HttpServerResponse) DefaultFailureHandler(org.eclipse.hono.service.http.DefaultFailureHandler) Optional(java.util.Optional) Span(io.opentracing.Span) HttpContext(org.eclipse.hono.service.http.HttpContext) Command(org.eclipse.hono.client.command.Command) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) HashMap(java.util.HashMap) ClientErrorException(org.eclipse.hono.client.ClientErrorException) Constants(org.eclipse.hono.util.Constants) ArrayList(java.util.ArrayList) TracingHandler(org.eclipse.hono.service.http.TracingHandler) CompositeFuture(io.vertx.core.CompositeFuture) HttpUtils(org.eclipse.hono.service.http.HttpUtils) AsyncResult(io.vertx.core.AsyncResult) Strings(org.eclipse.hono.util.Strings) Route(io.vertx.ext.web.Route) CredentialsApiAuthProvider(org.eclipse.hono.adapter.auth.device.CredentialsApiAuthProvider) AbstractProtocolAdapterBase(org.eclipse.hono.adapter.AbstractProtocolAdapterBase) Direction(org.eclipse.hono.service.metric.MetricsTags.Direction) Promise(io.vertx.core.Promise) ServerErrorException(org.eclipse.hono.client.ServerErrorException) Sample(io.micrometer.core.instrument.Timer.Sample) CommandResponse(org.eclipse.hono.client.command.CommandResponse) TenantObject(org.eclipse.hono.util.TenantObject) SpanContext(io.opentracing.SpanContext) HttpServerOptions(io.vertx.core.http.HttpServerOptions) NoopSpan(io.opentracing.noop.NoopSpan) Handler(io.vertx.core.Handler) CommandContext(org.eclipse.hono.client.command.CommandContext) Device(org.eclipse.hono.auth.Device) Span(io.opentracing.Span) NoopSpan(io.opentracing.noop.NoopSpan) ProcessingOutcome(org.eclipse.hono.service.metric.MetricsTags.ProcessingOutcome) TenantObject(org.eclipse.hono.util.TenantObject) RegistrationAssertion(org.eclipse.hono.util.RegistrationAssertion) CommandConsumer(org.eclipse.hono.client.command.CommandConsumer) MetricsTags(org.eclipse.hono.service.metric.MetricsTags) Future(io.vertx.core.Future) CompositeFuture(io.vertx.core.CompositeFuture) ServerErrorException(org.eclipse.hono.client.ServerErrorException) Map(java.util.Map) HashMap(java.util.HashMap)

Example 2 with TracingHandler

use of org.eclipse.hono.service.http.TracingHandler in project hono by eclipse.

the class AbstractVertxBasedHttpProtocolAdapter method createTracingHandler.

private TracingHandler createTracingHandler() {
    final Map<String, String> customTags = new HashMap<>();
    customTags.put(Tags.COMPONENT.getKey(), getTypeName());
    addCustomTags(customTags);
    final List<WebSpanDecorator> decorators = new ArrayList<>();
    decorators.add(new ComponentMetaDataDecorator(customTags));
    addCustomSpanDecorators(decorators);
    return new TracingHandler(tracer, decorators);
}
Also used : WebSpanDecorator(org.eclipse.hono.service.http.WebSpanDecorator) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) ComponentMetaDataDecorator(org.eclipse.hono.service.http.ComponentMetaDataDecorator) TracingHandler(org.eclipse.hono.service.http.TracingHandler)

Example 3 with TracingHandler

use of org.eclipse.hono.service.http.TracingHandler in project hono by eclipse.

the class AbstractVertxBasedHttpProtocolAdapter method createRouter.

/**
 * Creates the router for handling requests.
 * <p>
 * This method creates a router instance along with a route matching all request. That route is initialized with the
 * following handlers and failure handlers:
 * <ol>
 * <li>a handler to add a Micrometer {@code Timer.Sample} to the routing context,</li>
 * <li>a handler and failure handler that creates tracing data for all server requests,</li>
 * <li>a handler to log when the connection is closed prematurely,</li>
 * <li>a default failure handler,</li>
 * <li>a handler limiting the body size of requests to the maximum payload size set in the <em>config</em>
 * properties.</li>
 * </ol>
 *
 * @return The newly created router (never {@code null}).
 */
protected Router createRouter() {
    final Router router = Router.router(vertx);
    final Route matchAllRoute = router.route();
    // the handlers and failure handlers are added here in a specific order!
    // 1. handler to start the metrics timer
    matchAllRoute.handler(ctx -> {
        ctx.put(KEY_MICROMETER_SAMPLE, getMetrics().startTimer());
        ctx.next();
    });
    // 2. tracing handler
    final TracingHandler tracingHandler = createTracingHandler();
    matchAllRoute.handler(tracingHandler).failureHandler(tracingHandler);
    // 3. handler to log when the connection is closed prematurely
    matchAllRoute.handler(ctx -> {
        if (!ctx.response().closed() && !ctx.response().ended()) {
            ctx.response().closeHandler(v -> logResponseGettingClosedPrematurely(ctx));
        }
        ctx.next();
    });
    // 4. default handler for failed routes
    matchAllRoute.failureHandler(new DefaultFailureHandler());
    // 5. BodyHandler with request size limit
    log.info("limiting size of inbound request body to {} bytes", getConfig().getMaxPayloadSize());
    final BodyHandler bodyHandler = BodyHandler.create(DEFAULT_UPLOADS_DIRECTORY).setBodyLimit(getConfig().getMaxPayloadSize());
    matchAllRoute.handler(bodyHandler);
    return router;
}
Also used : BodyHandler(io.vertx.ext.web.handler.BodyHandler) DefaultFailureHandler(org.eclipse.hono.service.http.DefaultFailureHandler) Router(io.vertx.ext.web.Router) TracingHandler(org.eclipse.hono.service.http.TracingHandler) Route(io.vertx.ext.web.Route)

Aggregations

Route (io.vertx.ext.web.Route)2 Router (io.vertx.ext.web.Router)2 BodyHandler (io.vertx.ext.web.handler.BodyHandler)2 ArrayList (java.util.ArrayList)2 HashMap (java.util.HashMap)2 ComponentMetaDataDecorator (org.eclipse.hono.service.http.ComponentMetaDataDecorator)2 DefaultFailureHandler (org.eclipse.hono.service.http.DefaultFailureHandler)2 TracingHandler (org.eclipse.hono.service.http.TracingHandler)2 Sample (io.micrometer.core.instrument.Timer.Sample)1 References (io.opentracing.References)1 Span (io.opentracing.Span)1 SpanContext (io.opentracing.SpanContext)1 NoopSpan (io.opentracing.noop.NoopSpan)1 Tags (io.opentracing.tag.Tags)1 AsyncResult (io.vertx.core.AsyncResult)1 CompositeFuture (io.vertx.core.CompositeFuture)1 Future (io.vertx.core.Future)1 Handler (io.vertx.core.Handler)1 Promise (io.vertx.core.Promise)1 Buffer (io.vertx.core.buffer.Buffer)1