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

org.eclipse.jetty.reactive.client.ReactiveRequest Maven / Gradle / Ivy

/*
 * Copyright (c) 2017-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
 *
 *     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.
 */
package org.eclipse.jetty.reactive.client;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.function.BiFunction;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.reactive.client.internal.PublisherContent;
import org.eclipse.jetty.reactive.client.internal.PublisherRequestContent;
import org.eclipse.jetty.reactive.client.internal.RequestEventPublisher;
import org.eclipse.jetty.reactive.client.internal.ResponseEventPublisher;
import org.eclipse.jetty.reactive.client.internal.ResponseListenerProcessor;
import org.eclipse.jetty.reactive.client.internal.StringContent;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;

/**
 * 

A reactive wrapper over Jetty's {@code HttpClient} {@link Request}.

*

A ReactiveRequest can be obtained via a builder:

*
 * // Built with HttpClient and a string URI.
 * ReactiveRequest request = ReactiveRequest.newBuilder(httpClient, uri()).build();
 *
 * // Built by wrapping a Request.
 * Request req = httpClient.newRequest(...);
 * ...
 * ReactiveRequest request = ReactiveRequest.newBuilder(req).build();
 * 
*

Once created, a ReactiveRequest can be sent to obtain a {@link Publisher} * for a {@link ReactiveResponse} passing a function that handles the response * content:

*
 * Publisher<T> response = request.response((response, content) -> { ... });
 * 
*/ public class ReactiveRequest { /** * @param httpClient the HttpClient instance * @param uri the target URI for the request - must be properly encoded already * @return a builder for a GET request for the given URI */ public static ReactiveRequest.Builder newBuilder(HttpClient httpClient, String uri) { return new Builder(httpClient, uri); } /** * @param request the request instance * @return a builder for the given Request */ public static ReactiveRequest.Builder newBuilder(Request request) { return new Builder(request); } private final RequestEventPublisher requestEvents = new RequestEventPublisher(this); private final ResponseEventPublisher responseEvents = new ResponseEventPublisher(this); private final Request request; private final boolean abortOnCancel; private volatile ReactiveResponse response; protected ReactiveRequest(Request request) { this(request, false); } private ReactiveRequest(Request request, boolean abortOnCancel) { this.request = request.listener(requestEvents) .onResponseBegin(r -> { this.response = new ReactiveResponse(this, r); }) .onResponseBegin(responseEvents) .onResponseHeaders(responseEvents) .onResponseContentDemanded(responseEvents) .onResponseSuccess(responseEvents) .onResponseFailure(responseEvents) .onComplete(responseEvents); this.abortOnCancel = abortOnCancel; } /** * @return the ReactiveResponse correspondent to this request, * or null if the response is not available yet */ public ReactiveResponse getReactiveResponse() { return response; } /** * @return the wrapped Jetty request */ public Request getRequest() { return request; } /** *

Creates a Publisher that sends the request when a Subscriber requests the response * via {@link Subscription#request(long)}, discarding the response content.

* * @return a Publisher for the response */ public Publisher response() { return response(ReactiveResponse.Content.discard()); } /** *

Creates a Publisher that sends the request when a Subscriber requests the response * via {@link Subscription#request(long)}, processing the response content with the given * function.

*

Applications must subscribe (possibly asynchronously) to the response content Publisher, * even if it is known that the response has no content, to receive the response success/failure * events.

* * @param contentFn the function that processes the response content * @param the element type of the processed response content * @return a Publisher for the processed content */ public Publisher response(BiFunction, Publisher> contentFn) { return new ResponseListenerProcessor<>(this, contentFn, abortOnCancel); } /** * @return a Publisher for request events */ public Publisher requestEvents() { return requestEvents; } public Publisher responseEvents() { return responseEvents; } @Override public String toString() { return String.format("Reactive[%s]", request); } /** * A Builder for ReactiveRequest. */ public static class Builder { private final Request request; private boolean abortOnCancel; public Builder(HttpClient client, String uri) { this(client.newRequest(uri)); } public Builder(Request request) { this.request = request; } /** *

Provides the request content via a Publisher.

* * @param content the request content * @return this instance */ public Builder content(Content content) { request.body(new PublisherRequestContent(content)); return this; } /** * @param abortOnCancel whether a request should be aborted when the * content subscriber cancels the subscription * @return this instance */ public Builder abortOnCancel(boolean abortOnCancel) { this.abortOnCancel = abortOnCancel; return this; } /** * @return a built ReactiveRequest */ public ReactiveRequest build() { return new ReactiveRequest(request, abortOnCancel); } } /** * A ReactiveRequest event. */ public static class Event { private final Type type; private final ReactiveRequest request; private final ByteBuffer content; private final Throwable failure; public Event(Type type, ReactiveRequest request) { this(type, request, null, null); } public Event(Type type, ReactiveRequest request, ByteBuffer content) { this(type, request, content, null); } public Event(Type type, ReactiveRequest request, Throwable failure) { this(type, request, null, failure); } private Event(Type type, ReactiveRequest request, ByteBuffer content, Throwable failure) { this.type = type; this.request = request; this.content = content; this.failure = failure; } /** * @return the event type */ public Type getType() { return type; } /** * @return the request that generated this event */ public ReactiveRequest getRequest() { return request; } /** * @return the event content, or null if this is not a content event */ public ByteBuffer getContent() { return content; } /** * @return the event failure, or null if this is not a failure event */ public Throwable getFailure() { return failure; } /** * The event types */ public enum Type { /** * The request has been queued */ QUEUED, /** * The request is ready to be sent */ BEGIN, /** * The request headers have been prepared */ HEADERS, /** * The request headers have been sent */ COMMIT, /** * A chunk of content has been sent */ CONTENT, /** * The request succeeded */ SUCCESS, /** * The request failed */ FAILURE } } /** * A Publisher of content chunks that also specifies the content length and type. */ public static interface Content extends Publisher { /** * @return the content length */ public long getLength(); /** * @return the content type in the form {@code media_type[;charset=]} */ public String getContentType(); public static Content fromString(String string, String mediaType, Charset charset) { return new StringContent(string, mediaType, charset); } public static Content fromPublisher(Publisher publisher, String contentType) { return new PublisherContent(publisher, contentType); } public static Content fromPublisher(Publisher publisher, String mediaType, Charset charset) { return fromPublisher(publisher, mediaType + ";charset=" + charset.name()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy