Skip to content
This repository has been archived by the owner on Sep 26, 2023. It is now read-only.

feat: add REST interceptors infrastructure #1607

Merged
merged 4 commits into from
Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright 2022 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.api.gax.httpjson;

import com.google.api.core.BetaApi;
import javax.annotation.Nullable;

/**
* A {@link HttpJsonClientCall} which forwards all of its methods to another {@link
* HttpJsonClientCall}.
*/
@BetaApi
public abstract class ForwardingHttpJsonClientCall<RequestT, ResponseT>
extends HttpJsonClientCall<RequestT, ResponseT> {

protected abstract HttpJsonClientCall<RequestT, ResponseT> delegate();

@Override
public void start(Listener<ResponseT> responseListener, HttpJsonMetadata requestHeaders) {
delegate().start(responseListener, requestHeaders);
}

@Override
public void request(int numMessages) {
delegate().request(numMessages);
}

@Override
public void cancel(@Nullable String message, @Nullable Throwable cause) {
delegate().cancel(message, cause);
}

@Override
public void halfClose() {
delegate().halfClose();
}

@Override
public void sendMessage(RequestT message) {
delegate().sendMessage(message);
}

/**
* A simplified version of {@link ForwardingHttpJsonClientCall} where subclasses can pass in a
* {@link HttpJsonClientCall} as the delegate.
*/
public abstract static class SimpleForwardingHttpJsonClientCall<RequestT, ResponseT>
extends ForwardingHttpJsonClientCall<RequestT, ResponseT> {

private final HttpJsonClientCall<RequestT, ResponseT> delegate;

protected SimpleForwardingHttpJsonClientCall(HttpJsonClientCall<RequestT, ResponseT> delegate) {
this.delegate = delegate;
}

@Override
protected HttpJsonClientCall<RequestT, ResponseT> delegate() {
return delegate;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2022 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.google.api.gax.httpjson;

import com.google.api.core.BetaApi;

/**
* A {@link HttpJsonClientCall.Listener} which forwards all of its methods to another {@link
* HttpJsonClientCall.Listener}.
*/
@BetaApi
public abstract class ForwardingHttpJsonClientCallListener<ResponseT>
extends HttpJsonClientCall.Listener<ResponseT> {

protected abstract HttpJsonClientCall.Listener<ResponseT> delegate();

@Override
public void onHeaders(HttpJsonMetadata responseHeaders) {
delegate().onHeaders(responseHeaders);
}

@Override
public void onMessage(ResponseT message) {
delegate().onMessage(message);
}

@Override
public void onClose(int statusCode, HttpJsonMetadata trailers) {
delegate().onClose(statusCode, trailers);
}

/**
* A simplified version of {@link ForwardingHttpJsonClientCallListener} where subclasses can pass
* in a {@link HttpJsonClientCall.Listener} as the delegate.
*/
public abstract static class SimpleForwardingHttpJsonClientCallListener<ResponseT>
extends ForwardingHttpJsonClientCallListener<ResponseT> {

private final HttpJsonClientCall.Listener<ResponseT> delegate;

protected SimpleForwardingHttpJsonClientCallListener(
HttpJsonClientCall.Listener<ResponseT> delegate) {
this.delegate = delegate;
}

@Override
protected HttpJsonClientCall.Listener<ResponseT> delegate() {
return delegate;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,11 @@
*/
package com.google.api.gax.httpjson;

import com.google.api.core.ApiFuture;
import com.google.api.core.BetaApi;

/** HttpJsonChannel contains the functionality to issue http-json calls. */
@BetaApi
public interface HttpJsonChannel {
<RequestT, ResponseT> HttpJsonClientCall<RequestT, ResponseT> newCall(
ApiMethodDescriptor<RequestT, ResponseT> methodDescriptor, HttpJsonCallOptions callOptions);

@Deprecated
<ResponseT, RequestT> ApiFuture<ResponseT> issueFutureUnaryCall(
HttpJsonCallOptions callOptions,
RequestT request,
ApiMethodDescriptor<RequestT, ResponseT> methodDescriptor);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,19 @@
import com.google.api.gax.httpjson.HttpRequestRunnable.ResultListener;
import com.google.api.gax.httpjson.HttpRequestRunnable.RunnableResult;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;

/**
* This class servers as main implementation of {@link HttpJsonClientCall} for rest transport and is
* This class serves as main implementation of {@link HttpJsonClientCall} for REST transport and is
* expected to be used for every REST call. It currently supports unary and server-streaming
* workflows. The overall behavior and surface of the class mimics as close as possible behavior of
* the corresponding ClientCall implementation in gRPC transport.
Expand Down Expand Up @@ -90,7 +88,6 @@ final class HttpJsonClientCallImpl<RequestT, ResponseT>
private final ApiMethodDescriptor<RequestT, ResponseT> methodDescriptor;
private final HttpTransport httpTransport;
private final Executor executor;
private final HttpJsonMetadata defaultHeaders;

//
// Request-specific data (provided by client code) before we get a response.
Expand Down Expand Up @@ -124,15 +121,13 @@ final class HttpJsonClientCallImpl<RequestT, ResponseT>
String endpoint,
HttpJsonCallOptions callOptions,
HttpTransport httpTransport,
Executor executor,
HttpJsonMetadata defaultHeaders) {
Executor executor) {
this.methodDescriptor = methodDescriptor;
this.endpoint = endpoint;
this.callOptions = callOptions;
this.httpTransport = httpTransport;
this.executor = executor;
this.closed = false;
this.defaultHeaders = defaultHeaders;
}

@Override
Expand Down Expand Up @@ -164,12 +159,7 @@ public void start(Listener<ResponseT> responseListener, HttpJsonMetadata request
}
Preconditions.checkState(this.listener == null, "The call is already started");
this.listener = responseListener;
Map<String, Object> mergedHeaders =
ImmutableMap.<String, Object>builder()
.putAll(defaultHeaders.getHeaders())
.putAll(requestHeaders.getHeaders())
.build();
this.requestHeaders = requestHeaders.toBuilder().setHeaders(mergedHeaders).build();
this.requestHeaders = requestHeaders;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2022 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.api.gax.httpjson;

import com.google.api.core.BetaApi;

/**
* Interface for intercepting outgoing calls before they are dispatched by a {@link
* HttpJsonChannel}.
*
* <p>The interceptor may be called for multiple {@link HttpJsonClientCall calls} by one or more
* threads without completing the previous ones first. The implementations must be thread-safe.
*/
@BetaApi
public interface HttpJsonClientInterceptor {
/**
* Intercept {@link HttpJsonClientCall} creation by the {@code next} {@link HttpJsonChannel}.
*
* @param method the remote method to be called
* @param callOptions the runtime options to be applied to this call
* @param next the channel which is being intercepted
* @return the call object for the remote operation, never {@code null}
*/
<ReqT, RespT> HttpJsonClientCall<ReqT, RespT> interceptCall(
ApiMethodDescriptor<ReqT, RespT> method,
HttpJsonCallOptions callOptions,
HttpJsonChannel next);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2022 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.api.gax.httpjson;

import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall;
import com.google.common.collect.ImmutableMap;
import java.util.Map;

/**
* An interceptor to handle custom headers.
*
* <p>Package-private for internal usage.
*/
class HttpJsonHeaderInterceptor implements HttpJsonClientInterceptor {

private final Map<String, String> staticHeaders;

public HttpJsonHeaderInterceptor(Map<String, String> staticHeaders) {
this.staticHeaders = staticHeaders;
}

@Override
public <ReqT, RespT> HttpJsonClientCall<ReqT, RespT> interceptCall(
ApiMethodDescriptor<ReqT, RespT> method,
final HttpJsonCallOptions callOptions,
HttpJsonChannel next) {
HttpJsonClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
return new SimpleForwardingHttpJsonClientCall<ReqT, RespT>(call) {
@Override
public void start(
HttpJsonClientCall.Listener<RespT> responseListener, HttpJsonMetadata headers) {
Map<String, Object> mergedHeaders =
ImmutableMap.<String, Object>builder()
.putAll(headers.getHeaders())
.putAll(staticHeaders)
.build();

super.start(responseListener, headers.toBuilder().setHeaders(mergedHeaders).build());
}
};
}
}