Skip to content

Commit 40188ab

Browse files
committed
[fix] grpc and http2 frames are mixed in a response
1 parent 2788de9 commit 40188ab

File tree

4 files changed

+189
-51
lines changed

4 files changed

+189
-51
lines changed

src/main/java/core/packetproxy/encode/EncodeHTTPBase.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.io.InputStream;
1919

2020
import packetproxy.http.Http;
21+
import packetproxy.http2.FramesBase;
22+
import packetproxy.http2.Grpc;
2123
import packetproxy.http2.Http2;
2224
import packetproxy.model.Packet;
2325

@@ -27,7 +29,7 @@ public enum HTTPVersion {
2729
HTTP1, HTTP2
2830
}
2931
private HTTPVersion httpVersion;
30-
private Http2 http2;
32+
private FramesBase http2;
3133

3234
public EncodeHTTPBase() {
3335
super("http/1.1");
@@ -40,9 +42,12 @@ public EncodeHTTPBase(String ALPN) throws Exception {
4042
httpVersion = HTTPVersion.HTTP1;
4143
} else if (ALPN.equals("http/1.0") || ALPN.equals("http/1.1")) {
4244
httpVersion = HTTPVersion.HTTP1;
43-
} else if (ALPN.equals("h2") || ALPN.startsWith("grpc")) {
45+
} else if (ALPN.equals("h2")) {
4446
httpVersion = HTTPVersion.HTTP2;
4547
http2 = new Http2();
48+
} else if (ALPN.equals("grpc") || ALPN.equals("grpc-exp")) {
49+
httpVersion = HTTPVersion.HTTP2;
50+
http2 = new Grpc();
4651
} else {
4752
httpVersion = HTTPVersion.HTTP1;
4853
}

src/main/java/core/packetproxy/http2/FramesBase.java

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import packetproxy.http2.frames.Frame;
2626
import packetproxy.http2.frames.FrameUtils;
27+
import packetproxy.model.Packet;
2728

2829
public abstract class FramesBase
2930
{
@@ -139,4 +140,5 @@ public InputStream getServerFlowControlledInputStream() {
139140
protected abstract byte[] decodeServerResponseFromFrames(byte[] frames) throws Exception;
140141
protected abstract byte[] encodeClientRequestToFrames(byte[] data) throws Exception;
141142
protected abstract byte[] encodeServerResponseToFrames(byte[] data) throws Exception;
143+
public abstract void setGroupId(Packet packet) throws Exception;
142144
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Copyright 2019 DeNA Co., Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package packetproxy.http2;
17+
18+
import java.io.ByteArrayOutputStream;
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import org.eclipse.jetty.http.HttpField;
24+
import org.eclipse.jetty.http.HttpFields;
25+
import org.eclipse.jetty.http2.hpack.HpackEncoder;
26+
27+
import packetproxy.common.UniqueID;
28+
import packetproxy.http.HeaderField;
29+
import packetproxy.http.Http;
30+
import packetproxy.http2.frames.DataFrame;
31+
import packetproxy.http2.frames.Frame;
32+
import packetproxy.http2.frames.FrameUtils;
33+
import packetproxy.http2.frames.HeadersFrame;
34+
import packetproxy.model.Packet;
35+
36+
public class Grpc extends FramesBase
37+
{
38+
private StreamManager clientStreamManager = new StreamManager();
39+
private StreamManager serverStreamManager = new StreamManager();
40+
41+
private static String[] GRPC_RESPONSE_2ND_HEADERS = new String[]{"grpc-status","trace-proto-bin"};
42+
43+
public Grpc() throws Exception {
44+
super();
45+
}
46+
47+
@Override
48+
public String getName() {
49+
return "gRPC";
50+
}
51+
52+
@Override
53+
protected byte[] passFramesToDecodeClientRequest(List<Frame> frames) throws Exception { return filterFrames(clientStreamManager, frames); }
54+
@Override
55+
protected byte[] passFramesToDecodeServerResponse(List<Frame> frames) throws Exception { return filterFrames(serverStreamManager, frames); }
56+
57+
private byte[] filterFrames(StreamManager streamManager, List<Frame> frames) throws Exception {
58+
for (Frame frame : frames) {
59+
if (frame instanceof HeadersFrame) {
60+
streamManager.write(frame);
61+
} else if (frame instanceof DataFrame) {
62+
streamManager.write(frame);
63+
}
64+
if ((frame.getFlags() & 0x01) > 0) {
65+
List<Frame> stream = streamManager.read(frame.getStreamId());
66+
return FrameUtils.toByteArray(stream);
67+
}
68+
}
69+
return null;
70+
}
71+
72+
@Override
73+
protected byte[] decodeClientRequestFromFrames(byte[] frames) throws Exception { return decodeFromFrames(frames); }
74+
@Override
75+
protected byte[] decodeServerResponseFromFrames(byte[] frames) throws Exception { return decodeFromFrames(frames); }
76+
77+
private byte[] decodeFromFrames(byte[] frames) throws Exception {
78+
ByteArrayOutputStream outHeader = new ByteArrayOutputStream();
79+
ByteArrayOutputStream outData = new ByteArrayOutputStream();
80+
Http httpHeaderSums = null;
81+
82+
for (Frame frame : FrameUtils.parseFrames(frames)) {
83+
if (frame instanceof HeadersFrame) {
84+
HeadersFrame headersFrame = (HeadersFrame)frame;
85+
Http http = new Http(headersFrame.getHttp());
86+
if(null==httpHeaderSums){
87+
httpHeaderSums = http;
88+
}else{
89+
for(HeaderField field: http.getHeader().getFields()){
90+
httpHeaderSums.updateHeader(field.getName(), field.getValue());
91+
}
92+
}
93+
} else if (frame instanceof DataFrame) {
94+
DataFrame dataFrame = (DataFrame)frame;
95+
outData.write(dataFrame.getPayload());
96+
}
97+
}
98+
outHeader.write(httpHeaderSums.toByteArray());
99+
//順序をHeader, Dataにする。本当はStreamIDで管理してencode時に元の順番に戻せるようにしたい。
100+
//暫定でgRPC over HTTP2のレスポンスの2つめのヘッダはGRPC_RESPONSE_2ND_HEADERSに従って元の順番に戻す。
101+
outData.writeTo(outHeader);
102+
Http http = new Http(outHeader.toByteArray());
103+
int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags"));
104+
if (http.getBody() == null || http.getBody().length == 0) {
105+
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
106+
}
107+
return http.toByteArray();
108+
}
109+
110+
@Override
111+
protected byte[] encodeClientRequestToFrames(byte[] http) throws Exception { return encodeToFrames(http, super.getClientHpackEncoder()); }
112+
@Override
113+
protected byte[] encodeServerResponseToFrames(byte[] http) throws Exception { return encodeToFrames(http, super.getServerHpackEncoder()); }
114+
115+
private byte[] encodeToFrames(byte[] data, HpackEncoder encoder) throws Exception {
116+
ByteArrayOutputStream out = new ByteArrayOutputStream();
117+
Http http = new Http(data);
118+
int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags"));
119+
if (http.getBody() != null && http.getBody().length > 0) {
120+
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff & ~HeadersFrame.FLAG_END_STREAM));
121+
HttpFields GRPC2ndHeaderHttpFields = new HttpFields();
122+
for(String k:GRPC_RESPONSE_2ND_HEADERS){
123+
String v = http.getFirstHeader(k);
124+
http.removeHeader(k);
125+
if("".equals(v)) continue;
126+
GRPC2ndHeaderHttpFields.add(k, v);
127+
}
128+
129+
HeadersFrame headersFrame = new HeadersFrame(http);
130+
out.write(headersFrame.toByteArrayWithoutExtra(encoder));
131+
132+
DataFrame dataFrame = new DataFrame(http);
133+
if(GRPC2ndHeaderHttpFields.size()>0){
134+
dataFrame.setFlags(dataFrame.getFlags() & 0xff & ~DataFrame.FLAG_END_STREAM);
135+
}
136+
out.write(dataFrame.toByteArrayWithoutExtra());
137+
138+
if(GRPC2ndHeaderHttpFields.size()>0) {
139+
Http althttp = http;
140+
althttp.setBody(new byte[0]);
141+
althttp.removeMatches("^(?!X-PacketProxy-HTTP2).*$");
142+
143+
for (HttpField headerField : GRPC2ndHeaderHttpFields) {
144+
althttp.updateHeader(headerField.getName(), headerField.getValue());
145+
}
146+
althttp.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
147+
HeadersFrame headers2ndFrame = new HeadersFrame(althttp);
148+
out.write(headers2ndFrame.toByteArrayWithoutExtra(encoder));
149+
}
150+
} else {
151+
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
152+
HeadersFrame headersFrame = new HeadersFrame(http);
153+
out.write(headersFrame.toByteArrayWithoutExtra(encoder));
154+
}
155+
return out.toByteArray();
156+
}
157+
158+
/* key: streamId, value: groupId */
159+
private Map<Long,Long> groupMap = new HashMap<>();
160+
161+
public void setGroupId(Packet packet) throws Exception {
162+
byte[] data = (packet.getDecodedData().length > 0) ? packet.getDecodedData() : packet.getModifiedData();
163+
Http http = new Http(data);
164+
String streamIdStr = http.getFirstHeader("X-PacketProxy-HTTP2-Stream-Id");
165+
if (streamIdStr != null && streamIdStr.length() > 0) {
166+
long streamId = Long.parseLong(streamIdStr);
167+
if (groupMap.containsKey(streamId)) {
168+
packet.setGroup(groupMap.get(streamId));
169+
} else {
170+
long groupId = UniqueID.getInstance().createId();
171+
groupMap.put(streamId, groupId);
172+
packet.setGroup(groupId);
173+
}
174+
}
175+
}
176+
}

src/main/java/core/packetproxy/http2/Http2.java

+4-49
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,10 @@
2020
import java.util.List;
2121
import java.util.Map;
2222

23-
import org.eclipse.jetty.http.HttpField;
24-
import org.eclipse.jetty.http.HttpFields;
2523
import org.eclipse.jetty.http2.hpack.HpackEncoder;
2624

2725
import packetproxy.common.UniqueID;
28-
import packetproxy.http.HeaderField;
2926
import packetproxy.http.Http;
30-
import packetproxy.http.HttpHeader;
3127
import packetproxy.http2.frames.DataFrame;
3228
import packetproxy.http2.frames.Frame;
3329
import packetproxy.http2.frames.FrameUtils;
@@ -39,8 +35,6 @@ public class Http2 extends FramesBase
3935
private StreamManager clientStreamManager = new StreamManager();
4036
private StreamManager serverStreamManager = new StreamManager();
4137

42-
private static String[] GRPC_RESPONSE_2ND_HEADERS = new String[]{"grpc-status","trace-proto-bin"};
43-
4438
public Http2() throws Exception {
4539
super();
4640
}
@@ -76,31 +70,17 @@ private byte[] filterFrames(StreamManager streamManager, List<Frame> frames) thr
7670
protected byte[] decodeServerResponseFromFrames(byte[] frames) throws Exception { return decodeFromFrames(frames); }
7771

7872
private byte[] decodeFromFrames(byte[] frames) throws Exception {
79-
ByteArrayOutputStream outHeader = new ByteArrayOutputStream();
80-
ByteArrayOutputStream outData = new ByteArrayOutputStream();
81-
Http httpHeaderSums = null;
82-
73+
ByteArrayOutputStream out = new ByteArrayOutputStream();
8374
for (Frame frame : FrameUtils.parseFrames(frames)) {
8475
if (frame instanceof HeadersFrame) {
8576
HeadersFrame headersFrame = (HeadersFrame)frame;
86-
Http http = new Http(headersFrame.getHttp());
87-
if(null==httpHeaderSums){
88-
httpHeaderSums = http;
89-
}else{
90-
for(HeaderField field: http.getHeader().getFields()){
91-
httpHeaderSums.updateHeader(field.getName(), field.getValue());
92-
}
93-
}
77+
out.write(headersFrame.getHttp());
9478
} else if (frame instanceof DataFrame) {
9579
DataFrame dataFrame = (DataFrame)frame;
96-
outData.write(dataFrame.getPayload());
80+
out.write(dataFrame.getPayload());
9781
}
9882
}
99-
outHeader.write(httpHeaderSums.toByteArray());
100-
//順序をHeader, Dataにする。本当はStreamIDで管理してencode時に元の順番に戻せるようにしたい。
101-
//暫定でgRPC over HTTP2のレスポンスの2つめのヘッダはGRPC_RESPONSE_2ND_HEADERSに従って元の順番に戻す。
102-
outData.writeTo(outHeader);
103-
Http http = new Http(outHeader.toByteArray());
83+
Http http = new Http(out.toByteArray());
10484
int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags"));
10585
if (http.getBody() == null || http.getBody().length == 0) {
10686
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
@@ -119,35 +99,10 @@ private byte[] encodeToFrames(byte[] data, HpackEncoder encoder) throws Exceptio
11999
int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags"));
120100
if (http.getBody() != null && http.getBody().length > 0) {
121101
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff & ~HeadersFrame.FLAG_END_STREAM));
122-
HttpFields GRPC2ndHeaderHttpFields = new HttpFields();
123-
for(String k:GRPC_RESPONSE_2ND_HEADERS){
124-
String v = http.getFirstHeader(k);
125-
http.removeHeader(k);
126-
if("".equals(v)) continue;
127-
GRPC2ndHeaderHttpFields.add(k, v);
128-
}
129-
130102
HeadersFrame headersFrame = new HeadersFrame(http);
131103
out.write(headersFrame.toByteArrayWithoutExtra(encoder));
132-
133104
DataFrame dataFrame = new DataFrame(http);
134-
if(GRPC2ndHeaderHttpFields.size()>0){
135-
dataFrame.setFlags(dataFrame.getFlags() & 0xff & ~DataFrame.FLAG_END_STREAM);
136-
}
137105
out.write(dataFrame.toByteArrayWithoutExtra());
138-
139-
if(GRPC2ndHeaderHttpFields.size()>0) {
140-
Http althttp = http;
141-
althttp.setBody(new byte[0]);
142-
althttp.removeMatches("^(?!X-PacketProxy-HTTP2).*$");
143-
144-
for (HttpField headerField : GRPC2ndHeaderHttpFields) {
145-
althttp.updateHeader(headerField.getName(), headerField.getValue());
146-
}
147-
althttp.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
148-
HeadersFrame headers2ndFrame = new HeadersFrame(althttp);
149-
out.write(headers2ndFrame.toByteArrayWithoutExtra(encoder));
150-
}
151106
} else {
152107
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
153108
HeadersFrame headersFrame = new HeadersFrame(http);

0 commit comments

Comments
 (0)