From 380928a58c167ff92776823b032d31dada711674 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Thu, 11 May 2017 21:51:38 -0700 Subject: [PATCH] Web socket support. --- .../kubernetes/client/examples/Example.java | 12 ++ .../client/examples/WebSocketsExample.java | 47 +++++++ kubernetes/.swagger-codegen-ignore | 5 +- .../java/io/kubernetes/client/ApiClient.java | 9 +- util/pom.xml | 48 ++++--- .../io/kubernetes/client/util/Config.java | 14 +- .../io/kubernetes/client/util/KubeConfig.java | 12 ++ .../io/kubernetes/client/util/WebSockets.java | 128 ++++++++++++++++++ .../io/kuberentes/client/util/ConfigTest.java | 12 ++ 9 files changed, 265 insertions(+), 22 deletions(-) create mode 100644 examples/src/main/java/io/kubernetes/client/examples/WebSocketsExample.java create mode 100644 util/src/main/java/io/kubernetes/client/util/WebSockets.java diff --git a/examples/src/main/java/io/kubernetes/client/examples/Example.java b/examples/src/main/java/io/kubernetes/client/examples/Example.java index 6f91ec957a..6ec68307e1 100644 --- a/examples/src/main/java/io/kubernetes/client/examples/Example.java +++ b/examples/src/main/java/io/kubernetes/client/examples/Example.java @@ -1,3 +1,15 @@ +/* +Copyright 2017 The Kubernetes Authors. +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 io.kubernetes.client.examples; import io.kubernetes.client.ApiClient; diff --git a/examples/src/main/java/io/kubernetes/client/examples/WebSocketsExample.java b/examples/src/main/java/io/kubernetes/client/examples/WebSocketsExample.java new file mode 100644 index 0000000000..ef94ace63d --- /dev/null +++ b/examples/src/main/java/io/kubernetes/client/examples/WebSocketsExample.java @@ -0,0 +1,47 @@ +/* +Copyright 2017 The Kubernetes Authors. +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 io.kubernetes.client.examples; + +import io.kubernetes.client.ApiClient; +import io.kubernetes.client.ApiException; +import io.kubernetes.client.util.Config; +import io.kubernetes.client.util.WebSockets; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +public class WebSocketsExample { + public static void main(String... args) throws ApiException, IOException { + final ApiClient client = Config.defaultClient(); + WebSockets.stream(args[0], "GET", client, new WebSockets.SocketListener() { + public void open() {} + public void close() { + // Trigger shutdown of the dispatcher's executor so this process can exit cleanly. + client.getHttpClient().getDispatcher().getExecutorService().shutdown(); + } + public void bytesMessage(InputStream is) {} + public void textMessage(Reader in) { + try { + BufferedReader reader = new BufferedReader(in); + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + System.out.println(line); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + }); + } +} \ No newline at end of file diff --git a/kubernetes/.swagger-codegen-ignore b/kubernetes/.swagger-codegen-ignore index c8c706c4ae..91615308e0 100644 --- a/kubernetes/.swagger-codegen-ignore +++ b/kubernetes/.swagger-codegen-ignore @@ -1,7 +1,8 @@ .gitignore git_push.sh # Remove this once swagger-codegen 2.2.3 is released and we update. -# We want https://github.com/swagger-api/swagger-codegen/pull/5629 -# in the release. +# Verify the following PRs are in the release: +# * https://github.com/swagger-api/swagger-codegen/pull/5629 +# * https://github.com/swagger-api/swagger-codegen/pull/5648 src/main/java/io/kubernetes/client/ApiClient.java diff --git a/kubernetes/src/main/java/io/kubernetes/client/ApiClient.java b/kubernetes/src/main/java/io/kubernetes/client/ApiClient.java index 4c761d130d..430f077546 100644 --- a/kubernetes/src/main/java/io/kubernetes/client/ApiClient.java +++ b/kubernetes/src/main/java/io/kubernetes/client/ApiClient.java @@ -1069,6 +1069,12 @@ public T handleResponse(Response response, Type returnType) throws ApiExcept * @throws ApiException If fail to serialize the request body object */ public Call buildCall(String path, String method, List queryParams, Object body, Map headerParams, Map formParams, String[] authNames, ProgressRequestBody.ProgressRequestListener progressRequestListener) throws ApiException { + Request request = buildRequest(path, method, queryParams, body, headerParams, formParams, authNames, progressRequestListener); + + return httpClient.newCall(request); + } + + public Request buildRequest(String path, String method, List queryParams, Object body, Map headerParams, Map formParams, String[] authNames, ProgressRequestBody.ProgressRequestListener progressRequestListener) throws ApiException { updateParamsForAuth(authNames, queryParams, headerParams); final String url = buildUrl(path, queryParams); @@ -1108,8 +1114,7 @@ public Call buildCall(String path, String method, List queryParams, Object } else { request = reqBuilder.method(method, reqBody).build(); } - - return httpClient.newCall(request); + return request; } /** diff --git a/util/pom.xml b/util/pom.xml index 2f3bccc209..25cc45ef5d 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -24,6 +24,16 @@ commons-codec 1.10 + + com.squareup.okhttp + okhttp-ws + 2.7.5 + + + com.google.guava + guava + 22.0 + junit @@ -31,7 +41,6 @@ 4.12 test - com.github.stefanbirkner system-rules @@ -49,22 +58,27 @@ 1.7 - - org.apache.maven.plugins - maven-surefire-plugin - 2.12 - - - - loggerPath - conf/log4j.properties - - - -Xms512m -Xmx1500m - methods - pertest - - + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + loggerPath + conf/log4j.properties + + + -Xms512m -Xmx1500m + methods + pertest + + + + 1.7 + ${java.version} + ${java.version} + \ No newline at end of file diff --git a/util/src/main/java/io/kubernetes/client/util/Config.java b/util/src/main/java/io/kubernetes/client/util/Config.java index ff2b774055..030eedd206 100644 --- a/util/src/main/java/io/kubernetes/client/util/Config.java +++ b/util/src/main/java/io/kubernetes/client/util/Config.java @@ -1,3 +1,15 @@ +/* +Copyright 2017 The Kubernetes Authors. +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 io.kubernetes.client.util; import io.kubernetes.client.ApiClient; @@ -163,4 +175,4 @@ public static ApiClient defaultClient() throws IOException { client.setBasePath(DEFAULT_FALLBACK_HOST); return client; } -} \ No newline at end of file +} diff --git a/util/src/main/java/io/kubernetes/client/util/KubeConfig.java b/util/src/main/java/io/kubernetes/client/util/KubeConfig.java index 3f184ae4f7..592aadb78f 100644 --- a/util/src/main/java/io/kubernetes/client/util/KubeConfig.java +++ b/util/src/main/java/io/kubernetes/client/util/KubeConfig.java @@ -1,3 +1,15 @@ +/* +Copyright 2017 The Kubernetes Authors. +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 io.kubernetes.client.util; import java.io.File; diff --git a/util/src/main/java/io/kubernetes/client/util/WebSockets.java b/util/src/main/java/io/kubernetes/client/util/WebSockets.java new file mode 100644 index 0000000000..18b02080a3 --- /dev/null +++ b/util/src/main/java/io/kubernetes/client/util/WebSockets.java @@ -0,0 +1,128 @@ +/* +Copyright 2017 The Kubernetes Authors. +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 io.kubernetes.client.util; + +import io.kubernetes.client.ApiClient; +import io.kubernetes.client.ApiException; +import io.kubernetes.client.Pair; + +import com.google.common.net.HttpHeaders; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import com.squareup.okhttp.ResponseBody; +import com.squareup.okhttp.ws.WebSocket; +import com.squareup.okhttp.ws.WebSocketCall; +import com.squareup.okhttp.ws.WebSocketListener; +import okio.Buffer; + +import static com.squareup.okhttp.ws.WebSocket.BINARY; +import static com.squareup.okhttp.ws.WebSocket.TEXT; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.HashMap; + +public class WebSockets { + public static final String V4_STREAM_PROTOCOL = "v4.channel.k8s.io"; + public static final String V3_STREAM_PROTOCOL = "v3.channel.k8s.io"; + public static final String V2_STREAM_PROTOCOL = "v2.channel.k8s.io"; + public static final String V1_STREAM_PROTOCOL = "channel.k8s.io"; + public static final String STREAM_PROTOCOL_HEADER = "X-Stream-Protocol-Version"; + public static final String SPDY_3_1 = "SPDY/3.1"; + + /** + * A simple interface for a listener on a web socket + */ + public interface SocketListener { + /** + * Called when the socket is opened + */ + public void open(); + + /** + * Callled when a binary media type message is received + * @param in The input stream containing the binary data + */ + public void bytesMessage(InputStream in); + + /** + * Called when a text media type message is received + * @param in The character stream containing the message + */ + public void textMessage(Reader in); + + /** + * Called when the stream is closed. + */ + public void close(); + } + + /** + * Create a new WebSocket stream + * @param path The HTTP Path to request from the API + * @param method The HTTP method to use for the call + * @param client The ApiClient for communicating with the API + * @param listener The socket listener to handle socket events + */ + public static void stream(String path, String method, ApiClient client, SocketListener listener) throws ApiException, IOException { + HashMap headers = new HashMap(); + String allProtocols = String.format("%s,%s,%s,%s", V4_STREAM_PROTOCOL, V3_STREAM_PROTOCOL, V2_STREAM_PROTOCOL, V1_STREAM_PROTOCOL); + headers.put(STREAM_PROTOCOL_HEADER, allProtocols); + headers.put(HttpHeaders.CONNECTION, HttpHeaders.UPGRADE); + headers.put(HttpHeaders.UPGRADE, SPDY_3_1); + + Request request = client.buildRequest(path, method, new ArrayList(), null, headers, new HashMap(), new String[0], null); + WebSocketCall.create(client.getHttpClient(), request).enqueue(new Listener(listener)); + } + + private static class Listener implements WebSocketListener { + private SocketListener listener; + + public Listener(SocketListener listener) { + this.listener = listener; + } + + @Override + public void onOpen(WebSocket webSocket, Response response) { + listener.open(); + } + + @Override + public void onMessage(ResponseBody body) throws IOException { + if (body.contentType() == TEXT) { + listener.bytesMessage(body.byteStream()); + } else if (body.contentType() == BINARY) { + listener.textMessage(body.charStream()); + } + body.close(); + } + + @Override + public void onPong(Buffer payload) { + } + + @Override + public void onClose(int code, String reason) { + listener.close(); + } + + @Override + public void onFailure(IOException e, Response res) { + e.printStackTrace(); + listener.close(); + } + } +} diff --git a/util/src/test/java/io/kuberentes/client/util/ConfigTest.java b/util/src/test/java/io/kuberentes/client/util/ConfigTest.java index 1e4296abf7..eede9aee42 100644 --- a/util/src/test/java/io/kuberentes/client/util/ConfigTest.java +++ b/util/src/test/java/io/kuberentes/client/util/ConfigTest.java @@ -1,3 +1,15 @@ +/* +Copyright 2017 The Kubernetes Authors. +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 io.kubernetes.client.util; import io.kubernetes.client.ApiClient;