FramedGrpcService extends AbstractHttpService implements GrpcService { @Override protected HttpResponse doPost(ServiceRequestContext ctx, HttpRequest req) throws Exception { final MediaType contentType = req.contentType(); final SerializationFormat serializationFormat = findSerializationFormat(contentType); if (serializationFormat == null) { return HttpResponse.of(HttpStatus.UNSUPPORTED_MEDIA_TYPE, MediaType.PLAIN_TEXT_UTF_8, "Missing or invalid Content-Type header."); } ctx.logBuilder().serializationFormat(serializationFormat); final String methodName = GrpcRequestUtil.determineMethod(ctx); if (methodName == null) { return HttpResponse.of(HttpStatus.BAD_REQUEST, MediaType.PLAIN_TEXT_UTF_8, "Invalid path."); } final ServerMethodDefinition<?, ?> method = registry.lookupMethod(methodName); if (method == null) { return HttpResponse.of( (ResponseHeaders) ArmeriaServerCall.statusToTrailers( ctx, defaultHeaders.get(serializationFormat).toBuilder(), Status.UNIMPLEMENTED.withDescription("Method not found: " + methodName), new Metadata())); } if (useClientTimeoutHeader) { final String timeoutHeader = req.headers().get(GrpcHeaderNames.GRPC_TIMEOUT); if (timeoutHeader != null) { try { final long timeout = TimeoutHeaderUtil.fromHeaderValue(timeoutHeader); if (timeout == 0) { ctx.clearRequestTimeout(); } else { ctx.setRequestTimeout(TimeoutMode.SET_FROM_NOW, Duration.ofNanos(timeout)); } } catch (IllegalArgumentException e) { return HttpResponse.of( (ResponseHeaders) ArmeriaServerCall.statusToTrailers( ctx, defaultHeaders.get(serializationFormat).toBuilder(), GrpcStatus.fromThrowable(e), new Metadata())); } } } ctx.logBuilder().defer(RequestLogProperty.REQUEST_CONTENT, RequestLogProperty.RESPONSE_CONTENT); final HttpResponseWriter res = HttpResponse.streaming(); final ArmeriaServerCall<?, ?> call = startCall( methodName, method, ctx, req.headers(), res, serializationFormat); if (call != null) { ctx.whenRequestTimingOut().thenRun(() -> call.close(Status.CANCELLED, new Metadata())); req.subscribe(call.messageReader(), ctx.eventLoop(), SubscriptionOption.WITH_POOLED_OBJECTS); } return res; } FramedGrpcService(HandlerRegistry registry,
                      Set<Route> routes,
                      DecompressorRegistry decompressorRegistry,
                      CompressorRegistry compressorRegistry,
                      Set<SerializationFormat> supportedSerializationFormats,
                      Function<? super ServiceDescriptor, ? extends GrpcJsonMarshaller> jsonMarshallerFactory,
                      @Nullable ProtoReflectionServiceInterceptor protoReflectionServiceInterceptor,
                      int maxOutboundMessageSizeBytes,
                      boolean useBlockingTaskExecutor,
                      boolean unsafeWrapRequestBuffers,
                      boolean useClientTimeoutHeader,
                      int maxInboundMessageSizeBytes); @Override void serviceAdded(ServiceConfig cfg); @Override boolean isFramed(); @Override List<ServerServiceDefinition> services(); @Override Set<SerializationFormat> supportedSerializationFormats(); @Override Set<Route> routes();  }
@Test void missingContentType() throws Exception { final HttpRequest req = HttpRequest.of(HttpMethod.POST, "/grpc.testing.TestService.UnaryCall"); final ServiceRequestContext ctx = ServiceRequestContext.of(req); final HttpResponse response = grpcService.doPost(ctx, req); assertThat(response.aggregate().get()).isEqualTo(AggregatedHttpResponse.of( ResponseHeaders.of(HttpStatus.UNSUPPORTED_MEDIA_TYPE, HttpHeaderNames.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8, HttpHeaderNames.CONTENT_LENGTH, 39), HttpData.ofUtf8("Missing or invalid Content-Type header."))); }
@Test void badContentType() throws Exception { final HttpRequest req = HttpRequest.of( RequestHeaders.of(HttpMethod.POST, "/grpc.testing.TestService.UnaryCall", HttpHeaderNames.CONTENT_TYPE, MediaType.JSON_UTF_8)); final ServiceRequestContext ctx = ServiceRequestContext.of(req); final HttpResponse response = grpcService.doPost(ctx, req); assertThat(response.aggregate().get()).isEqualTo(AggregatedHttpResponse.of( ResponseHeaders.of(HttpStatus.UNSUPPORTED_MEDIA_TYPE, HttpHeaderNames.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8, HttpHeaderNames.CONTENT_LENGTH, 39), HttpData.ofUtf8("Missing or invalid Content-Type header."))); }
@Test void pathMissingSlash() throws Exception { final HttpRequest req = HttpRequest.of( RequestHeaders.of(HttpMethod.POST, "/grpc.testing.TestService.UnaryCall", HttpHeaderNames.CONTENT_TYPE, "application/grpc+proto")); final RoutingResult routingResult = RoutingResult.builder() .path("grpc.testing.TestService.UnaryCall") .build(); final ServiceRequestContext ctx = ServiceRequestContext.builder(req) .routingResult(routingResult) .build(); final HttpResponse response = grpcService.doPost(ctx, req); assertThat(response.aggregate().get()).isEqualTo(AggregatedHttpResponse.of( ResponseHeaders.of(HttpStatus.BAD_REQUEST, HttpHeaderNames.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8, HttpHeaderNames.CONTENT_LENGTH, 13), HttpData.ofUtf8("Invalid path."))); }
@Test void missingMethod() throws Exception { final HttpRequest req = HttpRequest.of( RequestHeaders.of(HttpMethod.POST, "/grpc.testing.TestService/FooCall", HttpHeaderNames.CONTENT_TYPE, "application/grpc+proto")); final RoutingResult routingResult = RoutingResult.builder() .path("/grpc.testing.TestService/FooCall") .build(); final ServiceRequestContext ctx = ServiceRequestContext.builder(req) .routingResult(routingResult) .build(); final HttpResponse response = grpcService.doPost(ctx, req); assertThat(response.aggregate().get()).isEqualTo(AggregatedHttpResponse.of( ResponseHeaders.builder(HttpStatus.OK) .endOfStream(true) .add(HttpHeaderNames.CONTENT_TYPE, "application/grpc+proto") .add(GrpcHeaderNames.GRPC_ENCODING, "identity") .add(GrpcHeaderNames.GRPC_ACCEPT_ENCODING, "gzip") .addInt("grpc-status", 12) .add("grpc-message", "Method not found: grpc.testing.TestService/FooCall") .addInt(HttpHeaderNames.CONTENT_LENGTH, 0) .build(), HttpData.empty())); }