/*
* Copyright 2016 LINE Corporation
*
* LINE Corporation licenses this file to you 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 com.linecorp.armeria.server.grpc;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableSet;
import com.linecorp.armeria.common.SerializationFormat;
import com.linecorp.armeria.common.grpc.GrpcSerializationFormats;
import com.linecorp.armeria.common.http.HttpRequest;
import com.linecorp.armeria.common.http.HttpResponse;
import com.linecorp.armeria.internal.grpc.ArmeriaMessageFramer;
import com.linecorp.armeria.server.ServerConfig;
import com.linecorp.armeria.server.Service;
import io.grpc.BindableService;
import io.grpc.CompressorRegistry;
import io.grpc.DecompressorRegistry;
import io.grpc.ServerServiceDefinition;
/**
* Constructs a {@link GrpcService} to serve GRPC services from within Armeria.
*/
public final class GrpcServiceBuilder {
private static final Set<SerializationFormat> DEFAULT_SUPPORTED_SERIALIZATION_FORMATS =
ImmutableSet.of(GrpcSerializationFormats.PROTO, GrpcSerializationFormats.PROTO_WEB);
private final HandlerRegistry.Builder registryBuilder = new HandlerRegistry.Builder();
@Nullable
private DecompressorRegistry decompressorRegistry;
@Nullable
private CompressorRegistry compressorRegistry;
private Set<SerializationFormat> supportedSerializationFormats = DEFAULT_SUPPORTED_SERIALIZATION_FORMATS;
private int maxInboundMessageSizeBytes = GrpcService.NO_MAX_INBOUND_MESSAGE_SIZE;
private int maxOutboundMessageSizeBytes = ArmeriaMessageFramer.NO_MAX_OUTBOUND_MESSAGE_SIZE;
private boolean enableUnframedRequests;
/**
* Adds a GRPC {@link ServerServiceDefinition} to this {@link GrpcServiceBuilder}, such as
* what's returned by {@link BindableService#bindService()}.
*/
public GrpcServiceBuilder addService(ServerServiceDefinition service) {
registryBuilder.addService(requireNonNull(service, "service"));
return this;
}
/**
* Adds a GRPC {@link BindableService} to this {@link GrpcServiceBuilder}. Most GRPC service
* implementations are {@link BindableService}s.
*/
public GrpcServiceBuilder addService(BindableService bindableService) {
return addService(bindableService.bindService());
}
/**
* Sets the {@link DecompressorRegistry} to use when decompressing messages. If not set, will use
* the default, which supports gzip only.
*/
public GrpcServiceBuilder decompressorRegistry(DecompressorRegistry registry) {
decompressorRegistry = requireNonNull(registry, "registry");
return this;
}
/**
* Sets the {@link CompressorRegistry} to use when compressing messages. If not set, will use the
* default, which supports gzip only.
*/
public GrpcServiceBuilder compressorRegistry(CompressorRegistry registry) {
compressorRegistry = requireNonNull(registry, "registry");
return this;
}
/**
* Sets the {@link SerializationFormat}s supported by this server. If not set, defaults to supporting binary
* protobuf formats. JSON formats are currently very inefficient and not recommended for use in production.
*
* <p>TODO(anuraaga): Use faster JSON marshalling.
*/
public GrpcServiceBuilder supportedSerializationFormats(SerializationFormat... formats) {
return supportedSerializationFormats(ImmutableSet.copyOf(requireNonNull(formats, "formats")));
}
/**
* Sets the {@link SerializationFormat}s supported by this server. If not set, defaults to supporting binary
* protobuf formats. JSON formats are currently very inefficient and not recommended for use in production.
*
* <p>TODO(anuraaga): Use faster JSON marshalling.
*/
public GrpcServiceBuilder supportedSerializationFormats(Iterable<SerializationFormat> formats) {
requireNonNull(formats, "formats");
for (SerializationFormat format : formats) {
if (!GrpcSerializationFormats.isGrpc(format)) {
throw new IllegalArgumentException("Not a GRPC serialization format: " + format);
}
}
supportedSerializationFormats = ImmutableSet.copyOf(formats);
return this;
}
/**
* Sets the maximum size in bytes of an individual incoming message. If not set, will use
* {@link ServerConfig#defaultMaxRequestLength}. To support long-running RPC streams, it is recommended to
* set {@link ServerConfig#defaultMaxRequestLength} and {@link ServerConfig#defaultRequestTimeoutMillis} to
* very high values and set this to the expected limit of individual messages in the stream.
*/
public GrpcServiceBuilder setMaxInboundMessageSizeBytes(int maxInboundMessageSizeBytes) {
checkArgument(maxInboundMessageSizeBytes > 0,
"maxInboundMessageSizeBytes must be >0");
this.maxInboundMessageSizeBytes = maxInboundMessageSizeBytes;
return this;
}
/**
* Sets the maximum size in bytes of an individual outgoing message. If not set, all messages will be sent.
* This can be a safety valve to prevent overflowing network connections with large messages due to business
* logic bugs.
*/
public GrpcServiceBuilder setMaxOutboundMessageSizeBytes(int maxOutboundMessageSizeBytes) {
checkArgument(maxOutboundMessageSizeBytes > 0,
"maxOutboundMessageSizeBytes must be >0");
this.maxOutboundMessageSizeBytes = maxOutboundMessageSizeBytes;
return this;
}
/**
* Sets whether the service handles requests not framed using the GRPC wire protocol. Such requests should
* only have the serialized message as the request content, and the response content will only have the
* serialized response message. Supporting unframed requests can be useful, for example, when migrating an
* existing service to GRPC.
*
* <p>Limitations:
* <ul>
* <li>Only unary methods (single request, single response) are supported.</li>
* <li>
* Message compression is not supported.
* {@link com.linecorp.armeria.server.http.encoding.HttpEncodingService} should be used instead for
* transport level encoding.
* </li>
* </ul>
*/
public GrpcServiceBuilder enableUnframedRequests(boolean enableUnframedRequests) {
this.enableUnframedRequests = enableUnframedRequests;
return this;
}
/**
* Constructs a new {@link GrpcService} that can be bound to
* {@link com.linecorp.armeria.server.ServerBuilder}. As GRPC services themselves are mounted at a path that
* corresponds to their protobuf package, you will almost always want to bind to a prefix, e.g. by using
* {@link com.linecorp.armeria.server.ServerBuilder#serviceUnder(String, Service)}.
*/
public Service<HttpRequest, HttpResponse> build() {
GrpcService grpcService = new GrpcService(
registryBuilder.build(),
firstNonNull(decompressorRegistry, DecompressorRegistry.getDefaultInstance()),
firstNonNull(compressorRegistry, CompressorRegistry.getDefaultInstance()),
supportedSerializationFormats, maxOutboundMessageSizeBytes,
maxInboundMessageSizeBytes);
return enableUnframedRequests ? grpcService.decorate(UnframedGrpcService::new) : grpcService;
}
}