All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rabbitmq.http.client.ReactorNettyClient Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2018-2022 the original author or 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
 *
 *      https://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.
 */

package com.rabbitmq.http.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.rabbitmq.http.client.domain.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import org.reactivestreams.Publisher;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.ByteBufFlux;
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.client.HttpClientResponse;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.rabbitmq.http.client.PercentEncoder.encodeParameter;
import static com.rabbitmq.http.client.PercentEncoder.encodePathSegment;

/**
 * Reactive client based on Reactor Netty.
 * Use the {@link ReactorNettyClientOptions} constructors for
 * advanced settings, e.g. TLS, authentication other than HTTP basic, etc.
 * The default settings for this class are the following:
 * 
    *
  • {@link HttpClient}: created with the {@link HttpClient#baseUrl(String)}. *
  • *
  • * {@link ObjectMapper}: DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES and * MapperFeature.DEFAULT_VIEW_INCLUSION are disabled. * {@link JsonUtils#CURRENT_USER_DETAILS_DESERIALIZER_INSTANCE}, {@link JsonUtils#USER_INFO_DESERIALIZER_INSTANCE}, * {@link JsonUtils#VHOST_LIMITS_DESERIALIZER_INSTANCE}, * and {@link JsonUtils#CHANNEL_DETAILS_DESERIALIZER_INSTANCE} set up. *
  • *
  • Mono<String> token: basic HTTP authentication used for the * authorization header. *
  • *
  • BiConsumer<? super HttpRequest, ? super HttpResponse> responseCallback: * 4xx and 5xx responses on GET requests throw {@link HttpClientException} and {@link HttpServerException} * respectively. *
  • *
* * @see ReactorNettyClientOptions * @since 2.1.0 */ public class ReactorNettyClient { private static final Consumer JSON_HEADER = headers -> headers.set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON); private final ObjectMapper objectMapper; private final HttpClient client; private final Mono token; private final Supplier byteBufSupplier; private final Consumer responseCallback; public ReactorNettyClient(String url, ReactorNettyClientOptions options) { this(Utils.urlWithoutCredentials(url), Utils.extractUsernamePassword(url)[0], Utils.extractUsernamePassword(url)[1], options); } public ReactorNettyClient(String url) { this(url, new ReactorNettyClientOptions()); } public ReactorNettyClient(String url, String username, String password) { this(url, username, password, new ReactorNettyClientOptions()); } public ReactorNettyClient(String url, String username, String password, ReactorNettyClientOptions options) { objectMapper = options.objectMapper() == null ? createDefaultObjectMapper() : options.objectMapper().get(); client = options.client() == null ? HttpClient.create().baseUrl(url) : options.client().get(); this.token = options.token() == null ? createBasicAuthenticationToken(username, password) : options.token(); if (options.onResponseCallback() == null) { this.responseCallback = response -> { if (response.method() == HttpMethod.GET) { if (response.status().code() >= 500) { throw new HttpServerException(response.status().code(), response.status().reasonPhrase()); } else if (response.status().code() >= 400) { throw new HttpClientException(response.status().code(), response.status().reasonPhrase()); } } }; } else { this.responseCallback = response -> options.onResponseCallback().accept(new HttpEndpoint(response.uri(), response.method().name()), toHttpResponse(response)); } this.byteBufSupplier = options.byteBufSupplier() == null ? () -> PooledByteBufAllocator.DEFAULT.buffer() : options.byteBufSupplier(); } private static HttpResponse toHttpResponse(HttpClientResponse response) { Map headers = new LinkedHashMap<>(); for (Map.Entry headerEntry : response.responseHeaders().entries()) { headers.put(headerEntry.getKey(), headerEntry.getValue()); } return new HttpResponse(response.status().code(), response.status().reasonPhrase(), headers); } public static ObjectMapper createDefaultObjectMapper() { return JsonUtils.createDefaultObjectMapper(); } public static Mono createBasicAuthenticationToken(String username, String password) { return Mono.fromSupplier(() -> basicAuthentication(username, password)).cache(); } public static String basicAuthentication(String username, String password) { String credentials = username + ":" + password; byte[] credentialsAsBytes = credentials.getBytes(StandardCharsets.ISO_8859_1); byte[] encodedBytes = Base64.getEncoder().encode(credentialsAsBytes); String encodedCredentials = new String(encodedBytes, StandardCharsets.ISO_8859_1); return "Basic " + encodedCredentials; } public Mono getOverview() { return doGetMono(OverviewResponse.class, "overview"); } public Flux getNodes() { return doGetFlux(NodeInfo.class, "nodes"); } public Mono getNode(String name) { return doGetMono(NodeInfo.class, "nodes", encodePathSegment(name)); } public Flux getConnections() { return doGetFlux(ConnectionInfo.class, "connections"); } public Mono getConnection(String name) { return doGetMono(ConnectionInfo.class, "connections", encodePathSegment(name)); } public Mono closeConnection(String name) { return doDelete("connections", encodePathSegment(name)); } public Mono closeConnection(String name, String reason) { return doDelete(headers -> headers.set("X-Reason", reason), "connections", encodePathSegment(name)); } public Flux getConsumers() { return doGetFlux(ConsumerDetails.class, "consumers"); } public Flux getConsumers(String vhost) { return doGetFlux(ConsumerDetails.class, "consumers", encodePathSegment(vhost)); } public Mono declarePolicy(String vhost, String name, PolicyInfo info) { return doPut(info, "policies", encodePathSegment(vhost), encodePathSegment(name)); } public Mono declareOperatorPolicy(String vhost, String name, PolicyInfo info) { return doPut(info, "operator-policies", encodePathSegment(vhost), encodePathSegment(name)); } public Flux getPolicies() { return doGetFlux(PolicyInfo.class, "policies"); } public Flux getPolicies(String vhost) { return doGetFlux(PolicyInfo.class, "policies", encodePathSegment(vhost)); } public Flux getOperatorPolicies() { return doGetFlux(PolicyInfo.class, "operator-policies"); } public Flux getOperatorPolicies(String vhost) { return doGetFlux(PolicyInfo.class, "operator-policies", encodePathSegment(vhost)); } public Mono deletePolicy(String vhost, String name) { return doDelete("policies", encodePathSegment(vhost), encodePathSegment(name)); } public Mono deleteOperatorPolicy(String vhost, String name) { return doDelete("operator-policies", encodePathSegment(vhost), encodePathSegment(name)); } public Flux getChannels() { return doGetFlux(ChannelInfo.class, "channels"); } public Flux getChannels(String connectionName) { return doGetFlux(ChannelInfo.class, "connections", encodePathSegment(connectionName), "channels"); } public Mono getChannel(String name) { return doGetMono(ChannelInfo.class, "channels", encodePathSegment(name)); } public Flux getVhosts() { return doGetFlux(VhostInfo.class, "vhosts"); } public Mono getVhost(String name) { return doGetMono(VhostInfo.class, "vhosts", encodePathSegment(name)); } /** * Create a virtual host with name, tracing flag, and metadata. * Note metadata (description and tags) are supported as of RabbitMQ 3.8. * * @param name name of the virtual host * @param tracing whether tracing is enabled or not * @param description virtual host description (requires RabbitMQ 3.8 or more) * @param tags virtual host tags (requires RabbitMQ 3.8 or more) * @return response wrapped in {@link Mono} * @since 3.4.0 */ public Mono createVhost(String name, boolean tracing, String description, String... tags) { Map body = new HashMap(); body.put("tracing", tracing); if (description != null && !description.isEmpty()) { body.put("description", description); } if (tags != null && tags.length > 0) { body.put("tags", String.join(",", tags)); } return doPut(body, "vhosts", encodePathSegment(name)); } /** * Create a virtual host with name and metadata. * Note metadata (description and tags) are supported as of RabbitMQ 3.8. * * @param name name of the virtual host * @param description virtual host description (requires RabbitMQ 3.8 or more) * @param tags virtual host tags (requires RabbitMQ 3.8 or more) * @return response wrapped in {@link Mono} * @since 3.4.0 */ public Mono createVhost(String name, String description, String... tags) { return createVhost(name, false, description, tags); } /** * Create a virtual host with name and tracing flag. * * @param name name of the virtual host * @param tracing whether tracing is enabled or not * @return response wrapped in {@link Mono} * @since 3.4.0 */ public Mono createVhost(String name, boolean tracing) { return createVhost(name, tracing, null); } public Mono createVhost(String name) { return doPut("vhosts", encodePathSegment(name)); } public Mono deleteVhost(String name) { return doDelete("vhosts", encodePathSegment(name)); } public Flux getPermissionsIn(String vhost) { return doGetFlux(UserPermissions.class, "vhosts", encodePathSegment(vhost), "permissions"); } public Mono updatePermissions(String vhost, String username, UserPermissions permissions) { return doPut(permissions, "permissions", encodePathSegment(vhost), encodePathSegment(username)); } public Flux getTopicPermissionsIn(String vhost) { return doGetFlux(TopicPermissions.class, "vhosts", encodePathSegment(vhost), "topic-permissions"); } public Mono updateTopicPermissions(String vhost, String username, TopicPermissions permissions) { return doPut(permissions, "topic-permissions", encodePathSegment(vhost), encodePathSegment(username)); } public Flux getUsers() { return doGetFlux(UserInfo.class, "users"); } public Mono getUser(String username) { return doGetMono(UserInfo.class, "users", encodePathSegment(username)); } public Mono deleteUser(String username) { return doDelete("users", encodePathSegment(username)); } public Mono createUser(String username, char[] password, List tags) { if (username == null) { throw new IllegalArgumentException("username cannot be null"); } if (password == null) { throw new IllegalArgumentException("password cannot be null or empty. If you need to create a user that " + "will only authenticate using an x509 certificate, use createUserWithPasswordHash with a blank hash."); } Map body = new HashMap(); body.put("password", new String(password)); if (tags == null || tags.isEmpty()) { body.put("tags", ""); } else { body.put("tags", String.join(",", tags)); } return doPut(body, "users", encodePathSegment(username)); } public Mono updateUser(String username, char[] password, List tags) { if (username == null) { throw new IllegalArgumentException("username cannot be null"); } Map body = new HashMap(); // only update password if provided if (password != null) { body.put("password", new String(password)); } if (tags == null || tags.isEmpty()) { body.put("tags", ""); } else { body.put("tags", String.join(",", tags)); } return doPut(body, "users", encodePathSegment(username)); } public Flux getPermissionsOf(String username) { return doGetFlux(UserPermissions.class, "users", encodePathSegment(username), "permissions"); } public Flux getTopicPermissionsOf(String username) { return doGetFlux(TopicPermissions.class, "users", encodePathSegment(username), "topic-permissions"); } public Mono createUserWithPasswordHash(String username, char[] passwordHash, List tags) { if (username == null) { throw new IllegalArgumentException("username cannot be null"); } // passwordless authentication is a thing. See // https://github.com/rabbitmq/hop/issues/94 and https://www.rabbitmq.com/authentication.html. MK. if (passwordHash == null) { passwordHash = "".toCharArray(); } Map body = new HashMap(); body.put("password_hash", String.valueOf(passwordHash)); if (tags == null || tags.isEmpty()) { body.put("tags", ""); } else { body.put("tags", String.join(",", tags)); } return doPut(body, "users", encodePathSegment(username)); } public Mono whoAmI() { return doGetMono(CurrentUserDetails.class, "whoami"); } public Flux getPermissions() { return doGetFlux(UserPermissions.class, "permissions"); } public Mono getPermissions(String vhost, String username) { return doGetMono(UserPermissions.class, "permissions", encodePathSegment(vhost), encodePathSegment(username)); } public Mono clearPermissions(String vhost, String username) { return doDelete("permissions", encodePathSegment(vhost), encodePathSegment(username)); } public Flux getTopicPermissions() { return doGetFlux(TopicPermissions.class, "topic-permissions"); } public Flux getTopicPermissions(String vhost, String username) { return doGetFlux(TopicPermissions.class, "topic-permissions", encodePathSegment(vhost), encodePathSegment(username)); } public Mono clearTopicPermissions(String vhost, String username) { return doDelete("topic-permissions", encodePathSegment(vhost), encodePathSegment(username)); } public Flux getExchanges() { return doGetFlux(ExchangeInfo.class, "exchanges"); } public Flux getExchanges(String vhost) { return doGetFlux(ExchangeInfo.class, "exchanges", encodePathSegment(vhost)); } public Mono getExchange(String vhost, String name) { return doGetMono(ExchangeInfo.class, "exchanges", encodePathSegment(vhost), encodePathSegment(name)); } public Mono declareExchange(String vhost, String name, ExchangeInfo info) { return doPut(info, "exchanges", encodePathSegment(vhost), encodePathSegment(name)); } public Mono deleteExchange(String vhost, String name) { return doDelete("exchanges", encodePathSegment(vhost), encodePathSegment(name)); } /** * Publishes a message to an exchange. *

* DO NOT USE THIS METHOD IN PRODUCTION. The HTTP API has to create a new TCP * connection for each message published, which is highly suboptimal. *

* Use this method for test or development code only. * In production, use AMQP 0-9-1 or any other messaging protocol that uses a long-lived connection. * * @param vhost the virtual host to use * @param exchange the target exchange * @param routingKey the routing key to use * @param outboundMessage the message to publish * @return true if message has been routed to at least a queue, false otherwise * @since 3.4.0 */ public Mono publish(String vhost, String exchange, String routingKey, OutboundMessage outboundMessage) { if (vhost == null || vhost.isEmpty()) { throw new IllegalArgumentException("vhost cannot be null or blank"); } if (exchange == null || exchange.isEmpty()) { throw new IllegalArgumentException("exchange cannot be null or blank"); } Map body = Utils.bodyForPublish(routingKey, outboundMessage); return doPostMono(body, Map.class, "exchanges", encodePathSegment(vhost), encodePathSegment(exchange), "publish").map(response -> { Boolean routed = (Boolean) response.get("routed"); if (routed == null) { return Boolean.FALSE; } else { return routed; } }); } public Mono alivenessTest(String vhost) { return doGetMono(AlivenessTestResult.class, "aliveness-test", encodePathSegment(vhost)); } public Mono getClusterName() { return doGetMono(ClusterId.class, "cluster-name"); } public Mono setClusterName(String name) { if (name == null || name.isEmpty()) { throw new IllegalArgumentException("name cannot be null or blank"); } return doPut(Collections.singletonMap("name", name), "cluster-name"); } @SuppressWarnings({"unchecked", "rawtypes"}) public Flux getExtensions() { return doGetFlux(Map.class, "extensions"); } public Mono getDefinitions() { return doGetMono(Definitions.class, "definitions"); } public Flux getQueues() { return getQueues((DetailsParameters) null); } public Flux getQueues(DetailsParameters detailsParameters) { return doGetFlux(QueueInfo.class, detailsParameters == null ? Collections.emptyMap() : detailsParameters.parameters(), "queues"); } public Flux getQueues(String vhost) { return getQueues(vhost, null); } public Flux getQueues(String vhost, DetailsParameters detailsParameters) { return doGetFlux(QueueInfo.class, detailsParameters == null ? Collections.emptyMap() : detailsParameters.parameters(), "queues", encodePathSegment(vhost)); } public Mono getQueue(String vhost, String name, DetailsParameters detailsParameters) { return doGetMono(QueueInfo.class, detailsParameters == null ? Collections.emptyMap() : detailsParameters.parameters(), "queues", encodePathSegment(vhost), encodePathSegment(name)); } public Mono getQueue(String vhost, String name) { return this.getQueue(vhost, name, null); } public Mono declareQueue(String vhost, String name, QueueInfo info) { return doPut(info, "queues", encodePathSegment(vhost), encodePathSegment(name)); } public Mono purgeQueue(String vhost, String name) { return doDelete("queues", encodePathSegment(vhost), encodePathSegment(name), "contents"); } public Mono deleteQueue(String vhost, String name) { return doDelete("queues", encodePathSegment(vhost), encodePathSegment(name)); } public Mono deleteQueue(String vhost, String name, DeleteQueueParameters parameters) { return doDelete(headers -> { }, parameters.getAsQueryParams(), "queues", encodePathSegment(vhost), encodePathSegment(name)); } /** * Get messages from a queue. * * DO NOT USE THIS METHOD IN PRODUCTION. Getting messages with the HTTP API * is intended for diagnostics or tests. It does not implement reliable delivery * and so should be treated as a sysadmin's tool rather than a general API for messaging. * * @param vhost the virtual host the target queue is in * @param queue the queue to consume from * @param count the maximum number of messages to get * @param ackMode determines whether the messages will be removed from the queue * @param encoding the expected encoding of the message payload * @param truncate to truncate the message payload if it is larger than the size given (in bytes), -1 means no truncation * @return the messages wrapped in a {@link Flux} * @see GetAckMode * @see GetEncoding * @since 3.4.0 */ public Flux get(String vhost, String queue, int count, GetAckMode ackMode, GetEncoding encoding, int truncate) { if (vhost == null || vhost.isEmpty()) { throw new IllegalArgumentException("vhost cannot be null or blank"); } if (queue == null || queue.isEmpty()) { throw new IllegalArgumentException("queue cannot be null or blank"); } Map body = Utils.bodyForGet(count, ackMode, encoding, truncate); return doPostFlux(body, InboundMessage.class, "queues", encodePathSegment(vhost), encodePathSegment(queue), "get"); } /** * Get messages from a queue, with no limit for message payload truncation. * * DO NOT USE THIS METHOD IN PRODUCTION. Getting messages with the HTTP API * is intended for diagnostics or tests. It does not implement reliable delivery * and so should be treated as a sysadmin's tool rather than a general API for messaging. * * @param vhost the virtual host the target queue is in * @param queue the queue to consume from * @param count the maximum number of messages to get * @param ackMode determines whether the messages will be removed from the queue * @param encoding the expected encoding of the message payload * @return the messages wrapped in a {@link Flux} * @see GetAckMode * @see GetEncoding * @since 3.4.0 */ public Flux get(String vhost, String queue, int count, GetAckMode ackMode, GetEncoding encoding) { return get(vhost, queue, count, ackMode, encoding, -1); } /** * Get one message from a queue and requeue it. * * DO NOT USE THIS METHOD IN PRODUCTION. Getting messages with the HTTP API * is intended for diagnostics or tests. It does not implement reliable delivery * and so should be treated as a sysadmin's tool rather than a general API for messaging. * * @param vhost the virtual host the target queue is in * @param queue the queue to consume from * @return the message wrapped in a {@link Mono} * @see GetAckMode * @see GetEncoding * @since 3.4.0 */ public Mono get(String vhost, String queue) { return get(vhost, queue, 1, GetAckMode.NACK_REQUEUE_TRUE, GetEncoding.AUTO, 50000).last(); } public Flux getBindings() { return doGetFlux(BindingInfo.class, "bindings"); } public Flux getBindings(String vhost) { return doGetFlux(BindingInfo.class, "bindings", encodePathSegment(vhost)); } public Flux getExchangeBindingsBySource(String vhost, String exchange) { final String x = exchange.equals("") ? "amq.default" : exchange; return doGetFlux(BindingInfo.class, "exchanges", encodePathSegment(vhost), encodePathSegment(x), "bindings", "source"); } public Flux getExchangeBindingsByDestination(String vhost, String exchange) { final String x = exchange.equals("") ? "amq.default" : exchange; return doGetFlux(BindingInfo.class, "exchanges", encodePathSegment(vhost), encodePathSegment(x), "bindings", "destination"); } public Flux getQueueBindings(String vhost, String queue) { return doGetFlux(BindingInfo.class, "queues", encodePathSegment(vhost), encodePathSegment(queue), "bindings"); } public Flux getQueueBindingsBetween(String vhost, String exchange, String queue) { return doGetFlux(BindingInfo.class, "bindings", encodePathSegment(vhost), "e", encodePathSegment(exchange), "q", encodePathSegment(queue)); } public Flux getExchangeBindingsBetween(String vhost, String source, String destination) { return doGetFlux(BindingInfo.class, "bindings", encodePathSegment(vhost), "e", encodePathSegment(source), "e", encodePathSegment(destination)); } public Mono bindExchange(String vhost, String destination, String source, String routingKey) { return bindExchange(vhost, destination, source, routingKey, new HashMap<>()); } public Mono bindExchange(String vhost, String destination, String source, String routingKey, Map args) { if (vhost == null || vhost.isEmpty()) { throw new IllegalArgumentException("vhost cannot be null or blank"); } if (destination == null || destination.isEmpty()) { throw new IllegalArgumentException("destination cannot be null or blank"); } if (source == null || source.isEmpty()) { throw new IllegalArgumentException("source cannot be null or blank"); } Map body = new HashMap(); if (!(args == null)) { body.put("arguments", args); } body.put("routing_key", routingKey); return doPost(body, "bindings", encodePathSegment(vhost), "e", encodePathSegment(source), "e", encodePathSegment(destination)); } public Mono bindQueue(String vhost, String queue, String exchange, String routingKey) { return bindQueue(vhost, queue, exchange, routingKey, new HashMap<>()); } public Mono bindQueue(String vhost, String queue, String exchange, String routingKey, Map args) { if (vhost == null || vhost.isEmpty()) { throw new IllegalArgumentException("vhost cannot be null or blank"); } if (queue == null || queue.isEmpty()) { throw new IllegalArgumentException("queue cannot be null or blank"); } if (exchange == null || exchange.isEmpty()) { throw new IllegalArgumentException("exchange cannot be null or blank"); } Map body = new HashMap(); if (!(args == null)) { body.put("arguments", args); } body.put("routing_key", routingKey); return doPost(body, "bindings", encodePathSegment(vhost), "e", encodePathSegment(exchange), "q", encodePathSegment(queue)); } public Mono declareShovel(String vhost, ShovelInfo info) { Map props = info.getDetails().getPublishProperties(); if (props != null && props.isEmpty()) { throw new IllegalArgumentException("Shovel publish properties must be a non-empty map or null"); } return doPut(info, "parameters", "shovel", encodePathSegment(vhost), encodePathSegment(info.getName())); } public Flux getShovels() { return doGetFlux(ShovelInfo.class, "parameters", "shovel"); } public Flux getShovelsStatus() { return doGetFlux(ShovelStatus.class, "shovels"); } public Mono deleteShovel(String vhost, String shovelName) { return doDelete("parameters", "shovel", encodePathSegment(vhost), encodePathSegment(shovelName)); } // // Federation support // /** * Declares an upstream * * @param vhost virtual host for which to declare the upstream * @param name name of the upstream to declare * @param details upstream arguments * @return HTTP response in a mono */ public Mono declareUpstream(String vhost, String name, UpstreamDetails details) { if (isEmpty(details.getUri())) { throw new IllegalArgumentException("Upstream uri must not be null or empty"); } UpstreamInfo body = new UpstreamInfo(); body.setVhost(vhost); body.setName(name); body.setValue(details); return doPut(body, "parameters", "federation-upstream", encodePathSegment(vhost), encodePathSegment(name)); } /** * Deletes an upstream * * @param vhost virtual host for which to delete the upstream * @param name name of the upstream to delete * @return HTTP response in a mono */ public Mono deleteUpstream(String vhost, String name) { return doDelete("parameters", "federation-upstream", encodePathSegment(vhost), encodePathSegment(name)); } /** * Returns a list of upstreams for "/" virtual host * * @return flux of upstream info */ public Flux getUpstreams() { return doGetFlux(UpstreamInfo.class, "parameters", "federation-upstream"); } /** * Returns a list of upstreams * * @param vhost virtual host the upstreams are in. * @return flux of upstream info */ public Flux getUpstreams(String vhost) { return doGetFlux(UpstreamInfo.class, "parameters", "federation-upstream", encodePathSegment(vhost)); } /** * Declares an upstream set. * * @param vhost virtual host for which to declare the upstream set * @param name name of the upstream set to declare * @param details upstream set arguments * @return HTTP response in a mono */ public Mono declareUpstreamSet(String vhost, String name, List details) { for (UpstreamSetDetails item : details) { if (isEmpty(item.getUpstream())) { throw new IllegalArgumentException("Each federation upstream set item must have a non-null and not " + "empty upstream name"); } } UpstreamSetInfo body = new UpstreamSetInfo(); body.setVhost(vhost); body.setName(name); body.setValue(details); return doPut(body, "parameters", "federation-upstream-set", encodePathSegment(vhost), encodePathSegment(name)); } /** * Deletes an upstream set * * @param vhost virtual host for which to delete the upstream set * @param name name of the upstream set to delete * @return HTTP response in a mono */ public Mono deleteUpstreamSet(String vhost, String name) { return doDelete("parameters", "federation-upstream-set", encodePathSegment(vhost), encodePathSegment(name)); } /** * Returns a list of upstream sets for "/" virtual host * * @return flux of upstream set info */ public Flux getUpstreamSets() { return doGetFlux(UpstreamSetInfo.class, "parameters", "federation-upstream-set"); } /** * Returns a list of upstream sets * * @param vhost Virtual host from where to get upstreams. * @return flux of upstream set info */ public Flux getUpstreamSets(String vhost) { return doGetFlux(UpstreamSetInfo.class, "parameters", "federation-upstream-set", encodePathSegment(vhost)); } public Mono getMqttPortToVhostMapping(){ return doGetMono(MqttVhostPortInfo.class, "global-parameters", "mqtt_port_to_vhost_mapping"); } public Mono deleteMqttPortToVhostMapping(){ return doDelete( "global-parameters", "mqtt_port_to_vhost_mapping"); } public Mono setMqttPortToVhostMapping(Map portMappings){ for (String vhost : portMappings.values()){ if (vhost.isBlank()) { throw new IllegalArgumentException("Map with undefined vhosts provided!"); } } MqttVhostPortInfo body = new MqttVhostPortInfo(); body.setValue(portMappings); return doPut(body, "global-parameters", "mqtt_port_to_vhost_mapping"); } /** * Returns the limits (max queues and connections) for all virtual hosts. * * @return flux of the limits * @since 3.7.0 */ public Flux getVhostLimits() { return doGetFlux(VhostLimits.class, "vhost-limits"); } /** * Returns the limits (max queues and connections) for a given virtual host. * * @param vhost the virtual host * @return flux of the limits for this virtual host * @since 3.7.0 */ public Mono getVhostLimits(String vhost) { return doGetMono(VhostLimits.class, "vhost-limits", encodePathSegment(vhost)) .map(limits -> limits.getVhost() == null ? new VhostLimits(vhost, -1, -1) : limits); } /** * Sets the max number (limit) of connections for a virtual host. * * @param vhost the virtual host * @param limit the max number of connections allowed * @return HTTP response in a mono * @since 3.7.0 */ public Mono limitMaxNumberOfConnections(String vhost, int limit) { return doPut(Collections.singletonMap("value", limit), "vhost-limits", encodePathSegment(vhost), "max-connections"); } /** * Sets the max number (limit) of queues for a virtual host. * * @param vhost the virtual host * @param limit the max number of queues allowed * @return HTTP response in a mono * @since 3.7.0 */ public Mono limitMaxNumberOfQueues(String vhost, int limit) { return doPut(Collections.singletonMap("value", limit), "vhost-limits", encodePathSegment(vhost), "max-queues"); } /** * Clears the connection limit for a virtual host. * * @param vhost the virtual host * @return HTTP response in a mono * @since 3.7.0 */ public Mono clearMaxConnectionsLimit(String vhost) { return doDelete("vhost-limits", encodePathSegment(vhost), "max-connections"); } /** * Clears the queue limit for a virtual host. * * @param vhost the virtual host * @return HTTP response in a mono * @since 3.7.0 */ public Mono clearMaxQueuesLimit(String vhost) { return doDelete("vhost-limits", encodePathSegment(vhost), "max-queues"); } private Mono doGetMono(Class type, String... pathSegments) { return doGetMono(type, null, pathSegments); } private Mono doGetMono(Class type, Map queryParameters, String... pathSegments) { String uri = uri(pathSegments); if (queryParameters != null && !queryParameters.isEmpty()) { uri += queryParameters.entrySet().stream() .map(e -> String.format("%s=%s", e.getKey(), encodeParameter(e.getValue()))) .collect(Collectors.joining("&", "?", "")); } return Mono.from(client .headersWhen(authorizedHeader()) .get() .uri(uri) .response(decode(type))); } protected BiFunction> decode(Class type) { return (response, byteBufFlux) -> { this.responseCallback.accept(response); if (response.status().code() == 404) { return Mono.empty(); } else { return byteBufFlux.aggregate().asInputStream().map(bytes -> deserialize(bytes, type)); } }; } private T deserialize(InputStream inputStream, Class type) { try { T value = objectMapper.readValue(inputStream, type); inputStream.close(); return value; } catch (IOException e) { throw Exceptions.propagate(e); } } private Flux doGetFlux(Class type, String... pathSegments) { return doGetFlux(type, null, pathSegments); } @SuppressWarnings("unchecked") private Flux doGetFlux(Class type, Map queryParameters, String... pathSegments) { return (Flux) doGetMono(Array.newInstance(type, 0).getClass(), queryParameters, pathSegments) .flatMapMany(items -> Flux.fromArray((Object[]) items)); } protected Function> authorizedHeader() { return headers -> token.map(t -> headers.set(HttpHeaderNames.AUTHORIZATION, t)); } private Mono doPost(Object body, String... pathSegments) { return client.headersWhen(authorizedHeader()) .headers(JSON_HEADER) .post() .uri(uri(pathSegments)) .send(bodyPublisher(body)) .response() .doOnNext(applyResponseCallback()) .map(ReactorNettyClient::toHttpResponse); } private Mono doPostMono(Object body, Class type, String... pathSegments) { return Mono.from(client.headersWhen(authorizedHeader()) .headers(JSON_HEADER) .post() .uri(uri(pathSegments)) .send(bodyPublisher(body)) .response(decode(type))); } @SuppressWarnings("unchecked") private Flux doPostFlux(Object body, Class type, String... pathSegments) { return (Flux) doPostMono(body, Array.newInstance(type, 0).getClass(), pathSegments).flatMapMany(items -> Flux.fromArray((Object[]) items)); } protected Consumer applyResponseCallback() { return response -> this.responseCallback.accept(response); } private Mono doPut(Object body, String... pathSegments) { return client.headersWhen(authorizedHeader()) .headers(JSON_HEADER) .put() .uri(uri(pathSegments)) .send(bodyPublisher(body)) .response() .doOnNext(applyResponseCallback()) .map(ReactorNettyClient::toHttpResponse); } private Mono bodyPublisher(Object body) { return Mono.fromCallable(() -> { ByteBuf byteBuf = this.byteBufSupplier.get(); ByteBufOutputStream byteBufOutputStream = new ByteBufOutputStream(byteBuf); objectMapper.writeValue((OutputStream) byteBufOutputStream, body); return byteBuf; }); } private Mono doPut(String... pathSegments) { return client.headersWhen(authorizedHeader()) .headers(JSON_HEADER) .put() .uri(uri(pathSegments)) .response() .doOnNext(applyResponseCallback()) .map(ReactorNettyClient::toHttpResponse); } private Mono doDelete(Consumer headerBuilder, Map queryParams, String... pathSegments) { String uri = uri(pathSegments); if (queryParams != null && !queryParams.isEmpty()) { uri += queryParams.entrySet().stream() .map(e -> String.format("%s=%s", e.getKey(), encodeParameter(e.getValue()))) .collect(Collectors.joining("&", "?", "")); } return client.headersWhen(authorizedHeader()) .headers(headerBuilder) .delete() .uri(uri) .response() .doOnNext(applyResponseCallback()) .map(ReactorNettyClient::toHttpResponse); } private Mono doDelete(Consumer headerBuilder, String... pathSegments) { return doDelete(headerBuilder, Collections.emptyMap(), pathSegments); } private Mono doDelete(String... pathSegments) { return doDelete(headers -> { }, pathSegments); } private static String uri(String... pathSegments) { return "/" + String.join("/", pathSegments); } private static boolean isEmpty(String str) { return (str == null || "".equals(str)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy