All Products
Search
Document Center

Understand how an SDK implements bidirectional communication

Last Updated: Sep 28, 2020

API Gateway provides the bidirectional communication capability. However, the SDKs that are automatically generated by API Gateway are limited to a few coding languages. If you need SDKs for other coding languages, you must develop them yourself. This topic describes in detail the SDKs that are automatically generated by API Gateway for bidirectional communication. This helps you develop SDKs for other coding languages. API Gateway provides SDKs for Android, Objective-C, and Java to support bidirectional communication. You can develop your own SDK in another coding language, as instructed in this topic, based on the SDKs that are provided by API Gateway.

For information about how to use the bidirectional communication feature of API Gateway, see Implement bidirectional communication .

1. Communications protocol and channel command words

In bidirectional communication, a client communicates with API Gateway by using a WebSocket channel. The WebSocket channel supports two communications methods: calling API operations and sending commands. This section describes the communications rules and channel command words that are configured for the WebSocket channel.

1.1 Format conversion of API requests

In bidirectional communication, each API request or response must be transmitted as a set of JSON strings. The WebSocket channel between a client and API Gateway converts each HTTP request to a set of JSON strings and sends the JSON strings to API Gateway. The following code snippet shows the conversion logic:

public class WebSocketApiRequest {
    String method;
    String host;
    String path;
    Map<String , String> querys;
    Map<String, List<String>> headers;
    int isBase64 = 0;
    String body;
}

For example, the following code snippet shows a standard HTTP request:

POST /http2test/test? param1=test HTTP/1.1  
host: apihangzhou444.foundai.com  
x-ca-seq:0 
x-ca-key:12344133
ca_version:1
content-type:application/x-www-form-urlencoded; charset=utf-8
x-ca-timestamp:1525872629832
date:Wed, 09 May 2018 13:30:29 GMT+00:00
x-ca-signature:kYjdIuCnCrxx+EyLMTTx5NiXxqvfTTHBQAe68tv33Tw=
user-agent:ALIYUN-ANDROID-DEMO
x-ca-nonce:c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44
x-ca-signature-headers:x-ca-seq,x-ca-key,x-ca-timestamp,x-ca-nonce
content-length:33

username=xiaoming&password=123456789  

The WebSocket protocol converts the HTTP request to a set of JSON strings, as shown in the following code snippet, and then sends the JSON strings to API Gateway.

{
	"headers": {
		"accept": ["application/json; charset=utf-8"],
		"host": ["apihangzhou444.foundai.com"],
		"x-ca-seq": ["0"],
		"x-ca-key": ["12344133"],
		"ca_version": ["1"],
		"content-type": ["application/x-www-form-urlencoded; charset=utf-8"],
		"x-ca-timestamp": ["1525872629832"],
		"date": ["Wed, 09 May 2018 13:30:29 GMT+00:00"],
		"x-ca-signature": ["kYjdIuCnCrxx+EyLMTTx5NiXxqvfTTHBQAe68tv33Tw="],
		"user-agent": ["ALIYUN-ANDROID-DEMO"],
		"x-ca-nonce": ["c9f15cbf-f4ac-4a6c-b54d-f51abf4b5b44"],
		"x-ca-signature-headers": ["x-ca-seq,x-ca-key,x-ca-timestamp,x-ca-nonce"]
	},
	"host": "apihangzhou444.foundai.com",
	"isBase64": 0,
	"method": "POST",
	"path": "/http2test/test",
	"querys": {"param1":"test"},
	"body":"username=xiaoming&password=123456789"
}

1.2 Channel commands

In bidirectional communication, apart from calling API operations, a client can also send channel commands. This section describes the channel commands that are supported between a client and API Gateway.

1.2.1 Commands related to client registration

Command word: RG  
Description: establishes a persistent connection between a client and API Gateway. This command must include the device ID of the client.
Command type: request  
Sender: client  
Format: RG#DeviceId  
Example: RG#ffd3234343dae324342@12344133

Command word: RO  
Description: returns a success response to indicate that the persistent connection has been established and API Gateway has received the device ID of the client. This command must include the unique identifier of the persistent connection and the required heartbeat interval.
Command type: response  
Sender: API Gateway  
Format: RO#ConnectionCredential#keepAliveInterval  
Example: RO#1534692949977#25000

