12
12
*/
13
13
package io .kubernetes .client ;
14
14
15
+ import com .google .protobuf .CodedOutputStream ;
15
16
import com .google .protobuf .Message ;
16
17
import com .google .protobuf .Message .Builder ;
17
18
import io .kubernetes .client .openapi .ApiClient ;
22
23
import io .kubernetes .client .proto .Meta .Status ;
23
24
import io .kubernetes .client .proto .Runtime .TypeMeta ;
24
25
import io .kubernetes .client .proto .Runtime .Unknown ;
25
- import io .kubernetes .client .util .Streams ;
26
26
import java .io .IOException ;
27
- import java .io .InputStream ;
28
27
import java .util .ArrayList ;
29
- import java .util .Arrays ;
30
28
import java .util .HashMap ;
31
29
import okhttp3 .MediaType ;
32
30
import okhttp3 .Request ;
33
31
import okhttp3 .RequestBody ;
34
32
import okhttp3 .Response ;
35
- import org .apache .commons .codec .binary .Hex ;
33
+ import okio .BufferedSource ;
34
+ import okio .ByteString ;
36
35
37
36
public class ProtoClient {
38
37
/**
@@ -62,9 +61,10 @@ public String toString() {
62
61
// Magic number for the beginning of proto encoded.
63
62
// https://github.com/kubernetes/apimachinery/blob/release-1.13/pkg/runtime/serializer/protobuf/protobuf.go#L44
64
63
private static final byte [] MAGIC = new byte [] {0x6b , 0x38 , 0x73 , 0x00 };
64
+ private static final ByteString MAGIC_BYTESTRING = ByteString .of (MAGIC );
65
65
private static final String MEDIA_TYPE = "application/vnd.kubernetes.protobuf" ;
66
66
67
- /** Simple Protocol Budder API client constructor, uses default configuration */
67
+ /** Simple Protocol Buffers API client constructor, uses default configuration */
68
68
public ProtoClient () {
69
69
this (Configuration .getDefaultApiClient ());
70
70
}
@@ -280,17 +280,19 @@ public <T extends Message> ObjectOrStatus<T> request(
280
280
281
281
private <T extends Message > ObjectOrStatus <T > getObjectOrStatusFromServer (
282
282
Builder builder , Request request ) throws IOException , ApiException {
283
- Response resp = apiClient .getHttpClient ().newCall (request ).execute ();
284
- Unknown u = parse (resp .body ().byteStream ());
285
- resp .body ().close ();
283
+ Unknown u ;
284
+ try (Response resp = apiClient .getHttpClient ().newCall (request ).execute ()) {
285
+ // Note: closing the response, closes the body and the underlying source.
286
+ u = parse (resp .body ().source ());
287
+ }
286
288
287
289
if (u .getTypeMeta ().getApiVersion ().equals ("v1" )
288
290
&& u .getTypeMeta ().getKind ().equals ("Status" )) {
289
291
Status status = Status .newBuilder ().mergeFrom (u .getRaw ()).build ();
290
- return new ObjectOrStatus (null , status );
292
+ return new ObjectOrStatus <> (null , status );
291
293
}
292
294
293
- return new ObjectOrStatus ((T ) builder .mergeFrom (u .getRaw ()).build (), null );
295
+ return new ObjectOrStatus <> ((T ) builder .mergeFrom (u .getRaw ()).build (), null );
294
296
}
295
297
296
298
// This isn't really documented anywhere except the code, but
@@ -301,7 +303,7 @@ private <T extends Message> ObjectOrStatus<T> getObjectOrStatusFromServer(
301
303
// encoding of the actual object.
302
304
// TODO: Document this somewhere proper.
303
305
304
- private byte [] encode (Message msg , String apiVersion , String kind ) {
306
+ private static byte [] encode (Message msg , String apiVersion , String kind ) throws IOException {
305
307
// It is unfortunate that we have to include apiVersion and kind,
306
308
// since we should be able to extract it from the Message, but
307
309
// for now at least, those fields are missing from the proto-buffer.
@@ -310,22 +312,27 @@ private byte[] encode(Message msg, String apiVersion, String kind) {
310
312
.setTypeMeta (TypeMeta .newBuilder ().setApiVersion (apiVersion ).setKind (kind ))
311
313
.setRaw (msg .toByteString ())
312
314
.build ();
313
- return concat (MAGIC , u .toByteArray ());
314
- }
315
315
316
- private static byte [] concat (byte [] one , byte [] two ) {
317
- byte [] result = new byte [one .length + two .length ];
318
- System .arraycopy (one , 0 , result , 0 , one .length );
319
- System .arraycopy (two , 0 , result , one .length , two .length );
316
+ // Encode directly to a sized array, to eliminate buffering
317
+ int serializedSize = u .getSerializedSize ();
318
+ byte [] result = new byte [MAGIC .length + u .getSerializedSize ()];
319
+ System .arraycopy (MAGIC , 0 , result , 0 , MAGIC .length );
320
+ u .writeTo (CodedOutputStream .newInstance (result , MAGIC .length , serializedSize ));
320
321
return result ;
321
322
}
322
323
323
- private Unknown parse (InputStream stream ) throws ApiException , IOException {
324
- byte [] magic = new byte [4 ];
325
- Streams .readFully (stream , magic );
326
- if (!Arrays .equals (magic , MAGIC )) {
327
- throw new ApiException ("Unexpected magic number: " + Hex .encodeHexString (magic ));
324
+ private static Unknown parse (BufferedSource responseBody ) throws ApiException , IOException {
325
+ if (!responseBody .request (MAGIC .length )) {
326
+ throw new ApiException ("Truncated reading magic number" );
327
+ }
328
+
329
+ // Check the magic without allocating a byte array
330
+ if (responseBody .rangeEquals (0 , MAGIC_BYTESTRING )) {
331
+ responseBody .skip (MAGIC .length );
332
+ } else {
333
+ ByteString badMagic = responseBody .readByteString (MAGIC .length );
334
+ throw new ApiException ("Unexpected magic number: " + badMagic .hex ());
328
335
}
329
- return Unknown .parseFrom (stream );
336
+ return Unknown .parseFrom (responseBody . inputStream () );
330
337
}
331
338
}
0 commit comments