31
31
import java .nio .BufferOverflowException ;
32
32
import java .nio .ByteBuffer ;
33
33
import java .nio .channels .SelectionKey ;
34
+ import java .nio .charset .StandardCharsets ;
34
35
import java .util .Deque ;
35
36
import java .util .Iterator ;
36
37
import java .util .List ;
38
+ import java .util .Map ;
37
39
import java .util .Queue ;
40
+ import java .util .concurrent .ConcurrentHashMap ;
38
41
import java .util .concurrent .ConcurrentLinkedDeque ;
39
42
import java .util .concurrent .ConcurrentLinkedQueue ;
40
43
import java .util .concurrent .atomic .AtomicInteger ;
68
71
import org .apache .hc .core5 .http .nio .command .StaleCheckCommand ;
69
72
import org .apache .hc .core5 .http .protocol .HttpContext ;
70
73
import org .apache .hc .core5 .http .protocol .HttpProcessor ;
74
+ import org .apache .hc .core5 .http .HttpHeaders ;
71
75
import org .apache .hc .core5 .http2 .H2ConnectionException ;
72
76
import org .apache .hc .core5 .http2 .H2Error ;
73
77
import org .apache .hc .core5 .http2 .H2StreamResetException ;
85
89
import org .apache .hc .core5 .http2 .nio .AsyncPingHandler ;
86
90
import org .apache .hc .core5 .http2 .nio .command .PingCommand ;
87
91
import org .apache .hc .core5 .http2 .nio .command .PushResponseCommand ;
92
+ import org .apache .hc .core5 .http2 .priority .PriorityParamsParser ;
93
+ import org .apache .hc .core5 .http2 .priority .PriorityValue ;
94
+ import org .apache .hc .core5 .http2 .priority .PriorityFormatter ;
88
95
import org .apache .hc .core5 .io .CloseMode ;
89
96
import org .apache .hc .core5 .reactor .Command ;
90
97
import org .apache .hc .core5 .reactor .ProtocolIOSession ;
96
103
97
104
abstract class AbstractH2StreamMultiplexer implements Identifiable , HttpConnection {
98
105
99
- private static final long CONNECTION_WINDOW_LOW_MARK = 10 * 1024 * 1024 ; // 10 MiB
106
+ private static final long CONNECTION_WINDOW_LOW_MARK = 10 * 1024 * 1024 ;
100
107
101
108
enum ConnectionHandshake { READY , ACTIVE , GRACEFUL_SHUTDOWN , SHUTDOWN }
102
109
enum SettingsHandshake { READY , TRANSMITTED , ACKED }
@@ -135,6 +142,9 @@ enum SettingsHandshake { READY, TRANSMITTED, ACKED }
135
142
private EndpointDetails endpointDetails ;
136
143
private boolean goAwayReceived ;
137
144
145
+ private final Map <Integer , PriorityValue > priorities = new ConcurrentHashMap <>();
146
+ private volatile boolean peerNoRfc7540Priorities ;
147
+
138
148
AbstractH2StreamMultiplexer (
139
149
final ProtocolIOSession ioSession ,
140
150
final FrameFactory frameFactory ,
@@ -902,15 +912,13 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio
902
912
consumeSettingsFrame (payload );
903
913
remoteSettingState = SettingsHandshake .TRANSMITTED ;
904
914
}
905
- // Send ACK
906
915
final RawFrame response = frameFactory .createSettingsAck ();
907
916
commitFrame (response );
908
917
remoteSettingState = SettingsHandshake .ACKED ;
909
918
}
910
919
}
911
920
break ;
912
921
case PRIORITY :
913
- // Stream priority not supported
914
922
break ;
915
923
case PUSH_PROMISE : {
916
924
acceptPushFrame ();
@@ -995,6 +1003,29 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio
995
1003
}
996
1004
ioSession .setEvent (SelectionKey .OP_WRITE );
997
1005
break ;
1006
+ case PRIORITY_UPDATE : {
1007
+ if (streamId != 0 ) {
1008
+ throw new H2ConnectionException (H2Error .PROTOCOL_ERROR , "PRIORITY_UPDATE must be on stream 0" );
1009
+ }
1010
+ final ByteBuffer payload = frame .getPayload ();
1011
+ if (payload == null || payload .remaining () < 4 ) {
1012
+ throw new H2ConnectionException (H2Error .FRAME_SIZE_ERROR , "Invalid PRIORITY_UPDATE payload" );
1013
+ }
1014
+ final int prioritizedId = payload .getInt () & 0x7fffffff ;
1015
+ final int len = payload .remaining ();
1016
+ final String field ;
1017
+ if (len > 0 ) {
1018
+ final byte [] b = new byte [len ];
1019
+ payload .get (b );
1020
+ field = new String (b , StandardCharsets .US_ASCII );
1021
+ } else {
1022
+ field = "" ;
1023
+ }
1024
+ final PriorityValue pv = PriorityParamsParser .parse (field ).toValueWithDefaults ();
1025
+ priorities .put (prioritizedId , pv );
1026
+ requestSessionOutput ();
1027
+ }
1028
+ break ;
998
1029
}
999
1030
}
1000
1031
@@ -1059,7 +1090,6 @@ private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) thr
1059
1090
}
1060
1091
final ByteBuffer payload = frame .getPayloadContent ();
1061
1092
if (frame .isFlagSet (FrameFlag .PRIORITY )) {
1062
- // Priority not supported
1063
1093
payload .getInt ();
1064
1094
payload .get ();
1065
1095
}
@@ -1068,6 +1098,7 @@ private void consumeHeaderFrame(final RawFrame frame, final H2Stream stream) thr
1068
1098
if (streamListener != null ) {
1069
1099
streamListener .onHeaderInput (this , streamId , headers );
1070
1100
}
1101
+ recordPriorityFromHeaders (streamId , headers );
1071
1102
stream .consumeHeader (headers , frame .isFlagSet (FrameFlag .END_STREAM ));
1072
1103
} else {
1073
1104
continuation .copyPayload (payload );
@@ -1086,6 +1117,7 @@ private void consumeContinuationFrame(final RawFrame frame, final H2Stream strea
1086
1117
if (streamListener != null ) {
1087
1118
streamListener .onHeaderInput (this , streamId , headers );
1088
1119
}
1120
+ recordPriorityFromHeaders (streamId , headers );
1089
1121
if (continuation .type == FrameType .PUSH_PROMISE .getValue ()) {
1090
1122
stream .consumePromise (headers );
1091
1123
} else {
@@ -1142,6 +1174,9 @@ private void consumeSettingsFrame(final ByteBuffer payload) throws IOException {
1142
1174
throw new H2ConnectionException (H2Error .PROTOCOL_ERROR , ex .getMessage ());
1143
1175
}
1144
1176
break ;
1177
+ case SETTINGS_NO_RFC7540_PRIORITIES :
1178
+ peerNoRfc7540Priorities = value == 1 ;
1179
+ break ;
1145
1180
}
1146
1181
}
1147
1182
}
@@ -1334,6 +1369,38 @@ H2Stream createStream(final H2StreamChannel channel, final H2StreamHandler strea
1334
1369
return streams .createActive (channel , streamHandler );
1335
1370
}
1336
1371
1372
+ public final void sendPriorityUpdate (final int prioritizedStreamId , final PriorityValue value ) throws IOException {
1373
+ if (value == null ) {
1374
+ return ;
1375
+ }
1376
+ final String field = PriorityFormatter .format (value );
1377
+ if (field == null ) {
1378
+ return ;
1379
+ }
1380
+ final byte [] ascii = field .getBytes (StandardCharsets .US_ASCII );
1381
+ final ByteArrayBuffer buf = new ByteArrayBuffer (4 + ascii .length );
1382
+ buf .append ((byte ) (prioritizedStreamId >> 24 ));
1383
+ buf .append ((byte ) (prioritizedStreamId >> 16 ));
1384
+ buf .append ((byte ) (prioritizedStreamId >> 8 ));
1385
+ buf .append ((byte ) prioritizedStreamId );
1386
+ buf .append (ascii , 0 , ascii .length );
1387
+ final RawFrame frame = frameFactory .createPriorityUpdate (ByteBuffer .wrap (buf .array (), 0 , buf .length ()));
1388
+ commitFrame (frame );
1389
+ }
1390
+
1391
+ private void recordPriorityFromHeaders (final int streamId , final List <? extends Header > headers ) {
1392
+ if (headers == null || headers .isEmpty ()) {
1393
+ return ;
1394
+ }
1395
+ for (final Header h : headers ) {
1396
+ if (HttpHeaders .PRIORITY .equalsIgnoreCase (h .getName ())) {
1397
+ final PriorityValue pv = PriorityParamsParser .parse (h .getValue ()).toValueWithDefaults ();
1398
+ priorities .put (streamId , pv );
1399
+ break ;
1400
+ }
1401
+ }
1402
+ }
1403
+
1337
1404
class H2StreamChannelImpl implements H2StreamChannel {
1338
1405
1339
1406
private final int id ;
@@ -1381,6 +1448,25 @@ public void submit(final List<Header> headers, final boolean endStream) throws I
1381
1448
return ;
1382
1449
}
1383
1450
ensureNotClosed ();
1451
+ if (peerNoRfc7540Priorities && streams .isSameSide (id )) {
1452
+ for (final Header h : headers ) {
1453
+ if (HttpHeaders .PRIORITY .equalsIgnoreCase (h .getName ())) {
1454
+ final byte [] ascii = h .getValue () != null
1455
+ ? h .getValue ().getBytes (StandardCharsets .US_ASCII )
1456
+ : new byte [0 ];
1457
+ final ByteArrayBuffer b = new ByteArrayBuffer (4 + ascii .length );
1458
+ b .append ((byte ) (id >> 24 ));
1459
+ b .append ((byte ) (id >> 16 ));
1460
+ b .append ((byte ) (id >> 8 ));
1461
+ b .append ((byte ) id );
1462
+ b .append (ascii , 0 , ascii .length );
1463
+ final ByteBuffer pl = ByteBuffer .wrap (b .array (), 0 , b .length ());
1464
+ final RawFrame priUpd = new RawFrame (FrameType .PRIORITY_UPDATE .getValue (), 0 , 0 , pl );
1465
+ commitFrameInternal (priUpd );
1466
+ break ;
1467
+ }
1468
+ }
1469
+ }
1384
1470
commitHeaders (id , headers , endStream );
1385
1471
if (endStream ) {
1386
1472
localClosed = true ;
@@ -1518,4 +1604,4 @@ public String toString() {
1518
1604
1519
1605
}
1520
1606
1521
- }
1607
+ }
0 commit comments