Command word: RF
Description: returns an error response to indicate that the connection failed to be established.
Command type: response
Sender: API Gateway
Format: RF#ErrorMessage
Example: RF#ServerError

1.2.2 Commands related to a client's heartbeat

Command word: H1
Description: sends a heartbeat signal to API Gateway.
Command type: request
Sender: client
This command requires no parameters. You only need to send the command word.

Command word: HO
Description: returns a success response to indicate that API Gateway has received the heartbeat signal.
Command type: response
Sender: API Gateway
Format: HO#ConnectionCredential
Example: HO#ffd3234343dae324342

1.2.3 Commands related to server-to-client notifications

Command word: NF
Description: sends a request to push a server-to-client notification.
Command type: request
Sender: API Gateway
Format: NF#MESSAGE
Example: NF#HELLO WORLD!

Command word: NO
Description: returns a response to indicate that the client has received a server-to-client notification.
Command type: response
Sender: client
This command requires no parameters. You only need to send the command word.

1.2.4 Other commands

Command word: OS
Description: If the number of API requests from a client reaches a threshold that is specified by a throttling policy in API Gateway, API Gateway sends this command to the client. The client must cut off the connection and register again. This process will not affect user experience. If the client itself does not complete this process after it receives this command, the connection will be cut off by API Gateway not long after.
Command type: request
Sender: API Gateway
This command requires no parameters. You only need to send the command word.

Command word: CR
Description: If the persistent connection between a client and API Gateway reaches the end of its lifecycle, API Gateway sends this command to the client. The client must cut off the connection and register again. This process will not affect user experience. If the client itself does not complete this process after it receives this command, the connection will be cut off by API Gateway not long after.
Command type: request
Sender: API Gateway
This command requires no parameters. You only need to send the command word.

1.2.5 Unique identifier of a device

The device ID of a client is used to uniquely identify the persistent connection between the client and API Gateway. The format of a device ID must be UUID@AppKey. The following code snippet shows how to use Java code to specify a device ID. For the same application, each device ID must be unique among all the clients that use the application. Namely, for the same application, all device IDs must be different from each other. If two clients have the same device ID, an error will occur during registration.

String deviceId = UUID.randomUUID().toString().replace("-" , "")+ "@" + appKey;

For example, a device ID can be ffd3234343dae324342@12344133.

2. Communications process

The following figure shows the interaction between API Gateway and an SDK that is installed on a client.

As shown in the figure, the SDK on the client is required to complete the following operations:

  • Establish a WebSocket connection, including protocol negotiation.

  • Send an RG command to register the device ID of the client.

  • Call the API operation for registration signaling.

  • Enable a heartbeat thread and regularly send a heartbeat signal, by sending an H1 command, to API Gateway.

  • Call other API operations.

  • Receive notifications that are pushed by API Gateway.

  • Call the API operation for logoff signaling.

The SDK on the client is also required to handle the following exceptions:

  • After the persistent connection between the client and API Gateway reaches the end of its lifecycle, the SDK needs to cut off the connection and register again.

  • After the number of API requests from the client reaches a threshold that is specified by a throttling policy in API Gateway, the SDK needs to cut off the connection and register again.

2.1 Establish a WebSocket connection, including protocol negotiation

API Gateway implements bidirectional communication based on the WebSocket protocol. Each connection between a client and API Gateway is a standard WebSocket connection. For information about how to establish a WebSocket connection, see The WebSocket Protocol.

The WebSocket protocol can be implemented by using common coding languages. For example, Android applications use OkHttp to implement the WebSocket protocol.

2.2 Send an RG command to register the device ID of the client

After the WebSocket connection is established, the client sends a message to API Gateway to inform API Gateway of the device ID of the client.

After API Gateway receives the message, API Gateway establishes a persistent connection at the access layer and uses the device ID of the client to identify this persistent connection. Then, API Gateway returns a success response to the client, indicating that the connection has been established.

For information about the commands that are involved in this process, see section 1.2.1.

2.3 Call the API operation for registration signaling

After the WebSocket connection is established, the client sends a registration signal, namely, calls the API operation for registration signaling. You must have created the API operation for registration signaling in API Gateway in advance. For information about how to create the API operation for registration signaling, see Part three of Implement bidirectional communication.

