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; + } }