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

com.taobao.arthas.grpcweb.proxy.SendGrpcWebResponse Maven / Gradle / Ivy

There is a newer version: 4.0.3
Show newest version
/*
 * Copyright 2020  Google LLC
 *
 * 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 com.taobao.arthas.grpcweb.proxy;

import com.taobao.arthas.grpcweb.proxy.MessageUtils.ContentType;
import io.grpc.Metadata;
import io.grpc.Status;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedStream;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.util.Base64;
import java.util.Map;

/**
 * 
 * * https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
 * * https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
 * 
 * 据协议和抓包分析,grpc-web 回应需要以 HTTP chunk数据包,包装 grpc 本身的数据。
 * 
 * grpc-web 的 http1.1 Response 由三部分组成:
 * 1. headers , 返回 status 总是 200
 * 2. data chunk ,可能多个
 * 3. trailer chunk , grpc的 grpc-status, grpc-message 在这里
 * 
 * 
* * @author hengyunabc 2023-09-06 * */ class SendGrpcWebResponse { private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getName()); private final String contentType; /** * 回应的 http1.1 header 是否已发送 */ private boolean isHeaderSent = false; /** * 所有的 grpc message 都会转换为一个 HTTP Chunk,所有的 Chunk 发送完之后,需要发送一个空的 Chunk 结束 */ private boolean isEndChunkSent = false; /** * 在 grpc 协议里,在发送完 DATA 后,最后可能发送一个 trailer,它也需要转换为 HTTP Chunk */ private boolean isTrailerSent = false; /** * 客户端主动断开连接后,需要断开相应的grpc连接, grpc服务端才能停止监听 */ private Boolean isSuccessSendData = true; private ChannelHandlerContext ctx; SendGrpcWebResponse(ChannelHandlerContext ctx, FullHttpRequest req) { HttpHeaders headers = req.headers(); contentType = headers.get(HttpHeaderNames.CONTENT_TYPE); this.ctx = ctx; } synchronized void writeHeaders(Metadata headers) { if (isHeaderSent) { return; } // 发送 http1.1 开头部分的内容 DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType).set(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); CorsUtils.updateCorsHeader(response.headers()); if (headers != null) { Map ht = MetadataUtil.getHttpHeadersFromMetadata(headers); for (String key : ht.keySet()) { response.headers().set(key, ht.get(key)); } } logger.debug("write headers: {}", response); ctx.writeAndFlush(response); isHeaderSent = true; } synchronized void returnUnimplementedStatusCode(String className) { writeHeaders(null); writeTrailer( Status.UNIMPLEMENTED.withDescription("Can not find service impl, check dep, service: " + className), null); } // 发送最后的 http chunked 空块 private void writeEndChunk() { if (isEndChunkSent) { return; } LastHttpContent end = new DefaultLastHttpContent(); ctx.writeAndFlush(end); isEndChunkSent = true; } synchronized void writeError(Status s) { writeHeaders(null); writeTrailer(s, null); } synchronized void writeTrailer(Status status, Metadata trailer) { if (isTrailerSent) { return; } StringBuffer sb = new StringBuffer(); if (trailer != null) { Map ht = MetadataUtil.getHttpHeadersFromMetadata(trailer); for (String key : ht.keySet()) { sb.append(String.format("%s:%s\r\n", key, ht.get(key))); } } sb.append(String.format("grpc-status:%d\r\n", status.getCode().value())); if (status.getDescription() != null && !status.getDescription().isEmpty()) { sb.append(String.format("grpc-message:%s\r\n", status.getDescription())); } writeResponse(sb.toString().getBytes(), MessageFramer.Type.TRAILER); isTrailerSent = true; writeEndChunk(); } synchronized boolean writeResponse(byte[] out) { return writeResponse(out, MessageFramer.Type.DATA); } private boolean writeResponse(byte[] out, MessageFramer.Type type) { if (isTrailerSent) { logger.error("grpcweb trailer sented, writeResponse can not be called, framer type: {}", type); return false; } try { // PUNT multiple frames not handled byte[] prefix = new MessageFramer().getPrefix(out, type); ByteArrayOutputStream oStream = new ByteArrayOutputStream(); // binary encode if it is "text" content type if (MessageUtils.getContentType(contentType) == ContentType.GRPC_WEB_TEXT) { byte[] concated = new byte[out.length + 5]; System.arraycopy(prefix, 0, concated, 0, 5); System.arraycopy(out, 0, concated, 5, out.length); oStream.write(Base64.getEncoder().encode(concated)); } else { oStream.write(prefix); oStream.write(out); } byte[] byteArray = oStream.toByteArray(); InputStream dataStream = new ByteArrayInputStream(byteArray); ChunkedStream chunkedStream = new ChunkedStream(dataStream); SingleHttpChunkedInput httpChunkedInput = new SingleHttpChunkedInput(chunkedStream); ChannelFuture channelFuture = ctx.writeAndFlush(httpChunkedInput); ChannelFutureListener channelFutureListener = new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (!future.isSuccess()) { // 写入操作失败 isSuccessSendData = false; } } }; channelFuture.addListener(channelFutureListener); return isSuccessSendData; } catch (IOException e) { logger.error("write grpcweb response error, framer type: {}", type, e); return false; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy