Skip to content

Commit

Permalink
Start grpc-spring-boot-starter-web project
Browse files Browse the repository at this point in the history
  • Loading branch information
ST-DDT committed Jan 11, 2020
1 parent ac37042 commit 8ac23f4
Show file tree
Hide file tree
Showing 12 changed files with 1,213 additions and 0 deletions.
16 changes: 16 additions & 0 deletions grpc-spring-boot-starter-web/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apply from: '../deploy.gradle'

group = "net.devh"
version = "${projectVersion}"

compileJava.dependsOn(processResources)

dependencies {
annotationProcessor("org.springframework.boot:spring-boot-autoconfigure-processor")

compile("org.springframework.boot:spring-boot-starter")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.boot:spring-boot-starter-web")
compile("io.grpc:grpc-core:${grpcVersion}")
compile("io.grpc:grpc-protobuf:${grpcVersion}")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2016-2020 Michael Zhang <yidongnan@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package net.devh.boot.grpc.web.autoconfigure;

import java.util.Collection;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;

import io.grpc.BindableService;
import net.devh.boot.grpc.web.bridge.GrpcWebController;
import net.devh.boot.grpc.web.conversion.PojoFormat;

/**
* The auto configuration used by Spring-Boot that configures the web to grpc bridge components.
*
* @author Daniel Theuke (daniel.theuke@heuboe.de)
*/
@Configuration
@ConditionalOnClass(RestController.class)
public class GrpcServerWebAutoConfiguration {

@ConditionalOnMissingBean
@Bean
public PojoFormat defaultGrpcWebPojoFormat() {
return new PojoFormat(false, false);
}

@ConditionalOnMissingBean
@Bean
public GrpcWebController defaultGrpcWebController(final PojoFormat format,
final Collection<BindableService> services) {
final GrpcWebController grpcController = new GrpcWebController(format);
for (final BindableService service : services) {
grpcController.register(service);
}
return grpcController;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* The Spring-Boot auto configuration classes that setup the web server bridge to the grpc-service implementation.
*/

package net.devh.boot.grpc.web.autoconfigure;
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2016-2020 Michael Zhang <yidongnan@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package net.devh.boot.grpc.web.bridge;

import static java.util.Objects.requireNonNull;

import java.util.List;

import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;

public final class GrpcMethodResult<T> {

public static <T> GrpcMethodResult<T> from(final StatusRuntimeException e) {
return new GrpcMethodResult<>(e.getStatus(), e.getTrailers());
}

private final Status status;
private final Metadata headers;
private final List<T> messages;

/**
* Creates a new grpc method result for a failed call.
*
* @param status The result status of the call.
*/
public GrpcMethodResult(final Status status) {
this(status, null);
}

/**
* Creates a new grpc method result for a failed call.
*
* @param status The result status of the call.
* @param headers The headers of the call. Null will be replaced by a new empty instance.
*/
public GrpcMethodResult(final Status status, final Metadata headers) {
this(status, headers, null);
}

/**
* Creates a new grpc method result with the given messages.
*
* @param status The result status of the call.
* @param headers The headers of the call. Null will be replaced by a new empty instance.
* @param messages The result messages of call or null if there are no results.
*/
public GrpcMethodResult(final Status status, final Metadata headers, final List<T> messages) {
this.status = requireNonNull(status, "status");
this.headers = headers == null ? new Metadata() : headers;
this.messages = messages;
}

/**
* Gets the result status of the grpc call.
*
* @return The result status of the call.
*/
public Status getStatus() {
return this.status;
}

/**
* Gets whether the grpc method call was successful.
*
* @return True, if the call was successful (code = OK). False otherwise.
*/
public boolean wasSuccessful() {
return this.status.getCode() == Status.Code.OK;
}

/**
* Gets the response headers that were delivered during the request or its completion.
*
* @return The response headers of the call.
*/
public Metadata getHeaders() {
return this.headers;
}

/**
* Gets the messages of the call in the same order they were delivered.
*
* @return The results of the call.
*/
public List<T> getMessages() {
if (!wasSuccessful()) {
throw new IllegalStateException("Cannot access results of failed call");
}
return this.messages;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Copyright (c) 2016-2020 Michael Zhang <yidongnan@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package net.devh.boot.grpc.web.bridge;

import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Message;

import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.MethodDescriptor.PrototypeMarshaller;
import io.grpc.ServerCall;
import io.grpc.ServerCall.Listener;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.ServerMethodDefinition;
import io.grpc.Status;

public class GrpcMethodWrapper<RequestT extends Message, ResponseT extends Message>
implements ServerCallHandler<RequestT, ResponseT> {

@SuppressWarnings({"rawtypes", "unchecked"})
public static GrpcMethodWrapper<?, ?> ofRaw(final ServerMethodDefinition method) {
return of(method);
}

public static <RequestT extends Message, ResponseT extends Message> GrpcMethodWrapper<RequestT, ResponseT> of(
final ServerMethodDefinition<RequestT, ResponseT> method) {
final MethodDescriptor<RequestT, ResponseT> methodDescriptor = method.getMethodDescriptor();
return new GrpcMethodWrapper<>(
method.getMethodDescriptor(),
getRequestBuilderFor(methodDescriptor),
getRequestDescriptorFor(methodDescriptor),
method.getServerCallHandler());
}

protected static <RequestT extends Message> Supplier<Message.Builder> getRequestBuilderFor(
final MethodDescriptor<RequestT, ?> method) {
final RequestT requestPrototype = getRequestPrototypeFor(method);
return requestPrototype::newBuilderForType;
}

protected static <RequestT extends Message> Descriptor getRequestDescriptorFor(
final MethodDescriptor<RequestT, ?> method) {
final RequestT requestPrototype = getRequestPrototypeFor(method);
return requestPrototype.getDescriptorForType();
}

@SuppressWarnings("unchecked")
protected static <RequestT extends Message> RequestT getRequestPrototypeFor(
final MethodDescriptor<RequestT, ?> method) {
final PrototypeMarshaller<?> requestMarshaller = (PrototypeMarshaller<RequestT>) method.getRequestMarshaller();
return (RequestT) requestMarshaller.getMessagePrototype();
}

private final MethodDescriptor<RequestT, ResponseT> methodDescriptor;
private final Supplier<? extends Message.Builder> requestBuilderSupplier;
private final Descriptor requestDescriptor;
private final ServerCallHandler<RequestT, ResponseT> delegate;

public GrpcMethodWrapper(final MethodDescriptor<RequestT, ResponseT> methodDescriptor,
final Supplier<? extends Message.Builder> requestBuilderSupplier,
final Descriptor requestDescriptor,
final ServerCallHandler<RequestT, ResponseT> delegate) {
this.methodDescriptor = methodDescriptor;
this.requestBuilderSupplier = requestBuilderSupplier;
this.requestDescriptor = requestDescriptor;
this.delegate = delegate;
}

public GrpcMethodWrapper<RequestT, ResponseT> intercept(final ServerInterceptor interceptor) {
return new GrpcMethodWrapper<>(this.methodDescriptor, this.requestBuilderSupplier, this.requestDescriptor,
InterceptCallHandler.create(interceptor, this.delegate));
}

public WrappedServerCall<RequestT, ResponseT> prepare() {
return new WrappedServerCall<>(this.methodDescriptor);
}

@Override
public Listener<RequestT> startCall(final ServerCall<RequestT, ResponseT> call, final Metadata headers) {
return this.delegate.startCall(call, headers);
}

public Supplier<? extends Message.Builder> getRequestBuilderSupplier() {
return this.requestBuilderSupplier;
}

public Descriptor getRequestDescriptor() {
return this.requestDescriptor;
}

public String getFullMethodName() {
return this.methodDescriptor.getFullMethodName();
}

static final class InterceptCallHandler<ReqT, RespT> implements ServerCallHandler<ReqT, RespT> {

public static <ReqT, RespT> InterceptCallHandler<ReqT, RespT> create(
final ServerInterceptor interceptor, final ServerCallHandler<ReqT, RespT> callHandler) {
return new InterceptCallHandler<>(interceptor, callHandler);
}

private final ServerInterceptor interceptor;
private final ServerCallHandler<ReqT, RespT> callHandler;

private InterceptCallHandler(final ServerInterceptor interceptor,
final ServerCallHandler<ReqT, RespT> callHandler) {
this.interceptor = requireNonNull(interceptor, "interceptor");
this.callHandler = callHandler;
}

@Override
public ServerCall.Listener<ReqT> startCall(final ServerCall<ReqT, RespT> call, final Metadata headers) {
return this.interceptor.interceptCall(call, headers, this.callHandler);
}

}

static final class WrappedServerCall<RequestT, ResponseT> extends ServerCall<RequestT, ResponseT> {

private final MethodDescriptor<RequestT, ResponseT> methodDescriptor;

private Status status;
private Metadata headers;
private final List<ResponseT> messages = new ArrayList<>(2);

public WrappedServerCall(final MethodDescriptor<RequestT, ResponseT> methodDescriptor) {
this.methodDescriptor = methodDescriptor;
}

@Override
public void request(final int numMessages) {
// Does nothing
}

@Override
public void sendHeaders(final Metadata headers) {
if (this.headers != null) {
throw new IllegalStateException("Headers already send");
}
this.headers = headers;
}

@Override
public void sendMessage(final ResponseT message) {
this.messages.add(message);
}

@Override
public void close(final Status status, final Metadata trailers) {
this.status = status;
if (this.headers == null) {
this.headers = trailers;
} else {
this.headers.merge(trailers);
}
}

@Override
public boolean isCancelled() {
return false;
}

@Override
public MethodDescriptor<RequestT, ResponseT> getMethodDescriptor() {
return this.methodDescriptor;
}

public GrpcMethodResult<ResponseT> getResult() {
if (this.status == null) {
throw new IllegalStateException("Call not yet closed!");
}
return new GrpcMethodResult<>(this.status, this.headers, this.messages);
}

}

}
Loading

0 comments on commit 8ac23f4

Please sign in to comment.