Optional<com.spotify.apollo.Request> apolloIncomingRequest) { final Optional<RequestBody> requestBody = apolloRequest.payload().map(payload -> { final MediaType contentType = apolloRequest.header("Content-Type") .map(MediaType::parse) .orElse(DEFAULT_CONTENT_TYPE); apolloRequest.headerEntries().forEach((e) -> headersBuilder.add(e.getKey(), e.getValue())); .flatMap(req -> req.header(AUTHORIZATION_HEADER)) .ifPresent(header -> headersBuilder.add(AUTHORIZATION_HEADER, header)); .method(apolloRequest.method(), requestBody.orElse(null)) .url(apolloRequest.uri()) .headers(headersBuilder.build()) .build(); if (apolloRequest.ttl().isPresent() && client.getReadTimeout() != apolloRequest.ttl().get().toMillis()) { finalClient = client.clone(); finalClient.setReadTimeout(apolloRequest.ttl().get().toMillis(), TimeUnit.MILLISECONDS);
@Override public void gatherIncomingCall(OngoingRequest ongoingRequest, Endpoint endpoint) { Request message = ongoingRequest.request(); String fromService = message.service().orElse(null); String method = message.method(); EndpointInfo info = endpoint.info(); String endpointMethodName = info.getJavaMethodName(); CallsGatherer callsGatherer = gatherer.getIncomingCallsGatherer(fromService); EndpointGatherer endpointGatherer = callsGatherer.namedEndpointGatherer(endpointMethodName); endpointGatherer.setUri(info.getUri()); endpointGatherer.addMethod(method); for (String name : message.parameters().keySet()) { endpointGatherer.addQueryParameterName(name); } } }
@VisibleForTesting Request asApolloRequest(HttpServletRequest req) throws IOException { final String uri = req.getRequestURI() + (req.getQueryString() == null ? "" : "?" + req.getQueryString()); final String method = req.getMethod(); final int contentLength = req.getContentLength(); final ImmutableMap.Builder<String, String> headersBuilder = ImmutableMap.builder(); toStream(req.getHeaderNames()) .forEachOrdered( name -> headersBuilder.put( name, toStream(req.getHeaders(name)).collect(Collectors.joining(",")) )); final ImmutableMap<String, String> headers = headersBuilder.build(); Request request = Request.forUri(uri, method).withHeaders(headers); final Optional<String> callingService = request.header("X-Calling-Service"); if (callingService.isPresent() && !callingService.get().isEmpty()) { request = request.withService(callingService.get()); } Optional<ByteString> payload = readPayload(req, contentLength); if (payload.isPresent()) { request = request.withPayload(payload.get()); } return request; }
@Override public CompletionStage<Response<ByteString>> send(Request request, Optional<Request> incoming) { final Request withService = !request.service().isPresent() ? request.withService(serviceName) : request; return delegate.send(withService, incoming); } }
/** * Makes a call on the given uri. The uri can be an application relative path such as * {@code "/ping"} or a full path like {@link "http://<service-name>/ping"}. * * @param method The method of the call * @param uri The uri of the call * @param payload A payload body * @return A future of the response */ public CompletionStage<Response<ByteString>> request(String method, URI uri, ByteString payload) { final String uriString = addSchemaAuthForRelative(uri.toString()); return request(Request.forUri(uriString, method).withPayload(payload)); }
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); }; }
@Override protected String featureValueOf(Request actual) { return actual.uri(); } };
match = applicationRouter.match(request); } catch (InvalidUriException e) { LOG.warn("bad uri {} {} {}", request.method(), request.uri(), BAD_REQUEST, e); ongoingRequest.reply(forStatus(BAD_REQUEST)); return; Collection<String> methods = applicationRouter.getMethodsForValidRules(request); if (methods.isEmpty()) { LOG.warn("not found {} {} {}", request.method(), request.uri(), NOT_FOUND); ongoingRequest.reply(forStatus(NOT_FOUND)); } else { StatusType statusCode; if ("OPTIONS".equals(request.method())) { statusCode = NO_CONTENT; } else { statusCode = METHOD_NOT_ALLOWED; LOG.warn("wrong method {} {} {}", request.method(), request.uri(), statusCode);
@Override public void incoming(Request request) { requestSizeHistogram .ifPresent(histogram -> request.payload() .ifPresent(payload -> histogram.update(payload.size()))); }
@Override protected String featureValueOf(Request actual) { return actual.method(); } };
@Override public Payload serialize(Request message, Object o) { final ObjectMapper usedMapper = message.parameter("origins").isPresent() ? OBJECT_MAPPER : MAPPER; try { return Payloads.create( ByteString.encodeUtf8(usedMapper.writeValueAsString(o)), "application/json"); } catch (JsonProcessingException e) { throw Throwables.propagate(e); } }
/** * A uri query parameter of the request message, or empty if no parameter with that name is found. * Returns the first query parameter value if it is repeated. Use {@link #parameters()} to get * all repeated values. */ default Optional<String> parameter(String parameter) { List<String> values = parameters().get(parameter); if (values != null) { return Optional.ofNullable(values.get(0)); } else { return Optional.empty(); } }
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()))); }
/** * Makes a call on the given uri. The uri can be an application relative path such as * {@code "/ping"} or a full path like {@link "http://<service-name>/ping"}. * * @param method The method of the call * @param uri The uri of the call * @return A future of the response */ public CompletionStage<Response<ByteString>> request(String method, URI uri) { final String uriString = addSchemaAuthForRelative(uri.toString()); return request(Request.forUri(uriString, method)); }
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); } }; }
/** * A header of the request message, looked up in a case insensitive way, * or empty if no header with that name is found. */ default Optional<String> header(String name) { Objects.requireNonNull(name, "Header names cannot be null"); for (Map.Entry<String, String> headerEntry : headerEntries()) { if (name.equalsIgnoreCase(headerEntry.getKey())) { return Optional.ofNullable(headerEntry.getValue()); } } return Optional.empty(); }
@Override public Optional<RuleMatch<T>> match(Request message) throws InvalidUriException { final String method = message.method(); final String path = getPath(message); if (method == null) { LOG.warn("Invalid request for {} sent without method by service {}", message.uri(), message.service().orElse("<unknown>")); throw new InvalidUriException(); } if (path == null) { // Problem already logged in detail upstream throw new InvalidUriException(); } final Router.Result<Rule<T>> result = router.result(); router.route(method, path, result); if (!result.isSuccess()) { return Optional.empty(); } final Rule<T> rule = result.target(); final ImmutableMap.Builder<String, String> pathArgs = ImmutableMap.builder(); for (int i = 0; i < result.params(); i++) { pathArgs.put(result.paramName(i), readParameterValue(result, i)); } return Optional.of(new RuleMatch<T>(rule, pathArgs.build())); }
@Override protected boolean matchesSafely(Request request) { return uri.equals(request.uri()); }
public static <T> Middleware<AsyncHandler<Response<T>>, AsyncHandler<Response<T>>> tracer( Tracer tracer, String service) { return innerHandler -> requestContext -> { final Request request = requestContext.request(); final URI uri = URI.create(request.uri()); final Span span = tracer.spanBuilder(service + '/' + uri.getPath()).startSpan(); span.putAttribute("method", stringAttributeValue(request.method())); span.putAttribute("uri", stringAttributeValue(request.uri())); final CompletionStage<Response<T>> response; try { response = innerHandler.invoke(requestContext); } catch (Exception e) { span.setStatus(UNKNOWN); span.end(); Throwables.throwIfUnchecked(e); throw new RuntimeException(e); } response.whenComplete((r, ex) -> { if (ex != null) { span.setStatus(UNKNOWN); } span.end(); }); return response; }; }