For information about the format of an API request, see section 1.1. When you send a request for the API operation for registration signaling, take note of the following items:

  1. Send the request only after API Gateway sends an RO command in response to the RG command from the client.

  2. Add the following header field in the request to identify the API operation for registration signaling: x-ca-websocket_api_type:REGISTER.

2.4 Enable a heartbeat thread and regularly send a heartbeat signal, by sending an H1 command, to API Gateway

The client is required to send a heartbeat signal to API Gateway every 25 seconds. Each time API Gateway receives a heartbeat signal, API Gateway updates the lifespan of the connection between the client and API Gateway, and then returns a success response to indicate that the heartbeat signal has been received and handled.

For information about the commands that are involved in this process, see section 1.2.2.

2.5 Call other API operations

After the WebSocket connection is established, the client can also call other API operations that are unrelated to bidirectional communication. For information about the format of an API request, see section 1.1.

2.6 Receive notifications that are pushed by API Gateway

After the client sends a request for the API operation for registration signaling and receives a 200 response from API Gateway, the client is ready to receive server-to-client notifications from API Gateway. For information about the commands that are related to sending and receiving server-to-client notifications, see section 1.2.3.

2.7 Call the API operation for logoff signaling

To log off from the application, the client needs to send a request for the API operation for logoff signaling. You must have created the API operation for logoff signaling in API Gateway in advance. For information about how to create the API operation for registration signaling, see Part three of Implement bidirectional communication.

For information about the format of an API request, see section 1.1. When you send a request for the API operation for logoff signaling, add the following header field in the request to identify the API operation for logoff signaling: x-ca-websocket_api_type:UNREGISTER.

2.8 Handle a CR command that is sent from API Gateway because of connection expiration

Each persistent connection has a lifecycle. The lifecycle of a persistent connection to API Gateway ends after 2,000 API requests are sent by using this connection. When the number of API requests that are sent by using a persistent connection reaches 1,500, API Gateway sends a CR command to the client, which requires the client to cut off the connection and register again. For more information about the CR command, see section 1.2.4. If the client does not cut off the connection and register again, API Gateway will delete the connection when the number of API requests that are sent by using this connection reaches 2,000.

After a client receives a CR command or an OS command from API Gateway, the client stops receiving API requests and continues to send the API requests that are already received to API Gateway. Then, the client cuts off the current connection, establishes another connection with API Gateway, and then sends a registration signal to register again. We recommend that when you register a client for the first time, you cache the registration request. In this way, every time after you disconnect the client from API Gateway, you can use the cache to register again more conveniently.

2.9 Handle an OS command that is sent by API Gateway because of throttling

API Gateway supports throttling. If the number of requests per second (RPS) on a client exceeds a threshold that is specified by a throttling policy, API Gateway sends an OS command to the client. For more information about the OS command, see section 1.2.4. To avoid receiving an OS command, the client must control the number of RPS. If the OS command is ignored and the number of RPS on the client continues to increase, API Gateway will delete the persistent connection between the client and API Gateway.

3. Summary

3.1 Process summary

The following figure shows the process of bidirectional communication in detail.

Note: The request format and response format of an API operation that is unrelated to bidirectional communication are the same as those of the API operation for registration signaling. Therefore, the figure does not provide examples of a request and a response of an API operation that is unrelated to bidirectional communication.

3.2 Sample code

API Gateway provides SDKs for Android, Objective-C, and Java to support the bidirectional communication capability. Each SDK is implemented strictly based on the communications rules and process that are described in the preceding sections. This section uses the SDK for Android as an example and provides sample code for part of the process in section 2.

3.2.1 Establish a WebSocket connection, including protocol negotiation

// Use a domain name and port 8080 as the connection address. The domain name is an independent domain name that is bound to the API group to which the API operations for registration signaling, server-to-client notification signaling, and logoff signaling belong.
String websocketUrl = "ws:" + yourdomain + ":8080";

// Create a client object.
OkHttpClient client = new OkHttpClient.Builder()
                .readTimeout(params.getReadTimeout(), TimeUnit.MILLISECONDS)
                .writeTimeout(params.getWriteTimeout(), TimeUnit.MILLISECONDS)
                .connectTimeout(params.getConnectionTimeout(), TimeUnit.MILLISECONDS)
                .build();
                
// Construct an HTTP request that is used to establish a WebSocket connection between the client and API Gateway.
Request connectRequest = new Request.Builder().url(websocketUrl).build();

// Create a WebSocketListener class to be used to listen on server-to-client notifications after the WebSocket connection is established.
webSocketListener = new WebSocketListener() {
	......
}

// Establish the persistent WebSocket connection.
client.newWebSocket(connectRequest, webSocketListener);

3.2.2 Send an RG command to register the device ID of the client or send a heartbeat signal to API Gateway

	String deviceId = generateDeviceId();
	webSocketListener = new WebSocketListener() {
                @Override
                // Call the onOpen method after the WebSocket connection is established.
                public void onOpen(WebSocket webSocket, Response response) {
                		// Send a message to API Gateway to inform API Gateway of the device ID of the client.
                    String registerCommand = SdkConstant.CLOUDAPI_COMMAND_REGISTER_REQUEST + "#" + deviceId;
                    webSocketRef.getObj().send(registerCommand);
                }

                @Override
                // Call the onMessage method after the client receives a response from API Gateway.
                public void onMessage(WebSocket webSocket, String text) {
                    if(null  == text || "".equalsIgnoreCase(text)) {
                        return;
                    }
                    // If the response from API Gateway is a success response, run the following code segment:
                    else if(text.length() > 2 && text.startsWith(SdkConstant.CLOUDAPI_COMMAND_REGISTER_SUCCESS_RESPONSE)){
                        // Parse the success response.
                        String responseObject[] = text.split("#");
                        connectionCredential = responseObject[1];
                        // Obtain the heartbeat interval in the success response.
                        heartBeatInterval = Integer.parseInt(responseObject[2]);

                        }
							 
						// Enable a heartbeat thread to be used to send heartbeat signals.
                        if (null ! = heartBeatManager) {
                            heartBeatManager.stop();
                        }
                        heartBeatManager = new HeartBeatManager(instance, heartBeatInterval);
                        heartbeatThread = new Thread(heartBeatManager);
                        heartbeatThread.start();
                        return;
                    }
                    // If the response from API Gateway is an error response, run the following code segment:
                    else if(text.length() > 2 && text.startsWith(SdkConstant.CLOUDAPI_COMMAND_REGISTER_FAIL_REQUEST)){
                        
                        String responseObject[] = text.split("#");
                        errorMessage.setObj(responseObject[1]);
						// Stop the heartbeat thread.
                        if (null ! = heartBeatManager) {
                            heartBeatManager.stop();
                        }
                        return;
                }
            };
        }

    private String generateDeviceId(){
        return UUID.randomUUID().toString().replace("-" , "").substring(0 , 8);
    }			

3.2.3 Call the API operation for registration signaling, the API operation for logoff signaling, and API operations that are unrelated to bidirectional communication

protected void sendAsyncRequest(final ApiRequest apiRequest , final ApiCallback apiCallback){
        checkIsInit();
		// Check whether a connection has been established.
        synchronized (connectionLock) {
            if (null ! = connectLatch.getObj() && connectLatch.getObj().getCount() == 1) {
                try {
                    connectLatch.getObj().await(10, TimeUnit.SECONDS);
                } catch (InterruptedException ex) {
                    throw new SdkException("WebSocket connect server failed ", ex);
                } finally {
                    connectLatch.setObj(null);
                }
            }

            if (status == WebSocketConnectStatus.LOST_CONNECTION) {
                apiCallback.onFailure(apiRequest, new SdkException("WebSocket conection lost , connecting"));
                return;
            }

			// If the API operation to be called is the API operation for registration signaling or the API operation for logoff signaling, run the following code segment:
            if (WebSocketApiType.COMMON ! = apiRequest.getWebSocketApiType()) {
                if(! preSendWebsocketCommandApi(apiRequest , apiCallback)) {
                    return;
                }
            }

            Integer seqNumber = seq.getAndIncrement();
            apiRequest.addHeader(SdkConstant.CLOUDAPI_X_CA_SEQ, seqNumber.toString());
            callbackManager.add(seqNumber, new ApiContext(apiCallback, apiRequest));
            
            // Generate a request string to be sent.
            String request = buildRequest(apiRequest);
            webSocketRef.getObj().send(request);
        }

    }
    
    private boolean preSendWebsocketCommandApi(final ApiRequest apiRequest , final ApiCallback apiCallback){
        // If the API operation to be called is the API operation for registration signaling, check whether API Gateway has been informed of the device ID of the client.
        if(WebSocketApiType.REGISTER == apiRequest.getWebSocketApiType()) {
            try {
                if (null ! = registerLatch.getObj() && ! registerLatch.getObj().await(10, TimeUnit.SECONDS)) {
                    Thread.sleep(5000);
                    close();
                    apiCallback.onFailure(apiRequest, new SdkException("WebSocket conection lost , connecting"));
                    return false;
                }
            } catch (InterruptedException ex) {
                throw new SdkException("WebSocket register failed ", ex);
            } finally {
                registerLatch.setObj(null);
            }

            if (! registerCommandSuccess.getObj()) {
                apiCallback.onFailure(null, new SdkException("Register Comand return error :" + errorMessage.getObj()));
                return false;
            }

			// Record the registration request for future use in the event of disconnection and reconnection.
            lastRegisterReqeust = apiRequest.duplicate();
            lastRegisterCallback = apiCallback;

        }
		// If the API operation to be called is the API operation for registration signaling or the API operation for logoff signaling, add a header field in the API request to identify the API operation for registration signaling or the API operation for logoff signaling.
        apiRequest.addHeader(SdkConstant.CLOUDAPI_X_CA_WEBSOCKET_API_TYPE, apiRequest.getWebSocketApiType().toString());


        return true;
    }
    
    
     private String buildRequest(ApiRequest apiRequest){
        apiRequest.setHost(host);
        apiRequest.setScheme(scheme);
        ApiRequestMaker.make(apiRequest , appKey , appSecret);


        WebSocketApiRequest webSocketApiRequest = new WebSocketApiRequest();
        webSocketApiRequest.setHost(host);
        webSocketApiRequest.setPath(apiRequest.getPath());
        webSocketApiRequest.setMethod(apiRequest.getMethod().getValue());
        webSocketApiRequest.setQuerys(apiRequest.getQuerys());
        webSocketApiRequest.setHeaders(apiRequest.getHeaders());
        webSocketApiRequest.setIsBase64(apiRequest.isBase64BodyViaWebsocket() == true ? 1 : 0);
        MediaType bodyType = MediaType.parse(apiRequest.getFirstHeaderValue(HttpConstant.CLOUDAPI_HTTP_HEADER_CONTENT_TYPE));

        if(null ! = apiRequest.getFormParams() && apiRequest.getFormParams().size() > 0){
            webSocketApiRequest.setBody(HttpCommonUtil.buildParamString(apiRequest.getFormParams()));
        }else if(null ! = apiRequest.getBody()){
            webSocketApiRequest.setBody(new String(apiRequest.getBody() , bodyType.charset(SdkConstant.CLOUDAPI_ENCODING)));
        }

        if(apiRequest.isBase64BodyViaWebsocket()){
            webSocketApiRequest.setBody(new String(Base64.encode(apiRequest.getBody() , Base64.DEFAULT) , bodyType.charset(SdkConstant.CLOUDAPI_ENCODING)));
        }

        return JSON.toJSONString(webSocketApiRequest);
}		

3.2.4 Receive notifications that are pushed by API Gateway

	ApiWebSocketListner apiWebSocketListner = params.getApiWebSocketListner();
	webSocketListener = new WebSocketListener() {
		......

                @Override
                // Call the onMessage method after the client receives a server-to-client notification from API Gateway.
                public void onMessage(WebSocket webSocket, String text) {
                     String message  = text.substring(3);
                     apiWebSocketListner.onNotify(message);
                     if(status == WebSocketConnectStatus.CONNECTED && webSocketRef.getObj() ! = null){
                            webSocketRef.getObj().send(SdkConstant.CLOUDAPI_COMMAND_NOTIFY_RESPONSE);
                     }
                     return ;
             	}

     ......
	}

For more information about sample code, download and decompress the SDK for Android. You can find more sample code in the src folder.