Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new service type changes and client changes #13

Merged
merged 30 commits into from
Jan 15, 2021
Merged

Conversation

Bhashinee
Copy link
Member

@Bhashinee Bhashinee commented Jan 11, 2021

Purpose

This PR addresses following issues,

  1. Align asynchronous client according to the new design
  2. Query param, path param support for WebSocket upgrade service.
  3. Custom headers support for WebSocket upgrade service.
  4. Introducing new Sync client
  5. Make the server init function return errors
  6. Take the http:Listener as an input for service init
  7. Rename the websocket:WebSocketError to websocket:Error
  8. Change the pushText to writeString, pushBinary to writeBytes, onText to onString, onBinary to onBytes, onOpen to onConnect
  9. Change the asynchronous client init to take the callbackservice as an input and remove the callbackService config from client configuration
  10. Make the upgrade service to have one resource instead of a remote function

Samples

websocket service

listener websocket:Listener l2 = checkpanic new(21003);

service /onTextString on l2 {
   resource function get .() returns websocket:Service|websocket:UpgradeError {
       return new WsService1();
   }
}

service class WsService1 {
  *websocket:Service;
  remote isolated function onString(websocket:Caller caller, string data) {
      checkpanic caller->writeString(data);
  }
}

Websocket Async client

service class clientPushCallbackService {
    *websocket:Service;
    remote function onString(websocket:AsyncClient wsEp, string text) {
        data = <@untainted>text;
    }

    remote isolated function onError(websocket:AsyncClient wsEp, error err) {
        io:println(err);
    }
}

websocket:AsyncClient wsClient = new("ws://localhost:21003/onTextString/", new clientPushCallbackService());

Websocket Sync client

Read string

websocket:SyncClient wsClient = new("ws://localhost:21000/onTextString");
var resp1 = wsClient->writeString("Hi world1");
string resp2 = check wsClient->readString(string);

Read binary data
byte[] resp3 = check wsClient->readBytes();

@@ -1,75 +1,73 @@
//// Copyright (c) 2020 WSO2 Inc. (//www.wso2.org) All Rights Reserved.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to think of a way to rewrite this test once this issue gets fixed. Kept the test commented for reference.

@@ -1,61 +1,62 @@
//// Copyright (c) 2020 WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to think of a way to rewrite this test once this issue gets fixed. Kept the test commented for reference.

}
resource function upgrader(http:Caller caller, http:Request req, string name) {
service /basePath on new websocket:Listener(21003) {
resource function onUpgrade .(http:Caller caller, http:Request req) returns websocket:Service|websocket:UpgradeError {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct ? I thought it should be

resource function get .(http:Caller caller, http:Request req) returns websocket:Service|websocket:UpgradeError

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the resource(method) name, right? In HTTP it is mapped to the accessor get, post, etc. In WebSocket, we always get a specific get request which is always dispatched to this single resource. I think it is clear to have onUpgrade as the name in websocket context.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually what comes first is the accessor name. @chamil321 can you confirm this ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, The first identifier is the accessor name. The second one is the resource name(path). In this case she has used onUpgrade as accessor name which is a custom accessor. Here the HTTP dispatching logic does not apply as webscoket as its own implementation.

Copy link
Contributor

@shafreenAnfar shafreenAnfar Jan 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, this could lead to confusions as we maintain all the HTTP semantics in this resource. Initial request is HTTP and HTTP does not have an onUpgrade verb.

@Bhashinee it is not the resource name. So what we need is

resource function get .(http:Caller caller, http:Request req) returns websocket:Service|websocket:UpgradeError

}
}
resource function upgrader(http:Caller caller, http:Request req, string name) {
service /basePath on new websocket:Listener(21003) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the base path is not given does it default to /ws ?

Copy link
Member Author

@Bhashinee Bhashinee Jan 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default is '/'. ws://localhost:21029 or ws://localhost:21029/ will get dispatched to this service.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should default to /ws as that is most common case ? wdyt ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the discussion had, we decided not to do this for now.


**onOpen resource**: As soon as the WebSocket handshake is completed and the connection is established, the `onOpen` resource is dispatched. This resource is only available in the service of the server.
**onConnect resource**: As soon as the WebSocket handshake is completed and the connection is established, the `onConnect` resource is dispatched. This resource is only available in the service of the server.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it resource or remote ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a remote method. Will change this.

public isolated function init(int port, ListenerConfiguration? config = ()) {
# + port - Listening port of the websocket service listener
# + config - Configurations for the websocket service listener
public isolated function init(int|http:Listener listenerPort, ListenerConfiguration? config = ()) returns Error? {
Copy link
Contributor

@shafreenAnfar shafreenAnfar Jan 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's say listener not listenerport

service UpgradeService /onTextString on new Listener(21003) {
remote isolated function onUpgrade(http:Caller caller, http:Request req) returns Service|WebSocketError {
service /onTextString on l2 {
resource function onUpgrade .() returns Service|UpgradeError {
Copy link
Contributor

@shafreenAnfar shafreenAnfar Jan 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct ? Do we have an accessor named onUpgrade ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to get

remote isolated function onText(Caller caller, string data, boolean finalFrame) {
checkpanic caller->pushText(data);
remote isolated function onString(Caller caller, string data) {
checkpanic caller->writeString(data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of panic I think it is better if we can check and return the error

service object {} clientPushCallbackService = service object {
remote function onText(Client wsEp, string text) {
service class clientPushCallbackService {
remote function onString(AsyncClient wsEp, string text) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here what we need is the caller right not the AsyncClient.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller is for the server-side. Here we pass in the created WebSocket client.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I thought we should expose the same client in the form of the caller.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be the caller even when it is on the client side. Is there a specific reason to do otherwise ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can rename this to Caller. But isn't it confusing for the users? They have created an AsyncClient and for sending messages they are using something called Caller?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the discussion had, renaming this to Caller

}
}) {
remote isolated function onUpgrade(http:Caller caller, http:Request req) returns Service {
listener Listener l7 = checkpanic new(21029, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need checkpanic here ? I see, it seems like the spec has changed. ballerina-platform/ballerina-spec#671

If we need it can't we use check instead of checkpanic ?

});

service /sslEcho on l7 {
resource function upgrade .(http:Request req) returns Service {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@Bhashinee Bhashinee merged commit b9013dd into main Jan 15, 2021
Bhashinee pushed a commit to Bhashinee/module-ballerina-websocket that referenced this pull request May 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants