Skip to content

Commit a050ac2

Browse files
committed
Start grpc-spring-boot-starter-web project
1 parent 8c82cf8 commit a050ac2

File tree

12 files changed

+1213
-0
lines changed

12 files changed

+1213
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apply from: '../deploy.gradle'
2+
3+
group = "net.devh"
4+
version = "${projectVersion}"
5+
6+
compileJava.dependsOn(processResources)
7+
8+
dependencies {
9+
annotationProcessor("org.springframework.boot:spring-boot-autoconfigure-processor")
10+
11+
compile("org.springframework.boot:spring-boot-starter")
12+
compile("org.springframework.boot:spring-boot-starter-actuator")
13+
compile("org.springframework.boot:spring-boot-starter-web")
14+
compile("io.grpc:grpc-core:${grpcVersion}")
15+
compile("io.grpc:grpc-protobuf:${grpcVersion}")
16+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) 2016-2018 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.web.autoconfigure;
19+
20+
import java.util.Collection;
21+
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.web.bind.annotation.RestController;
27+
28+
import io.grpc.BindableService;
29+
import net.devh.boot.grpc.web.bridge.GrpcWebController;
30+
import net.devh.boot.grpc.web.conversion.PojoFormat;
31+
32+
/**
33+
* The auto configuration used by Spring-Boot that configures the web to grpc bridge components.
34+
*
35+
* @author Daniel Theuke ([email protected])
36+
*/
37+
@Configuration
38+
@ConditionalOnClass(RestController.class)
39+
public class GrpcServerWebAutoConfiguration {
40+
41+
@ConditionalOnMissingBean
42+
@Bean
43+
public PojoFormat defaultGrpcWebPojoFormat() {
44+
return new PojoFormat(false, false);
45+
}
46+
47+
@ConditionalOnMissingBean
48+
@Bean
49+
public GrpcWebController defaultGrpcWebController(final PojoFormat format,
50+
final Collection<BindableService> services) {
51+
final GrpcWebController grpcController = new GrpcWebController(format);
52+
for (final BindableService service : services) {
53+
grpcController.register(service);
54+
}
55+
return grpcController;
56+
}
57+
58+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* The Spring-Boot auto configuration classes that setup the web server bridge to the grpc-service implementation.
3+
*/
4+
5+
package net.devh.boot.grpc.web.autoconfigure;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright (c) 2016-2018 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.web.bridge;
19+
20+
import static java.util.Objects.requireNonNull;
21+
22+
import java.util.List;
23+
24+
import io.grpc.Metadata;
25+
import io.grpc.Status;
26+
import io.grpc.StatusRuntimeException;
27+
28+
public final class GrpcMethodResult<T> {
29+
30+
public static <T> GrpcMethodResult<T> from(final StatusRuntimeException e) {
31+
return new GrpcMethodResult<>(e.getStatus(), e.getTrailers());
32+
}
33+
34+
private final Status status;
35+
private final Metadata headers;
36+
private final List<T> messages;
37+
38+
/**
39+
* Creates a new grpc method result for a failed call.
40+
*
41+
* @param status The result status of the call.
42+
*/
43+
public GrpcMethodResult(final Status status) {
44+
this(status, null);
45+
}
46+
47+
/**
48+
* Creates a new grpc method result for a failed call.
49+
*
50+
* @param status The result status of the call.
51+
* @param headers The headers of the call. Null will be replaced by a new empty instance.
52+
*/
53+
public GrpcMethodResult(final Status status, final Metadata headers) {
54+
this(status, headers, null);
55+
}
56+
57+
/**
58+
* Creates a new grpc method result with the given messages.
59+
*
60+
* @param status The result status of the call.
61+
* @param headers The headers of the call. Null will be replaced by a new empty instance.
62+
* @param messages The result messages of call or null if there are no results.
63+
*/
64+
public GrpcMethodResult(final Status status, final Metadata headers, final List<T> messages) {
65+
this.status = requireNonNull(status, "status");
66+
this.headers = headers == null ? new Metadata() : headers;
67+
this.messages = messages;
68+
}
69+
70+
/**
71+
* Gets the result status of the grpc call.
72+
*
73+
* @return The result status of the call.
74+
*/
75+
public Status getStatus() {
76+
return this.status;
77+
}
78+
79+
/**
80+
* Gets whether the grpc method call was successful.
81+
*
82+
* @return True, if the call was successful (code = OK). False otherwise.
83+
*/
84+
public boolean wasSuccessful() {
85+
return this.status.getCode() == Status.Code.OK;
86+
}
87+
88+
/**
89+
* Gets the response headers that were delivered during the request or its completion.
90+
*
91+
* @return The response headers of the call.
92+
*/
93+
public Metadata getHeaders() {
94+
return this.headers;
95+
}
96+
97+
/**
98+
* Gets the messages of the call in the same order they were delivered.
99+
*
100+
* @return The results of the call.
101+
*/
102+
public List<T> getMessages() {
103+
if (!wasSuccessful()) {
104+
throw new IllegalStateException("Cannot access results of failed call");
105+
}
106+
return this.messages;
107+
}
108+
109+
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* Copyright (c) 2016-2018 Michael Zhang <[email protected]>
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
6+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
7+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
*
9+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
10+
* Software.
11+
*
12+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
13+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
15+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16+
*/
17+
18+
package net.devh.boot.grpc.web.bridge;
19+
20+
import static java.util.Objects.requireNonNull;
21+
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.function.Supplier;
25+
26+
import com.google.protobuf.Descriptors.Descriptor;
27+
import com.google.protobuf.Message;
28+
29+
import io.grpc.Metadata;
30+
import io.grpc.MethodDescriptor;
31+
import io.grpc.MethodDescriptor.PrototypeMarshaller;
32+
import io.grpc.ServerCall;
33+
import io.grpc.ServerCall.Listener;
34+
import io.grpc.ServerCallHandler;
35+
import io.grpc.ServerInterceptor;
36+
import io.grpc.ServerMethodDefinition;
37+
import io.grpc.Status;
38+
39+
public class GrpcMethodWrapper<RequestT extends Message, ResponseT extends Message>
40+
implements ServerCallHandler<RequestT, ResponseT> {
41+
42+
@SuppressWarnings({"rawtypes", "unchecked"})
43+
public static GrpcMethodWrapper<?, ?> ofRaw(final ServerMethodDefinition method) {
44+
return of(method);
45+
}
46+
47+
public static <RequestT extends Message, ResponseT extends Message> GrpcMethodWrapper<RequestT, ResponseT> of(
48+
final ServerMethodDefinition<RequestT, ResponseT> method) {
49+
final MethodDescriptor<RequestT, ResponseT> methodDescriptor = method.getMethodDescriptor();
50+
return new GrpcMethodWrapper<>(
51+
method.getMethodDescriptor(),
52+
getRequestBuilderFor(methodDescriptor),
53+
getRequestDescriptorFor(methodDescriptor),
54+
method.getServerCallHandler());
55+
}
56+
57+
protected static <RequestT extends Message> Supplier<Message.Builder> getRequestBuilderFor(
58+
final MethodDescriptor<RequestT, ?> method) {
59+
final RequestT requestPrototype = getRequestPrototypeFor(method);
60+
return requestPrototype::newBuilderForType;
61+
}
62+
63+
protected static <RequestT extends Message> Descriptor getRequestDescriptorFor(
64+
final MethodDescriptor<RequestT, ?> method) {
65+
final RequestT requestPrototype = getRequestPrototypeFor(method);
66+
return requestPrototype.getDescriptorForType();
67+
}
68+
69+
@SuppressWarnings("unchecked")
70+
protected static <RequestT extends Message> RequestT getRequestPrototypeFor(
71+
final MethodDescriptor<RequestT, ?> method) {
72+
final PrototypeMarshaller<?> requestMarshaller = (PrototypeMarshaller<RequestT>) method.getRequestMarshaller();
73+
return (RequestT) requestMarshaller.getMessagePrototype();
74+
}
75+
76+
private final MethodDescriptor<RequestT, ResponseT> methodDescriptor;
77+
private final Supplier<? extends Message.Builder> requestBuilderSupplier;
78+
private final Descriptor requestDescriptor;
79+
private final ServerCallHandler<RequestT, ResponseT> delegate;
80+
81+
public GrpcMethodWrapper(final MethodDescriptor<RequestT, ResponseT> methodDescriptor,
82+
final Supplier<? extends Message.Builder> requestBuilderSupplier,
83+
final Descriptor requestDescriptor,
84+
final ServerCallHandler<RequestT, ResponseT> delegate) {
85+
this.methodDescriptor = methodDescriptor;
86+
this.requestBuilderSupplier = requestBuilderSupplier;
87+
this.requestDescriptor = requestDescriptor;
88+
this.delegate = delegate;
89+
}
90+
91+
public GrpcMethodWrapper<RequestT, ResponseT> intercept(final ServerInterceptor interceptor) {
92+
return new GrpcMethodWrapper<>(this.methodDescriptor, this.requestBuilderSupplier, this.requestDescriptor,
93+
InterceptCallHandler.create(interceptor, this.delegate));
94+
}
95+
96+
public WrappedServerCall<RequestT, ResponseT> prepare() {
97+
return new WrappedServerCall<>(this.methodDescriptor);
98+
}
99+
100+
@Override
101+
public Listener<RequestT> startCall(final ServerCall<RequestT, ResponseT> call, final Metadata headers) {
102+
return this.delegate.startCall(call, headers);
103+
}
104+
105+
public Supplier<? extends Message.Builder> getRequestBuilderSupplier() {
106+
return this.requestBuilderSupplier;
107+
}
108+
109+
public Descriptor getRequestDescriptor() {
110+
return this.requestDescriptor;
111+
}
112+
113+
public String getFullMethodName() {
114+
return this.methodDescriptor.getFullMethodName();
115+
}
116+
117+
static final class InterceptCallHandler<ReqT, RespT> implements ServerCallHandler<ReqT, RespT> {
118+
119+
public static <ReqT, RespT> InterceptCallHandler<ReqT, RespT> create(
120+
final ServerInterceptor interceptor, final ServerCallHandler<ReqT, RespT> callHandler) {
121+
return new InterceptCallHandler<>(interceptor, callHandler);
122+
}
123+
124+
private final ServerInterceptor interceptor;
125+
private final ServerCallHandler<ReqT, RespT> callHandler;
126+
127+
private InterceptCallHandler(final ServerInterceptor interceptor,
128+
final ServerCallHandler<ReqT, RespT> callHandler) {
129+
this.interceptor = requireNonNull(interceptor, "interceptor");
130+
this.callHandler = callHandler;
131+
}
132+
133+
@Override
134+
public ServerCall.Listener<ReqT> startCall(final ServerCall<ReqT, RespT> call, final Metadata headers) {
135+
return this.interceptor.interceptCall(call, headers, this.callHandler);
136+
}
137+
138+
}
139+
140+
static final class WrappedServerCall<RequestT, ResponseT> extends ServerCall<RequestT, ResponseT> {
141+
142+
private final MethodDescriptor<RequestT, ResponseT> methodDescriptor;
143+
144+
private Status status;
145+
private Metadata headers;
146+
private final List<ResponseT> messages = new ArrayList<>(2);
147+
148+
public WrappedServerCall(final MethodDescriptor<RequestT, ResponseT> methodDescriptor) {
149+
this.methodDescriptor = methodDescriptor;
150+
}
151+
152+
@Override
153+
public void request(final int numMessages) {
154+
// Does nothing
155+
}
156+
157+
@Override
158+
public void sendHeaders(final Metadata headers) {
159+
if (this.headers != null) {
160+
throw new IllegalStateException("Headers already send");
161+
}
162+
this.headers = headers;
163+
}
164+
165+
@Override
166+
public void sendMessage(final ResponseT message) {
167+
this.messages.add(message);
168+
}
169+
170+
@Override
171+
public void close(final Status status, final Metadata trailers) {
172+
this.status = status;
173+
if (this.headers == null) {
174+
this.headers = trailers;
175+
} else {
176+
this.headers.merge(trailers);
177+
}
178+
}
179+
180+
@Override
181+
public boolean isCancelled() {
182+
return false;
183+
}
184+
185+
@Override
186+
public MethodDescriptor<RequestT, ResponseT> getMethodDescriptor() {
187+
return this.methodDescriptor;
188+
}
189+
190+
public GrpcMethodResult<ResponseT> getResult() {
191+
if (this.status == null) {
192+
throw new IllegalStateException("Call not yet closed!");
193+
}
194+
return new GrpcMethodResult<>(this.status, this.headers, this.messages);
195+
}
196+
197+
}
198+
199+
}

0 commit comments

Comments
 (0)