private void doSendHeaders(Metadata metadata) {
        if (isCancelled()) {
            // call was already closed by a client or a timeout scheduler.
            return;
        }
        checkState(responseHeaders == null, "sendHeaders already called");
        checkState(!closeCalled, "call is closed");

        final Compressor oldCompressor = compressor;
        if (messageCompression && !clientAcceptEncoding.isEmpty()) {
            final List<String> acceptedEncodings =
                    ACCEPT_ENCODING_SPLITTER.splitToList(clientAcceptEncoding);
            if (compressor != null) {
                if (!acceptedEncodings.contains(compressor.getMessageEncoding())) {
                    // resort to using no compression.
                    compressor = Codec.Identity.NONE;
                }
            } else if (autoCompression) {
                for (final String encoding : acceptedEncodings) {
                    final Compressor compressor0 = compressorRegistry.lookupCompressor(encoding);
                    if (compressor0 != null) {
                        compressor = compressor0;
                        break;
                    }
                }
            }
        } else {
            compressor = Codec.Identity.NONE;
        }
        if (compressor == null) {
            compressor = Codec.Identity.NONE;
        }
        if (oldCompressor != compressor) {
            // Update the old compressor to new one.
            responseFramer.setCompressor(ForwardingCompressor.forGrpc(compressor));
        }

        ResponseHeaders headers = defaultResponseHeaders;

        if (compressor != Codec.Identity.NONE || InternalMetadata.headerCount(metadata) > 0) {
            headers = headers.withMutations(builder -> {
                if (compressor != Codec.Identity.NONE) {
                    assert compressor != null;
                    builder.set(GrpcHeaderNames.GRPC_ENCODING, compressor.getMessageEncoding());
                }
                MetadataUtil.fillHeaders(metadata, builder);

                // Delete the custom content-length of the streaming response which might be set by wrongly
                // implemented stubs such as Monix-gRPC. https://github.com/monix/monix-grpc/issues/43
                // If a wrong content-length is set, an RST_STREAM error occurs at the Netty level.
                // We don't need to care about the content-length of a unary call because it is eventually
                // adjusted when the response is aggregated.
                if (!method.getType().serverSendsOneMessage() && builder.contentLength() > -1) {
                    builder.remove(HttpHeaderNames.CONTENT_LENGTH);
                }
            });
        }

        // https://github.com/grpc/proposal/blob/4c4a06d95eb1e7d3d7d84c4c9505a99f2a721db9/A6-client-retries.md#L263
        // gRPC servers should delay the Response-Headers until the first response message or
        // until the application code chooses to send headers.
        responseHeaders = headers;
    }
Cancels a call when the call was closed by a client, not by server.
grpc/src/main/java/com/linecorp/armeria/internal/server/grpc/AbstractServerCall.java