diff --git a/pom.xml b/pom.xml
index fb04965..3bd0a34 100755
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.aliyun
aliyun-java-sdk-fc
jar
- 1.8.24
+ 1.8.31
aliyun-java-sdk-fc
https://www.aliyun.com/product/fc
Aliyun Java SDK for FunctionCompute
@@ -104,6 +104,11 @@
aliyun-java-sdk-fc-open
1.0.1
+
+ org.java-websocket
+ Java-WebSocket
+ 1.3.8
+
diff --git a/src/main/java/com/aliyuncs/fc/client/FunctionComputeClient.java b/src/main/java/com/aliyuncs/fc/client/FunctionComputeClient.java
index 40314f7..c0487c1 100644
--- a/src/main/java/com/aliyuncs/fc/client/FunctionComputeClient.java
+++ b/src/main/java/com/aliyuncs/fc/client/FunctionComputeClient.java
@@ -35,6 +35,10 @@
import com.aliyuncs.fc.utils.FcUtil;
import com.google.gson.Gson;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
import java.util.logging.Logger;
/**
@@ -658,4 +662,24 @@ public StopStatefulAsyncInvocationResponse stopStatefulAsyncInvocation(StopState
stopStatefulAsyncInvocationResponse.setStatus(response.getStatus());
return stopStatefulAsyncInvocationResponse;
}
+
+ public ListInstancesResponse listInstances(ListInstancesRequest request) throws ClientException, ServerException {
+ HttpResponse response = client.doAction(request, CONTENT_TYPE_APPLICATION_JSON, GET);
+ return ResponseFactory.genListInstancesResponse(response);
+ }
+
+ public InstanceExecResponse instanceExec(InstanceExecRequest request)
+ throws ClientException, ServerException, UnsupportedEncodingException, InvalidKeyException,
+ IllegalStateException, UnsupportedEncodingException, NoSuchAlgorithmException {
+ StringBuilder sb = new StringBuilder();
+ sb.append(config.getEndpoint().replace("http", "ws")).append(request.getPath()).append("?")
+ .append(request.getQueries());
+ URI uri = URI.create(sb.toString());
+
+ FcUtil.signRequest(config, request, "", GET, false);
+
+ ExecWebsocket ws = new ExecWebsocket(uri, request.getHeaders());
+ InstanceExecResponse response = new InstanceExecResponse(ws);
+ return response;
+ }
}
diff --git a/src/main/java/com/aliyuncs/fc/constants/Const.java b/src/main/java/com/aliyuncs/fc/constants/Const.java
index 4979249..c26c0f7 100644
--- a/src/main/java/com/aliyuncs/fc/constants/Const.java
+++ b/src/main/java/com/aliyuncs/fc/constants/Const.java
@@ -63,6 +63,10 @@ public class Const {
public final static String LIST_FUNCTION_WITH_QUALIFIER_STATEFUL_ASYNC_INVOCATIONS_PATH =
SINGLE_FUNCTION_WITH_QUALIFIER_PATH + "/stateful-async-invocations";
+ public final static String LIST_INSTANCES_PATH = SINGLE_FUNCTION_PATH+"/instances";
+ public final static String INSTANCE_EXEC_PATH = SINGLE_FUNCTION_PATH +"/instances/%s/exec";
+ public final static String LIST_INSTANCES_WITH_QUALIFIER_PATH = SINGLE_FUNCTION_WITH_QUALIFIER_PATH +"/instances";
+ public final static String INSTANCE_EXEC_WITH_QUALIFIER_PATH = SINGLE_FUNCTION_WITH_QUALIFIER_PATH +"/instances/%s/exec";
/**
* 3 seconds
*
diff --git a/src/main/java/com/aliyuncs/fc/model/ExecCallback.java b/src/main/java/com/aliyuncs/fc/model/ExecCallback.java
new file mode 100644
index 0000000..122e92d
--- /dev/null
+++ b/src/main/java/com/aliyuncs/fc/model/ExecCallback.java
@@ -0,0 +1,15 @@
+package com.aliyuncs.fc.model;
+
+import org.java_websocket.handshake.ServerHandshake;
+
+public interface ExecCallback {
+ public void onOpen(ServerHandshake handshakedata);
+
+ public void onClose(int code, String reason, boolean remote);
+
+ public void onError(Exception ex);
+
+ public void onStdout(String message);
+
+ public void onStderr(String message);
+}
diff --git a/src/main/java/com/aliyuncs/fc/model/ExecWebsocket.java b/src/main/java/com/aliyuncs/fc/model/ExecWebsocket.java
new file mode 100644
index 0000000..d0ddc03
--- /dev/null
+++ b/src/main/java/com/aliyuncs/fc/model/ExecWebsocket.java
@@ -0,0 +1,101 @@
+package com.aliyuncs.fc.model;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+
+public class ExecWebsocket extends WebSocketClient {
+ static final byte STDIN = 0;
+ static final byte STDOUT = 1;
+ static final byte STDERR = 2;
+ static final byte SYSERR = 3;
+
+ private ExecCallback callback = null;
+
+ public ExecWebsocket(URI url, Map httpHeaders) {
+ super(url, httpHeaders);
+ }
+
+ public void setCallback(ExecCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void onOpen(ServerHandshake handshakedata) {
+ if (callback != null) {
+ callback.onOpen(handshakedata);
+ } else {
+ System.out.println("onOpen");
+ }
+ }
+
+ @Override
+ public void onClose(int code, String reason, boolean remote) {
+ if (callback != null) {
+ callback.onClose(code, reason, remote);
+ } else {
+ System.out.printf("CLOSE: %d %s\n", code, reason);
+ }
+ }
+
+ @Override
+ public void onError(Exception ex) {
+ if (callback != null) {
+ callback.onError(ex);
+ } else {
+ System.out.printf("ERROR:%s\n", ex.getMessage());
+ }
+ }
+
+ @Override
+ public void onMessage(String message) {
+ switch (message.charAt(0)) {
+ case STDOUT:
+ onStdout(message.substring(1));
+ break;
+ case STDERR:
+ onStderr(message.substring(1));
+ break;
+ case SYSERR:
+ Exception e = new Exception(message.substring(1));
+ onError(e);
+ break;
+ default:
+ throw new RuntimeException("Unknown message type: " + message);
+ }
+ }
+
+ @Override
+ public void send(String message) {
+ super.send( message.getBytes());
+ }
+
+ @Override
+ public void send(byte[] message) {
+ byte[] buf = new byte[message.length + 1];
+ buf[0] = STDIN;
+ for (int i = 0; i < message.length; i++) {
+ buf[i+1] = message[i];
+ }
+ super.send(buf);
+ }
+
+ public void onStdout(String message) {
+ if (callback != null) {
+ callback.onStdout(message);
+ } else {
+ System.out.printf("STDOUT:%s\n", message);
+ }
+
+ }
+
+ public void onStderr(String message) {
+ if (callback != null) {
+ callback.onStderr(message);
+ } else {
+ System.out.printf("STDERR:%s\n", message);
+ }
+ }
+}
diff --git a/src/main/java/com/aliyuncs/fc/model/InstanceMetadata.java b/src/main/java/com/aliyuncs/fc/model/InstanceMetadata.java
new file mode 100644
index 0000000..c2e6e3e
--- /dev/null
+++ b/src/main/java/com/aliyuncs/fc/model/InstanceMetadata.java
@@ -0,0 +1,41 @@
+package com.aliyuncs.fc.model;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.Map;
+
+/**
+ * TODO: add javadoc
+ */
+public class InstanceMetadata {
+
+ @SerializedName("instanceId")
+ private String instanceID;
+
+ @SerializedName("versionId")
+ private String versionID;
+
+ public InstanceMetadata() {
+ this.instanceID = "";
+ }
+
+ public InstanceMetadata(String instanceID) {
+ this.instanceID = instanceID;
+ }
+
+ public String getInstanceID() {
+ return instanceID;
+ }
+
+ public void setInstanceID(String instanceId) {
+ this.instanceID = instanceId;
+ }
+
+
+ public String getVersionID() {
+ return versionID;
+ }
+
+ public void setVersionID(String versionID) {
+ this.versionID = versionID;
+ }
+}
diff --git a/src/main/java/com/aliyuncs/fc/request/InstanceExecRequest.java b/src/main/java/com/aliyuncs/fc/request/InstanceExecRequest.java
new file mode 100755
index 0000000..1182e1c
--- /dev/null
+++ b/src/main/java/com/aliyuncs/fc/request/InstanceExecRequest.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.aliyuncs.fc.request;
+
+import com.aliyuncs.fc.constants.Const;
+import com.aliyuncs.fc.exceptions.ClientException;
+import com.aliyuncs.fc.http.HttpRequest;
+import com.aliyuncs.fc.model.ListRequestUrlHelper;
+import com.aliyuncs.fc.response.ListFunctionsResponse;
+import com.aliyuncs.fc.response.ListInstancesResponse;
+import com.google.common.base.Strings;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.aliyuncs.fc.auth.AcsURLEncoder.encode;
+
+/**
+ * TODO: add javadoc
+ */
+public class InstanceExecRequest extends HttpRequest {
+
+ private final String serviceName;
+ private final String functionName;
+ private final String instanceID;
+
+ private String qualifier;
+ private Boolean stdin;
+ private Boolean stdout;
+ private Boolean stderr;
+ private Boolean tty;
+ private String[] command;
+
+ public InstanceExecRequest(String serviceName, String functionName, String instanceID) {
+ this.serviceName = serviceName;
+ this.functionName = functionName;
+ this.instanceID = instanceID;
+
+ // default params
+ qualifier = "LATEST";
+ stdin = true;
+ stdout = true;
+ stderr = true;
+ tty = false;
+ command = new String[] {};
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public String getFunctionName() {
+ return functionName;
+ }
+
+ public String getInstanceID() {
+ return instanceID;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public InstanceExecRequest setQualifier(String qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+
+ public Boolean getStdin() {
+ return stdin;
+ }
+
+ public InstanceExecRequest setStdin(Boolean stdin) {
+ this.stdin = stdin;
+ return this;
+ }
+
+ public Boolean getStdout() {
+ return stdout;
+ }
+
+ public InstanceExecRequest setStdout(Boolean stdout) {
+ this.stdout = stdout;
+ return this;
+ }
+
+ public Boolean getStderr() {
+ return stderr;
+ }
+
+ public InstanceExecRequest setStderr(Boolean stderr) {
+ this.stderr = stderr;
+ return this;
+ }
+
+ public Boolean getTty() {
+ return tty;
+ }
+
+ public InstanceExecRequest setTty(Boolean tty) {
+ this.tty = tty;
+ return this;
+ }
+
+ public String[] getCommand() {
+ return command;
+ }
+
+ public InstanceExecRequest setCommand(String[] Command) {
+ this.command = new String[Command.length];
+ for (int i = 0; i < Command.length; i++) {
+ this.command[i] = Command[i];
+ }
+ return this;
+ }
+
+ public String getPath() {
+ if (Strings.isNullOrEmpty(qualifier)) {
+ return String.format(Const.INSTANCE_EXEC_PATH, Const.API_VERSION, this.serviceName, this.functionName,this.instanceID);
+ } else {
+ return String.format(Const.INSTANCE_EXEC_WITH_QUALIFIER_PATH, Const.API_VERSION, this.serviceName,
+ this.qualifier, this.functionName,this.instanceID);
+ }
+ }
+
+ public Map getQueryParams() {
+ Map queries = new HashMap();
+ if (command != null && command.length > 0) {
+ queries.put("command", command[0]);
+ }
+ queries.put("stdin", stdin ? "true" : "false");
+ queries.put("stdout", stdout ? "true" : "false");
+ queries.put("stderr", stderr ? "true" : "false");
+ queries.put("tty", tty ? "true" : "false");
+ return queries;
+ }
+
+ public String getQueries() throws UnsupportedEncodingException {
+ StringBuilder sb = new StringBuilder();
+ if (command != null) {
+ for (String cmd : command) {
+ sb.append("command").append("=").append(encode(cmd)).append("&");
+ }
+ }
+ Map queries = getQueryParams();
+ for (Map.Entry entry : queries.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if (!key.equals("command")) {
+ sb.append(encode(key)).append("=").append(encode(value)).append("&");
+ }
+ }
+
+ if (sb.length() > 0) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+ public byte[] getPayload() {
+ return null;
+ }
+
+ public void validate() throws ClientException {
+ if (Strings.isNullOrEmpty(serviceName)) {
+ throw new ClientException("Service name cannot be blank");
+ }
+ }
+
+ public Class getResponseClass() {
+ return ListInstancesResponse.class;
+ }
+}
diff --git a/src/main/java/com/aliyuncs/fc/request/ListInstancesRequest.java b/src/main/java/com/aliyuncs/fc/request/ListInstancesRequest.java
new file mode 100755
index 0000000..d3f7396
--- /dev/null
+++ b/src/main/java/com/aliyuncs/fc/request/ListInstancesRequest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.aliyuncs.fc.request;
+
+import com.aliyuncs.fc.constants.Const;
+import com.aliyuncs.fc.exceptions.ClientException;
+import com.aliyuncs.fc.http.HttpRequest;
+import com.aliyuncs.fc.model.ListRequestUrlHelper;
+import com.aliyuncs.fc.response.ListFunctionsResponse;
+import com.aliyuncs.fc.response.ListInstancesResponse;
+import com.google.common.base.Strings;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: add javadoc
+ */
+public class ListInstancesRequest extends HttpRequest {
+
+ private final String serviceName;
+ private final String functionName;
+ private Integer limit;
+ private String qualifier;
+ private String[] instanceIds;
+ protected boolean queryUsingArray = true;
+
+ public ListInstancesRequest(String serviceName, String functionName) {
+ this.serviceName = serviceName;
+ this.functionName = functionName;
+ }
+
+ public Integer getLimit() {
+ return limit;
+ }
+
+ public ListInstancesRequest setLimit(Integer limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public String getQualifier() {
+ return qualifier;
+ }
+
+ public ListInstancesRequest setQualifier(String qualifier) {
+ this.qualifier = qualifier;
+ return this;
+ }
+
+ public String getFunctionName() {
+ return functionName;
+ }
+
+ public String[] getInstanceIds() {
+ return instanceIds;
+ }
+
+ public ListInstancesRequest setInstanceIds(String[] instanceIds) {
+ this.instanceIds = instanceIds;
+ return this;
+ }
+
+ public String getPath() {
+ if (Strings.isNullOrEmpty(qualifier)) {
+ return String.format(Const.LIST_INSTANCES_PATH, Const.API_VERSION, this.serviceName, this.functionName);
+ } else {
+ return String.format(Const.LIST_INSTANCES_WITH_QUALIFIER_PATH, Const.API_VERSION, this.serviceName,
+ this.qualifier, this.functionName);
+ }
+ }
+
+ public Map getQueryParams() {
+ Map queryParams = new HashMap();
+ Gson gson = new Gson();
+ if (this.limit != null && this.limit > 0) {
+ queryParams.put("limit", String.valueOf(this.limit));
+ }
+ if (this.instanceIds != null && this.instanceIds.length > 0) {
+ queryParams.put("instanceIds", gson.toJson(this.instanceIds));
+ }
+ return queryParams;
+ }
+
+ public byte[] getPayload() {
+ return null;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public void validate() throws ClientException {
+ if (Strings.isNullOrEmpty(serviceName)) {
+ throw new ClientException("Service name cannot be blank");
+ }
+ }
+
+ public Class getResponseClass() {
+ return ListInstancesResponse.class;
+ }
+}
diff --git a/src/main/java/com/aliyuncs/fc/response/InstanceExecResponse.java b/src/main/java/com/aliyuncs/fc/response/InstanceExecResponse.java
new file mode 100644
index 0000000..780c7e8
--- /dev/null
+++ b/src/main/java/com/aliyuncs/fc/response/InstanceExecResponse.java
@@ -0,0 +1,41 @@
+package com.aliyuncs.fc.response;
+
+import com.aliyuncs.fc.model.ExecWebsocket;
+import com.aliyuncs.fc.model.ExecCallback;
+
+public class InstanceExecResponse {
+ private final ExecWebsocket execWebsocket;
+
+ public InstanceExecResponse(ExecWebsocket ws) {
+ execWebsocket = ws;
+ execWebsocket.connect();
+ }
+
+ public ExecWebsocket getExecWebsocket() {
+ return execWebsocket;
+ }
+
+ public void setCallback(ExecCallback callback) {
+ execWebsocket.setCallback(callback);
+ }
+
+ public void send(String message) {
+ execWebsocket.send(message);
+ }
+
+ public void send(byte[] message) {
+ execWebsocket.send(message);
+ }
+
+ public void close() {
+ execWebsocket.close();
+ }
+
+ public void close(int code, String reason) {
+ execWebsocket.close(code, reason);
+ }
+
+ public void close(int code) {
+ execWebsocket.close(code);
+ }
+}
diff --git a/src/main/java/com/aliyuncs/fc/response/ListInstancesResponse.java b/src/main/java/com/aliyuncs/fc/response/ListInstancesResponse.java
new file mode 100755
index 0000000..50d8624
--- /dev/null
+++ b/src/main/java/com/aliyuncs/fc/response/ListInstancesResponse.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.aliyuncs.fc.response;
+
+import com.aliyuncs.fc.model.InstanceMetadata;
+import com.google.gson.annotations.SerializedName;
+import com.aliyuncs.fc.http.HttpResponse;
+
+
+/**
+ * TODO: add javadoc
+ */
+public class ListInstancesResponse extends HttpResponse {
+ @SerializedName("instances")
+ private InstanceMetadata[] instances = null;
+
+ private String nextToken = null;
+
+ public String getNextToken() {
+ return nextToken;
+ }
+
+ public ListInstancesResponse setNextToken(String nextToken) {
+ this.nextToken = nextToken;
+ return this;
+ }
+
+ public InstanceMetadata[] getInstances() {
+ return instances;
+ }
+
+ public ListInstancesResponse setInstances(InstanceMetadata[] instances) {
+ this.instances = instances;
+ return this;
+ }
+}
diff --git a/src/main/java/com/aliyuncs/fc/response/ResponseFactory.java b/src/main/java/com/aliyuncs/fc/response/ResponseFactory.java
index 5195307..5db25fc 100644
--- a/src/main/java/com/aliyuncs/fc/response/ResponseFactory.java
+++ b/src/main/java/com/aliyuncs/fc/response/ResponseFactory.java
@@ -158,4 +158,12 @@ public static DeleteServiceResponse genDeleteServiceResponse(HttpResponse respon
deleteServiceResponse.setStatus(response.getStatus());
return deleteServiceResponse;
}
+
+ public static ListInstancesResponse genListInstancesResponse(HttpResponse response) throws ClientException, ServerException {
+ ListInstancesResponse listInstancesResponse = GSON.fromJson(FcUtil.toDefaultCharset(response.getContent()), ListInstancesResponse.class);
+ listInstancesResponse.setHeaders(response.getHeaders());
+ listInstancesResponse.setContent(response.getContent());
+ listInstancesResponse.setStatus(response.getStatus());
+ return listInstancesResponse;
+ }
}