/** * Applies logic to the inner {@link AsyncHandler} that makes it conform to the semantics * specified in HTTP regarding when to return response bodies, Content-Length headers, etc. */ public static AsyncHandler<Response<ByteString>> httpPayloadSemantics( AsyncHandler<Response<ByteString>> inner) { return inner.flatMapSync(resp -> ctx -> applyHttpPayloadSemantics(ctx.request(), resp)); }
@Override public EndpointRunnableFactory apply(EndpointRunnableFactory delegate) { return (request, requestContext, endpoint) -> { final String endpointName = endpoint.info().getName(); // note: will not time duration of matching and dispatching final RequestMetrics requestStats = metrics.metricsForEndpointCall(endpointName); requestStats.incoming(request.request()); final TrackedOngoingRequest trackedRequest = new MetricsTrackingOngoingRequest(requestStats, request); final Client instrumentingClient = new InstrumentingClient(requestContext.requestScopedClient(), trackedRequest); final RequestContext instrumentingContext = RequestContexts.create( requestContext.request(), instrumentingClient, requestContext.pathArgs(), requestContext.arrivalTimeNanos(), requestContext.metadata()); return delegate.create(trackedRequest, instrumentingContext, endpoint); }; } }
private CompletionStage<Response<ByteString>> haltBackfill(String id, RequestContext rc, RequestAuthenticator authenticator) { final AuthContext authContext = authenticator.authenticate(rc.request()); try { // TODO: run in transction final Optional<Backfill> backfillOptional = storage.backfill(id); if (backfillOptional.isPresent()) { final Backfill backfill = backfillOptional.get(); workflowActionAuthorizer.authorizeWorkflowAction(authContext, backfill.workflowId()); storage.storeBackfill(backfill.builder().halted(true).build()); return haltActiveBackfillInstances(backfill, rc.requestScopedClient()); } else { return CompletableFuture.completedFuture( Response.forStatus(Status.NOT_FOUND.withReasonPhrase("backfill not found"))); } } catch (IOException e) { return CompletableFuture.completedFuture(Response.forStatus( Status.INTERNAL_SERVER_ERROR .withReasonPhrase("could not halt backfill: " + e.getMessage()))); } }
private static String arg(String name, RequestContext rc) { return rc.pathArgs().get(name); } }
private static String arg(String name, RequestContext rc) { return rc.pathArgs().get(name); }
/** * Middleware that applies the supplied serializer to the result of the inner handler, * changing the payload and optionally the Content-Type header. * * This middleware is type-unsafe, and it might be better to write your own middleware that does * serialization. */ public static Middleware<AsyncHandler<?>, AsyncHandler<Response<ByteString>>> serialize( Serializer serializer) { return inner -> inner .map(Middlewares::ensureResponse) .flatMapSync(resp -> ctx -> serializePayload(serializer, ctx.request(), resp)); }
public Stream<Route<AsyncHandler<Response<ByteString>>>> routes() { final List<Route<AsyncHandler<Response<ByteString>>>> schedulerProxies = Arrays.asList( Route.async( "GET", BASE + "/<endpoint:path>", rc -> proxyToScheduler("/" + rc.pathArgs().get("endpoint"), rc)), Route.async( "POST", BASE + "/<endpoint:path>", rc -> proxyToScheduler("/" + rc.pathArgs().get("endpoint"), rc)), Route.async( "DELETE", BASE + "/<endpoint:path>", rc -> proxyToScheduler("/" + rc.pathArgs().get("endpoint"), rc)), Route.async( "PATCH", BASE + "/<endpoint:path>", rc -> proxyToScheduler("/" + rc.pathArgs().get("endpoint"), rc)), Route.async( "PUT", BASE + "/<endpoint:path>", rc -> proxyToScheduler("/" + rc.pathArgs().get("endpoint"), rc)) ); return Api.prefixRoutes(schedulerProxies, V3); }
private <E> Either<Response<?>, E> deserialize(RequestContext rc, Class<? extends E> entityClass) { final Optional<ByteString> payloadOpt = rc.request().payload(); if (!payloadOpt.isPresent()) { return Either.left(Response.forStatus( Status.BAD_REQUEST .withReasonPhrase("Missing payload"))); } final E entity; try { entity = codec.read(payloadOpt.get(), entityClass, rc); } catch (Throwable e) { LOG.warn("error", e); return Either.left(Response.forStatus( Status.BAD_REQUEST .withReasonPhrase("Payload parsing failed: " + e.getMessage()))); } return Either.right(entity); }
public Stream<Route<AsyncHandler<Response<ByteString>>>> routes() { final EntityMiddleware em = EntityMiddleware.forCodec(JacksonEntityCodec.forMapper(Json.OBJECT_MAPPER)); final List<Route<AsyncHandler<Response<ByteString>>>> routes = Stream.of( Route.with( em.serializerDirect(ResourcesPayload.class), "GET", BASE, rc -> getResources()), Route.with( em.direct(Resource.class), "POST", BASE, rc -> this::postResource), Route.with( em.serializerResponse(Resource.class), "GET", BASE + "/<rid>", rc -> getResource(rc.pathArgs().get("rid"))), Route.with( em.serializerResponse(Void.class), "DELETE", BASE + "/<rid>", rc -> deleteResource(rc.pathArgs().get("rid"))), Route.with( em.response(Resource.class), "PUT", BASE + "/<rid>", rc -> payload -> updateResource(rc.pathArgs().get("rid"), payload)) ) .map(r -> r.withMiddleware(Middleware::syncToAsync)) .collect(toList()); return Api.prefixRoutes(routes, V3); }
private static AuthContext auth(RequestContext requestContext, RequestAuthenticator authenticator) { return authenticator.authenticate(requestContext.request()); }
em.serializerResponse(BackfillPayload.class), "GET", BASE + "/<bid>", rc -> getBackfill(rc, rc.pathArgs().get("bid"))), Route.with( authedEntity(authenticator, em.response(EditableBackfillInput.class, Backfill.class)), "PUT", BASE + "/<bid>", ac -> rc -> payload -> updateBackfill(ac, rc.pathArgs().get("bid"), payload)) Route.async( "DELETE", BASE + "/<bid>", rc -> haltBackfill(rc.pathArgs().get("bid"), rc, authenticator)) );
private RunStateDataPayload activeStates(RequestContext requestContext) { final Optional<String> componentOpt = requestContext.request().parameter("component"); final List<RunStateData> runStates = Lists.newArrayList(); try { final Map<WorkflowInstance, RunState> activeStates = componentOpt.isPresent() ? storage.readActiveStates(componentOpt.get()) : storage.readActiveStates(); runStates.addAll( activeStates.values().stream().map(this::runStateToRunStateData).collect(toList())); } catch (IOException e) { throw new RuntimeException(e); } return RunStateDataPayload.create(runStates); }
public static <T> Middleware<AsyncHandler<Response<T>>, AsyncHandler<Response<T>>> clientValidator( Supplier<List<String>> supplier) { return innerHandler -> requestContext -> { if (requestContext.request().header("User-Agent") // TODO: should the blacklist be a set so this lookup is O(1) instead of O(n) ? .map(header -> supplier.get().contains(header)) .orElse(false)) { // TODO: fire some stats return completedFuture(Response.forStatus(Status.NOT_ACCEPTABLE.withReasonPhrase( "blacklisted client version, please upgrade"))); } else { return innerHandler.invoke(requestContext); } }; }
private CompletionStage<Response<ByteString>> proxyToScheduler(String path, RequestContext rc) { final HttpUrl.Builder builder = Objects.requireNonNull(HttpUrl.parse(schedulerServiceBaseUrl + SCHEDULER_BASE_PATH + path)) .newBuilder(); ImmutableSortedMap.copyOf(rc.request().parameters()).forEach((name, values) -> values.forEach(value -> builder.addQueryParameter(name, value))); return client.send(withRequestId(rc.request().withUri(builder.build().toString()))); }
private BackfillsPayload getBackfills(RequestContext rc) { final Optional<String> componentOpt = rc.request().parameter("component"); final Optional<String> workflowOpt = rc.request().parameter("workflow"); final boolean includeStatuses = rc.request().parameter("status").orElse("false").equals("true"); final boolean showAll = rc.request().parameter("showAll").orElse("false").equals("true");
public static <T> Middleware<AsyncHandler<Response<T>>, AsyncHandler<Response<T>>> authenticator( RequestAuthenticator authenticator) { return h -> rc -> { final Optional<GoogleIdToken> idToken = auth(rc, authenticator).user(); if (!"GET".equals(rc.request().method()) && !idToken.isPresent()) { return completedFuture( Response.forStatus(Status.UNAUTHORIZED.withReasonPhrase("Unauthorized access"))); } return h.invoke(rc); }; } }
private Response<BackfillPayload> getBackfill(RequestContext rc, String id) { final boolean includeStatuses = rc.request().parameter("status").orElse("true").equals("true"); final Optional<Backfill> backfillOpt; try { backfillOpt = storage.backfill(id); } catch (IOException e) { final String message = String.format("Couldn't read backfill %s. ", id); log.warn(message, e); return Response.forStatus(Status.INTERNAL_SERVER_ERROR.withReasonPhrase("Error in internal storage")); } if (!backfillOpt.isPresent()) { return Response.forStatus(Status.NOT_FOUND); } final Backfill backfill = backfillOpt.get(); if (includeStatuses) { final List<RunStateData> statuses = retrieveBackfillStatuses(backfill); return Response.forPayload(BackfillPayload.create( backfill, Optional.of(RunStateDataPayload.create(statuses)))); } else { return Response.forPayload(BackfillPayload.create(backfill, Optional.empty())); } }
public static <T> Middleware<AsyncHandler<Response<T>>, AsyncHandler<Response<T>>> httpLogger( Logger log, RequestAuthenticator authenticator) { return innerHandler -> requestContext -> { final Request request = requestContext.request(); log.info("{}{} {} by {} with headers {} parameters {} and payload {}", "GET".equals(request.method()) ? "" : "[AUDIT] ", request.method(), request.uri(), // TODO: pass in auth context instead of authenticating twice auth(requestContext, authenticator).user().map(idToken -> idToken.getPayload() .getEmail()) .orElse("anonymous"), hideSensitiveHeaders(request.headers()), request.parameters(), request.payload().map(ByteString::utf8).orElse("") .replaceAll("\n", " ")); return innerHandler.invoke(requestContext); }; }
private Optional<String> validate(RequestContext rc, BackfillInput input, Workflow workflow) { if (!workflow.configuration().dockerImage().isPresent()) { return Optional.of("Workflow is missing docker image"); } final Collection<String> errors = workflowValidator.validateWorkflow(workflow); if (!errors.isEmpty()) { return Optional.of("Invalid workflow configuration: " + String.join(", ", errors)); } final Schedule schedule = workflow.configuration().schedule(); if (!input.start().isBefore(input.end())) { return Optional.of("start must be before end"); } if (!TimeUtil.isAligned(input.start(), schedule)) { return Optional.of("start parameter not aligned with schedule"); } if (!TimeUtil.isAligned(input.end(), schedule)) { return Optional.of("end parameter not aligned with schedule"); } final boolean allowFuture = Boolean.parseBoolean(rc.request().parameter("allowFuture").orElse("false")); if (!allowFuture && (input.start().isAfter(time.get()) || TimeUtil.previousInstant(input.end(), schedule).isAfter(time.get()))) { return Optional.of("Cannot backfill future partitions"); } return Optional.empty(); }
private <E> Either<Response<?>, E> deserialize(RequestContext rc, Class<? extends E> entityClass) { final Optional<ByteString> payloadOpt = rc.request().payload(); if (!payloadOpt.isPresent()) { return Either.left(Response.forStatus( Status.BAD_REQUEST .withReasonPhrase("Missing payload"))); } final E entity; try { entity = codec.read(payloadOpt.get(), entityClass, rc); } catch (Throwable e) { LOG.warn("error", e); return Either.left(Response.forStatus( Status.BAD_REQUEST .withReasonPhrase("Payload parsing failed: " + e.getMessage()))); } return Either.right(entity); }