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

io.github.microcks.util.grpc.ProtoReflectionService Maven / Gradle / Ivy

The newest version!
/*
 * Copyright The Microcks 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 io.github.microcks.util.grpc;

import io.github.microcks.domain.Resource;
import io.github.microcks.domain.ResourceType;
import io.github.microcks.domain.Service;
import io.github.microcks.domain.ServiceType;
import io.github.microcks.repository.ResourceRepository;
import io.github.microcks.repository.ServiceRepository;

import com.google.protobuf.Descriptors;
import io.grpc.BindableService;
import io.grpc.Status;
import io.grpc.reflection.v1alpha.ErrorResponse;
import io.grpc.reflection.v1alpha.FileDescriptorResponse;
import io.grpc.reflection.v1alpha.ListServiceResponse;
import io.grpc.reflection.v1alpha.ServerReflectionGrpc;
import io.grpc.reflection.v1alpha.ServerReflectionRequest;
import io.grpc.reflection.v1alpha.ServerReflectionResponse;
import io.grpc.reflection.v1alpha.ServiceResponse;
import io.grpc.stub.ServerCallStreamObserver;
import io.grpc.stub.StreamObserver;

import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

/**
 * Provides a reflection service for GRPC services (excluding the reflection service itself) registered into this
 * Microcks instance.
 * @author laurent
 */
public class ProtoReflectionService extends ServerReflectionGrpc.ServerReflectionImplBase {

   final ServiceRepository serviceRepository;
   final ResourceRepository resourceRepository;

   /**
    * Build a new ProtoReflectionService using application repositories.
    * @param serviceRepository  Repository to get access to the list of GRPC services.
    * @param resourceRepository Repository to get access to the list of Protobuf resources for services.
    */
   public ProtoReflectionService(ServiceRepository serviceRepository, ResourceRepository resourceRepository) {
      this.serviceRepository = serviceRepository;
      this.resourceRepository = resourceRepository;
   }

   /**
    * Build a new ProtoReflectionService using application repositories.
    * @param serviceRepository  Repository to get access to the list of GRPC services.
    * @param resourceRepository Repository to get access to the list of Protobuf resources for services.
    */
   public static BindableService newInstance(ServiceRepository serviceRepository,
         ResourceRepository resourceRepository) {
      return new ProtoReflectionService(serviceRepository, resourceRepository);
   }

   @Override
   public StreamObserver serverReflectionInfo(
         StreamObserver responseObserver) {
      final ServerCallStreamObserver serverCallStreamObserver = (ServerCallStreamObserver) responseObserver;
      ProtoReflectionStreamObserver requestObserver = new ProtoReflectionStreamObserver(serviceRepository,
            resourceRepository, serverCallStreamObserver);
      serverCallStreamObserver.setOnReadyHandler(requestObserver);
      serverCallStreamObserver.disableAutoRequest();
      serverCallStreamObserver.request(1);
      return requestObserver;
   }

   private static class ProtoReflectionStreamObserver implements Runnable, StreamObserver {

      private final ServiceRepository serviceRepository;
      private final ResourceRepository resourceRepository;
      private final ServerCallStreamObserver serverCallStreamObserver;
      private boolean closeAfterSend = false;
      private ServerReflectionRequest request;

      ProtoReflectionStreamObserver(ServiceRepository serviceRepository, ResourceRepository resourceRepository,
            ServerCallStreamObserver serverCallStreamObserver) {
         this.serviceRepository = serviceRepository;
         this.resourceRepository = resourceRepository;
         this.serverCallStreamObserver = checkNotNull(serverCallStreamObserver, "observer");
      }

      @Override
      public void run() {
         if (request != null) {
            handleReflectionRequest();
         }
      }

      @Override
      public void onNext(ServerReflectionRequest request) {
         checkState(this.request == null);
         this.request = checkNotNull(request);
         handleReflectionRequest();
      }

      @Override
      public void onCompleted() {
         if (request != null) {
            closeAfterSend = true;
         } else {
            serverCallStreamObserver.onCompleted();
         }
      }

      @Override
      public void onError(Throwable cause) {
         serverCallStreamObserver.onError(cause);
      }

      private void handleReflectionRequest() {
         if (serverCallStreamObserver.isReady()) {
            switch (request.getMessageRequestCase()) {
               case FILE_BY_FILENAME:
                  getFileByName(request);
                  break;
               case FILE_CONTAINING_SYMBOL:
                  getFileContainingSymbol(request);
                  break;
               case FILE_CONTAINING_EXTENSION:
                  getFileByExtension(request);
                  break;
               case ALL_EXTENSION_NUMBERS_OF_TYPE:
                  getAllExtensions(request);
                  break;
               case LIST_SERVICES:
                  listServices(request);
                  break;
               default:
                  sendErrorResponse(request, Status.Code.UNIMPLEMENTED,
                        "not implemented " + request.getMessageRequestCase());
            }
            request = null;
            if (closeAfterSend) {
               serverCallStreamObserver.onCompleted();
            } else {
               serverCallStreamObserver.request(1);
            }
         }
      }

      private void getFileByName(ServerReflectionRequest request) {
         sendErrorResponse(request, Status.Code.UNIMPLEMENTED, "not implemented " + request.getMessageRequestCase());
      }

      private void getFileContainingSymbol(ServerReflectionRequest request) {
         String serviceName = request.getFileContainingSymbol();
         String version = getVersionFromServiceName(serviceName);

         Service service = serviceRepository.findByNameAndVersion(serviceName, version);
         if (service != null) {
            sendFileDescriptorForService(service, request);
         } else {
            // This could be a 'describe' request on a method or inner type like:
            // grpcurl -plaintext localhost:9090 describe io.github.microcks.grpc.hello.v1.HelloService.greeting
            serviceName = serviceName.substring(0, serviceName.lastIndexOf("."));
            version = getVersionFromServiceName(serviceName);

            service = serviceRepository.findByNameAndVersion(serviceName, version);
            if (service != null) {
               sendFileDescriptorForService(service, request);
            } else {
               sendErrorResponse(request, Status.Code.NOT_FOUND, "Symbol not found");
            }
         }
      }

      private void sendFileDescriptorForService(Service service, ServerReflectionRequest request) {
         List resources = resourceRepository.findByServiceIdAndType(service.getId(),
               ResourceType.PROTOBUF_DESCRIPTOR);
         if (!resources.isEmpty()) {
            Resource resource = resources.get(0);

            // Now we may have serviceName as being the FQDN. We have to find short version to later findServiceByName().
            String shortServiceName = service.getName();
            if (service.getName().contains(".")) {
               shortServiceName = service.getName().substring(service.getName().lastIndexOf(".") + 1);
            }

            try {
               Descriptors.FileDescriptor fd = GrpcUtil.findFileDescriptorBySymbol(resource.getContent(),
                     shortServiceName);
               serverCallStreamObserver.onNext(createServerReflectionResponse(request, fd));
            } catch (Exception e) {
               sendErrorResponse(request, Status.Code.INTERNAL, "Unreadable protobuf descriptor");
            }
         } else {
            sendErrorResponse(request, Status.Code.INTERNAL, "No protobuf descriptor found");
         }
      }

      private void getFileByExtension(ServerReflectionRequest request) {
         sendErrorResponse(request, Status.Code.UNIMPLEMENTED, "not implemented " + request.getMessageRequestCase());
      }

      private void getAllExtensions(ServerReflectionRequest request) {
         sendErrorResponse(request, Status.Code.UNIMPLEMENTED, "not implemented " + request.getMessageRequestCase());
      }

      private void listServices(ServerReflectionRequest request) {
         ListServiceResponse.Builder builder = ListServiceResponse.newBuilder();

         List services = serviceRepository.findByType(ServiceType.GRPC);
         for (Service service : services) {
            builder.addService(ServiceResponse.newBuilder().setName(service.getName()));
         }

         serverCallStreamObserver.onNext(ServerReflectionResponse.newBuilder().setValidHost(request.getHost())
               .setOriginalRequest(request).setListServicesResponse(builder).build());
      }

      private void sendErrorResponse(ServerReflectionRequest request, Status.Code code, String message) {
         ServerReflectionResponse response = ServerReflectionResponse.newBuilder().setValidHost(request.getHost())
               .setOriginalRequest(request)
               .setErrorResponse(ErrorResponse.newBuilder().setErrorCode(code.value()).setErrorMessage(message))
               .build();
         serverCallStreamObserver.onNext(response);
      }

      private ServerReflectionResponse createServerReflectionResponse(ServerReflectionRequest request,
            Descriptors.FileDescriptor fd) {
         FileDescriptorResponse.Builder fdRBuilder = FileDescriptorResponse.newBuilder();

         Set seenFiles = new HashSet<>();
         Queue frontier = new ArrayDeque<>();
         seenFiles.add(fd.getName());
         frontier.add(fd);
         while (!frontier.isEmpty()) {
            Descriptors.FileDescriptor nextFd = frontier.remove();
            fdRBuilder.addFileDescriptorProto(nextFd.toProto().toByteString());
            for (Descriptors.FileDescriptor dependencyFd : nextFd.getDependencies()) {
               if (!seenFiles.contains(dependencyFd.getName())) {
                  seenFiles.add(dependencyFd.getName());
                  frontier.add(dependencyFd);
               }
            }
         }
         return ServerReflectionResponse.newBuilder().setValidHost(request.getHost()).setOriginalRequest(request)
               .setFileDescriptorResponse(fdRBuilder).build();
      }

      private String getVersionFromServiceName(String serviceName) {
         // Retrieve version from package name.
         // org.acme package => org.acme version
         // org.acme.v1 package => v1 version
         String packageName = serviceName.substring(0, serviceName.lastIndexOf('.'));
         String[] parts = packageName.split("\\.");
         return (parts.length > 2 ? parts[parts.length - 1] : packageName);
      }
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy