GithubHelp home page GithubHelp logo

jawampa's Introduction

THIS LIBRARY IS UNMAINTAINED

No updates for any bug, security issue or any dependency had been implemented for long time, and no further development for this library is planned.

jawampa

  • is a library that brings support for the Web Application Messaging Protocol [WAMP] to Java.
  • provides WAMPv2 client side functionality as well as server side functionality and supports all currently defined WAMPv2 roles (caller, callee, publisher, subscriber, broker, dealer).
  • provides a pluggable transport layer. Connection providers and servers which use different networking mechanisms and low-level libraries can be built and plugged into jawampa.
  • exposes the client-side user-interface through RxJava Observables, which enable powerful compositions of different asynchronous operations and provide an easy solution for delegating data handling between different threads.
    Observables are also used in places where only single return values are expected and Futures would be sufficient - However the common use of Observables provides less dependencies and allows to schedule continuations for all kinds of asynchronous operations in a consistent way.
  • is compatible with Java6. However the examples in this document use Java8 syntax for convenience.

Install

Declare the following dependency for the base library:

<dependency>
    <groupId>ws.wamp.jawampa</groupId>
    <artifactId>jawampa-core</artifactId>
    <version>0.5.0</version>
</dependency>

However as the core library of jawampa does not provide a transport layer users should typically use a jawampa transport provider library (e.g. jawampa-netty - see subdirectory) as a depency.
This will automatically also add a dependency on jawampa-core.

WAMP client API (WampClient)

The client-side API is exposed through the WampClient object.
WampClients must be created through WampClientBuilder objects, which allow to configure the created clients.

There are 3 mandatory parameters that have to be set through the builder:

  • A connector provider which describes the framework which will be used for establishing a connection to the WAMP router. An example is the NettyWampClientConnectorProvider which is described in the documentation of the jawampa-netty subproject.
  • The URI that describes the address of the WAMP router
  • The realm that the client should join on the router

Additionally there exist some optional parameters, which for example allow to activate automatic reconnects between the client and the router or allow to configure how the client should behave in case of communication errors.

Example:

final WampClient client;
try {
    // Create a builder and configure the client
    WampClientBuilder builder = new WampClientBuilder();
    builder.withConnectorProvider(connectorProvider)
           .withUri("ws://localhost:8080/wamprouter")
           .withRealm("examplerealm")
           .withInfiniteReconnects()
           .withReconnectInterval(5, TimeUnit.SECONDS);
    // Create a client through the builder. This will not immediatly start
    // a connection attempt
    client = builder.build();
} catch (WampError e) {
    // Catch exceptions that will be thrown in case of invalid configuration
    System.out.println(e);
    return;
}

The WampClient object provides the RxJava Observable statusChanged() that notifies the user about the current status of the session between the client and the router, which can be either DisconnectedState, ConnectingState or ConnectedState. The application can monitor this Observable to detect when other steps should be performed (e.g. subscribe to topics or register functions after connect).

The onNext() status notification method of the Subscriber will be called on the WampClients thread. However it can easily be delegated to a Scheduler or EventLoop of the host application be using the Observable.observerOn() member function.

statusChanged() returns a BehaviorObservable, therefore it will immediatly send a notification about the current state to subscribers on subscribe and not only in case of state changes.

Example:

client.statusChanged()
      .observeOn(applicationScheduler)
      .subscribe((WampClient.State newState) -> {
        if (newState instanceof WampClient.ConnectedState) {
          // Client got connected to the remote router
          // and the session was established
        } else if (newState instanceof WampClient.DisconnectedState) {
          // Client got disconnected from the remoute router
          // or the last possible connect attempt failed
        } else if (newState instanceof WampClient.ConnectingState) {
          // Client starts connecting to the remote router
        }});

In order to start the connection between a client and a router the clients open() member function has to be called. This will lead to the first connection attempt and a state change from DisconnectedState to ConnectingState.

When the client is no longer needed is must be closed with the close() member function. This will shutdown the connection to the remote router and stop all reconnect attempts. After a WampClient was closed it can not be reopened again. Instead of this a new instance of the WampClient should be created if necessary.

The close process is also asynchronous. Therefore a call to close() does not guarantee an immediate close of the client. However the close() call returns an Observable which can be used to wait until the client was successfully closed.

Example for a typical session lifecycle:

WampClient client = builder.build();
client.statusChanged().subscribe(...);
client.open();

// ...
// use the client here
// ...

// Wait synchronously for the client to close
// On environments like UI thread asynchronous waiting should be preferred
client.close().toBlocking().last();

Performing procedure calls

Remote procedure calls can be performed through the various call member functions of the WampClient.

All overloaded version of call require the name of the procedure that should be called (and which must be a valid WAMP Uri) as the first parameter. All versions of call() return an Observable which is used to transfer the result of the function call in an asynchronous fashion to the caller. It is a hot observable, which means the call will be made indepently of whether someone subscribes to it or not. However the result will be cached in the Observable, which means that also late subscribers will be able to retrieve the result.

  • If the procedure call succeeds the subscribers onNext method will be called with the result and followed by an onCompleted call.
  • If the remote procedure call fails then the subscribers onError() method will be called with the occurred error as a parameter.

The different overloads of call() allow to provide the arguments to the procedure in different fashions as well as to retrieve the return value in a different fashion:

The most explicit signature of call is
Observable<Reply> call(String procedure, ArrayNode arguments, ObjectNode argumentsKw)
It allows to pass positional arguments as well as keyword arguments to the WAMP procedure and will return a structure which will as well contains fields for the positional and keyword arguments of the call result.
The arguments and return values use the ArrayNode and ObjectNode data types from the Jackson JSON library which describe an array or object of arbitrary other types.

If only positional arguments are required for the call the simplified variant
Observable<Reply> call(String procedure, Object... args)
can be used which allows to pass the positional arguments as a varargs array. It will also automatically use Jacksons object mapping capabilities to convert all Java POJO keyword arguments in their JsonNode form and create an argument array from that. This means you can directly use any kind of Java objects as function parameters as long as they can be properly serialized and deserialized by Jackson. For more complex data structures you might need to use annotations to instruct the serializer.

The last variant of call() provides some further convenience and has the following signature: <T> Observable<T> call(String procedure, Class<T> returnValueClass, Object... args).
It can be used when the procedure provides none or a single positional return value. Then you can specify the type of the expected return value in the second arguments and call will automatically try to map the first result argument of the procedure call into the required type. This will also be done through Jackson object mapping.

With this simplification you can call remote procedures and listen for return values in the following way (with Java8):

Observable<String> result = client.call("myservice.concat", String.class, "Hello nr ", 123);
// Subscribe for the result
// onNext will be called in case of success with a String value
// onError will be called in case of errors with a Throwable
result.observeOn(applicationScheduler)
      .subscribe((txt) -> System.out.println(txt),
                 (err) -> System.err.println(err));

Providing remote procedures to other clients

With WAMP all clients that are connected to a router are able to provide procedures that can be used by any other client.

jawampa exposes this functionality though the registerProcedure() member function. registerProcedure will return an Observable which will be used to receive incoming function calls. Each incoming request to the registered procedure name will be pushed to the Subscribers onNext method in form of a Request class. The application can retrieve and process requests on any thread through observeOn and can send responses to the request with the Request classes member functions. The procedure will only be registered at the router after subscribe was called and will be unregistered at the router if the subscription was unsubscribed.

If an error occurs during the registration of the procedure at the router the Subscribers onError method will be called to notify about the error. If the session disconnects (or is disconnected) then the subscription will simply be completed with onCompleted.

Example for providing a procedure which echos the first integer argument:

// Provide a procedure
Observable proc = client.registerProcedure("echo.first").subscribe(
    request -> {
        if (request.arguments() == null || request.arguments().size() != 1
         || !request.arguments().get(0).canConvertToLong())
        {
            try {
                request.replyError(new ApplicationError(ApplicationError.INVALID_PARAMETER));
            } catch (ApplicationError e) { }
        } else {
            long a = request.arguments().get(0).asLong();
            request.reply(a);
        }
    },
    e -> System.err.println(e));

// Unregister the procedure
proc.unsubscribe();

Publishing events

The publish() function of the WampClient can be used to publish an event with a topic (that must be a valid WAMP Uri) towards the router and thereby to all other connected WAMP clients.

Similar to call() the publish() function provides various overloads that allow to use differnt formats for passing the event arguments.

The publish() function returns an Observable<Long>. Just like the Observable returned from call() this is also a hot observable and does not need to be subscribed to perform the publishing. Subscribers that subscribe on the Observable will either receive a single onNext() call which delivers the publicationId or an onError() call when the publishing fails. This can for example be the case when there is no connection to the router.

Example for publishing an event:

client.publish("example.event", "Hello ", "from nr ", 28)
      .subscribe(
        publicationId -> { /* Event was published */ },
        err -> { /* Error during publication */});

Subscribing to events

To subscribe on events that are published from other clients on the router the makeSubscription() function of the WampClient can be used. makeSubscription will require the topic which the client is interested in and will return an Observable that can be used to perform the subscription. The subscription on the topic at the router will only happen after subscribe() was called on the Observable. After that, for each received event the onNext() function of the Subscriber will be called to deliver the event in form of a PubSubData struct that contains the positional as well as the keyword arguments. If the subscription can not be performed at the router because of an error onError() will be called to deliver that error. If the connection is closed or get's closed the subscription will be completed with onCompleted().

Hint: For most applications it makes sense to perform subscriptions after they got connected to the router in the statusChanged() handler.

There exists also an overload of the makeSubscription function which can be used in the case that the client is only interested in the first positional argument of the event. It allows to specify the class type of the event data and will automatically try to transform the received event into this data type through Jacksons object mapping capabilities. If the client is interested in no parameter Void.class can be used get an Observable<Void> which will notifiy the subscriber when an event without parameters received. If the received event data can not be converted into the desired format the subscription will be cancelled and an error will be delivered through the onError() function.

Example for subscribing to an event:

// Subscribe to an event
Observable<String> eventSubscription =
client.makeSubscription("example.event", String.class)
      .subscribe((s) -> { /* String event received */ },
                 (e) -> { /* Error during subscription or object mapping */ });

// Unsubscribe from the event
eventSubscription.unsubscribe();

WAMP server API (WampRouter)

jawampa provides a WAMP router that can be bundled into your application in order to avoid installing, configuring and running an external router. By instantiating a WampClient that provides an API as well as a WampRouter in an application a classical server architecture can be mimiced, where the server listens for connections as well as provides an API.

The WampRouter class implements the whole routing and realm logic that is described in the WAMPv2 basic profile. It can be only be created through the WampRouterBuilder class, which allows to configure a router before instantiating it. In the current version of jawampa the realms that the router shall expose have to be configured through it.

Example for configuring and instantiating a router:

WampRouterBuilder routerBuilder = new WampRouterBuilder();
WampRouter router;
try {
    routerBuilder.addRealm("realm1");
    routerBuilder.addRealm("realm2");
    router = routerBuilder.build();
} catch (ApplicationError e) {
    e.printStackTrace();
    return;
}

The router will be directly up-and-running after it was built. However it won't listen to any connections yet and therefore won't do anything up to this point.

In order for the router to work servers must be set up that accept connections, register them at the WampRouter and then push messages towards it.

The WampRouter provides an implementation of the IWampConnectionAcceptor interface which can be used to register a new connection at the client. It can be queried through the WampRouter.connectionAcceptor() getter.

Registering a new connection at the router is a 2-stage process:

  • At first the connection provider must query an instance of a IWampConnectionListener from the router by calling connectionAcceptor.createNewConnectionListener();. This will be the interface to which the new connection should push messages after it was fully established and registered.
    The returned interface does not yet occupy any non-garbage-collectible resources in the router. Therefore it is not harmful to ignore the return value if the connection provider determines that the connection can not be properly established.
  • In a second step the new connection must be registered at the router by calling connectionAcceptor.acceptNewConnection(connection, connectionListener); and thereby providing the sending interface of type IWampConnection to the router.

The new connection may only send message to the listener once both steps have been finished. Sending messages earlier causes undefined behavior.

The WampRouter will use the provided IWampConnection interface in order to send messages through connections. The connection must guarantee the following contract to the router:

  • The router must be able to call methods on the interface as long as it has not called close(...) on it.
    If the connection is already closed or in an errorenous state implementations of the interface should answer sendMessage(...) calls by rejecting the provided promise.
  • The router will always call close(...) on the interface, even if the connection was closed by the remote side before.
  • The connection must guarantee that it calls no method on the retrieved IWampConnectionListener interface after it has acknowledged the close call by fulfilling the provided future. The router will take the acknowledgement of the close(...) call as a sign that all resources owned by the connection have been released.

An example implementation of a server that pushes messages towards the router which is based on the Netty framework can be found in the jawampa-netty subproject.

Closing a router

To close a router the close() member function has to be called:

router.close.toBlocking().last();

This will gracefully close all WAMP sessions established between the router and clients and will also close the underlying transport channels. If new connections are made to the router after close() it will reject those by closing them.

Just like the close() call on the WampClient closing a WampRouter is also an asnychronous process and the the call will return an Observable that signals when the router is fully shutdown.

In order to allow the router to listen on a port, accept incoming connections one or more servers have to be started which use the router as their final request handler.

Restrictions

jawampa is very young and in a work-in-progress state.
Therefore the following restrictions apply:

  • jawampa does not properly support the transmission of binary values as required in the WAMP specification. jawampa will use Jackson to transform data from binary to JSON which will use a base64 encoding, but will not prepend the data with a leading 0 byte.
  • jawampa only supports the WAMPv2 basic profile and some selected parts of the advanced profile. Many advanced profile features are not implemented.
  • jawampa only supports websocket connections between WAMP clients and routers.
  • The roles of the client and router are properly transmitted but not taken into account for all other actions. E.g. it won't be verified whether a remote peer actually provides the needed functionality or not. The assumption is that all peers implement all of the roles that apply for them.

jawampa's People

Contributors

bhuvanalakshmi avatar ctiml avatar darkless012 avatar dmtrlbdv1 avatar fasar avatar jrogers avatar kmax avatar lmoretto avatar matthias247 avatar santhosh-tekuri avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jawampa's Issues

Authorization feature

I am trying to implement dynamic authorization with crossbar.io and jawampa as a guest worker. But I cannot find anything on what I will get as parameters.

from docs http://crossbar.io/docs/Authorization/

@wamp.register('com.example.authorize')
def custom_authorize(session, uri, action):
   ## your custom authorization logic to determine whether client
   ## session should be allowed to perform action on uri
   if ...
      ## allow action
      return True
   else:
      ## deny action
      return False

with the session being this:

{
   "realm": "realm1",
   "authprovider": None,
   "authid": "VA-TKRAaIT44meQKZ6n5y7wk",
   "authrole": "frontend",
   "authmethod": "anonymous",
   "session": 1849286409148650
}

how can I replicate this in java?

                request -> {
                    if (request.arguments() == null || request.arguments().size() != 3) {
                        try {
                            request.replyError(new ApplicationError(ApplicationError.INVALID_PARAMETER));
                        } catch (ApplicationError e) {
                            LOGGER.log(Level.FINE, "There was an ApplicationError", e);
                        }
                    } else {
                        //Something?
                    }
                }

wildcard subscribe issue

Hello,

I would like to subscribe to a wildcard wamp topic (i.e. "*"). I am using SubscriptionFlags.Wildcard with a topic name of empty string "". (see the code below). But the msg was not received.

Could you give me some idea and example code (preferred) on how the wildcard subscription would work? Thanks

----code-----
wampclient.makeSubscription("", SubscriptionFlags.Wildcard, String.class)
.observeOn(rxScheduler)
.subscribe(new Action1() {
@OverRide
public void call(String eventDetails) {
LOG.debug("event for 'onhello' received: " + eventDetails.toString());
}
});

BTW, another subscribe API shown as below does not work either.
wampclient..makeSubscription("", SubscriptionFlags.Wildcard, String.class);

Regards,
Jun

How to get byte[] argument

I'm trying to avoid creating any junk objects, including those that lib creates (PubSub...) and just process incomming data while its byte[] how do we do this?
is it possible at all with jawamp?

Request hat no Meta Information about the Caller

As it seems, this was already done with #46 but the version containing the fix never got it to maven. Can you up the version there?

When you (on client side) register a procedure to be remotely called, you only get a Request object per call. The Request object contains no session information and no methods to retrieve it.

Suggestion:
Allow for flags like disclose_caller when registering procedures and give this information to the Request object for retrieval.

Toggleable "/" behavior

I have a (non-jawampa) WAMP client that's trying to connect to my jawampa server at the root path ( "/" ) and is expecting a websocket handshake. I tried to configure my SimpleWampWebsocketListener to listen on the root path (by putting "/" as the uri) but it is still serving a 200 OK response "This server provides a wamp router on path" instead of the websocket handshake. Thus, the client returns the error message "Error during WebSocket handshake: Unexpected response code: 200"

Is there a way to toggle the behavior that sets up that handler, so that I can set up my WAMP router to listen on the root path, without this placeholder getting in the way?

local vs internet ws server

I am trying to open connection to the server and now I have an issue with it. If it local started crossbar.io server - I am able to connect to it, but I am can't connect to any internet server ( for example wss://demo.crossbar.io/ws ). In this case only cycled disconnect-connection states. What I am doing incorrect ?

try {
// Create a builder and configure the client
WampClientBuilder builder = new WampClientBuilder();

// builder.withUri("ws://192.168.1.39:8282/ws")
builder.withUri("wss://demo.crossbar.io/ws")
.withRealm("realm1")
.withInfiniteReconnects()
.withReconnectInterval(5, TimeUnit.SECONDS);
// Create a client through the builder. This will not immediatly start
// a connection attempt
client = builder.build();

        client.statusChanged().subscribe(new Action1<WampClient.Status>() {
            private Subscription procSubscription;

            public void call(WampClient.Status t1) {
                Log.d("info", "Session status changed to " + t1);

                if (t1 == WampClient.Status.Connected) {
                    Log.d("info", "Connected");
                }
            }
        });

        client.open();

    } catch (WampError e) {
        // Catch exceptions that will be thrown in case of invalid configuration
        e.printStackTrace();
        return;
    }

Invalid URI exception (on pretty valid URI)

I'm getting "invalid_uri" error when trying to subscribe to feed "BTC_XMR". Here is the code to reproduce.

public static void main(String[] args) {
    try {
      WampClient client = new WampClientBuilder()
          .withUri("wss://api.poloniex.com")
          .withRealm("realm1")
          .build();

      // open connection
      client.open();

      // subscribe to connection status events
      client.statusChanged()
          .observeOn(Schedulers.newThread())
          .subscribe((WampClient.Status status) -> {
            switch (status) {
              case Connected:
                System.out.println("INFO - CONNECTED");

                // perform subscriptions
                client
                    .makeSubscription("BTC_XMR")
                    .observeOn(Schedulers.newThread())
                    .subscribe(
                        (x) -> {
                          System.out.println(toPrettyJSON(x));
                        },
                        Throwable::printStackTrace
                    );
                break;
              case Disconnected:
                System.out.println("INFO - DISCONNECTED");
                break;
              case Connecting:
                System.out.println("INFO - CONNECTING");
                break;
              default:
                throw new IllegalStateException("unknown status");
            }
          });

      bearSleep(50000);

      client.close();
    } catch (WampError e) {
      e.printStackTrace();
    }
  }

bearSleep and toPrettyJson can be replaced with something more simple.
exception i get for this is

ApplicationError(wamp.error.invalid_uri, [], {})
    at ws.wamp.jawampa.internal.UriValidator.validate(UriValidator.java:77)

Please fix this, i'm blocked because of this issue...

Android and Proguard

Hi,

I want to use library in Android project with proguard. Does anybody know how to configure proguard to make Jawampa work correctly?

client.close() fails after connection failure

Hi,

it's indicated in the documentation that a client must be closed. However, trying to close the client results in an exception if the connection attempt fails.

Here is a minimal example:

package ws.wamp.example;

import rx.schedulers.*;
import ws.wamp.jawampa.*;
import java.util.concurrent.TimeUnit;

class Main {
    public static void main(String[] args) {
        final WampClient client;
        try {
            WampClientBuilder builder = new WampClientBuilder();
            builder.witUri("ws://localhost:8080/ws")
                   .withRealm("foobar");
            client = builder.build();
            client.statusChanged()
                .subscribe(s -> {}, e -> {e.printStackTrace();});
            client.open();
            Thread.sleep(5000);
            System.out.println("Trying to close the client.");
            client.close();
            System.out.println("Client closed.");
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
    }
}

and it fails like this

ApplicationError(wamp.error.no_such_realm, [], {})
    at ws.wamp.jawampa.WampClient.onMessageReceived(WampClient.java:549)
    at ws.wamp.jawampa.WampClient.access$400(WampClient.java:92)
    at ws.wamp.jawampa.WampClient$SessionHandler.channelRead0(WampClient.java:365)
    at ws.wamp.jawampa.WampClient$SessionHandler.channelRead0(WampClient.java:314)
    at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319)
    at ws.wamp.jawampa.transport.WampClientWebsocketHandler.channelRead(WampClientWebsocketHandler.java:63)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:161)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:333)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:319)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:787)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:130)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
    at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116)
    at java.lang.Thread.run(Thread.java:745)
Trying to close the client.
java.util.concurrent.RejectedExecutionException: event executor terminated
    at io.netty.util.concurrent.SingleThreadEventExecutor.reject(SingleThreadEventExecutor.java:745)
    at io.netty.util.concurrent.SingleThreadEventExecutor.addTask(SingleThreadEventExecutor.java:322)
    at io.netty.util.concurrent.SingleThreadEventExecutor.execute(SingleThreadEventExecutor.java:728)
    at io.netty.util.concurrent.AbstractEventExecutorGroup.execute(AbstractEventExecutorGroup.java:114)
    at ws.wamp.jawampa.WampClient.close(WampClient.java:460)
    at ws.wamp.example.Main.main(Main.java:20)

Cannot connect to the Poloniex wamp server at wss://api.poloniex.com

Hi there,

I have a simple client that connects to the Polonix push API. It used to work for quite some time but suddenly stopped working not being able to connect.

It will sit in the Connecting state forever.
I am trying to debug it and looks like there the channel is closed in NettyWampClientConnectorProvider. It is not clear why the channel gets closed.
Can you guys give some ideas how to approach the problem. I tried all the few WAMP libraries out there and JAWAMPA was the only one that worked until recently.

This is an example code that fails. I have a JS code using Authobahn|JS which connects with no problem.

import rx.functions.Action1;
import ws.wamp.jawampa.WampClient;
import ws.wamp.jawampa.WampClientBuilder;
import ws.wamp.jawampa.transport.netty.NettyWampClientConnectorProvider;
import ws.wamp.jawampa.transport.netty.NettyWampConnectionConfig;

import java.util.concurrent.TimeUnit;

public class JawampaTest {
    public static void main(String[] args) throws Exception {
        NettyWampClientConnectorProvider connectorProvider = new NettyWampClientConnectorProvider();
        NettyWampConnectionConfig connectionConfiguration = new NettyWampConnectionConfig.Builder().build();
        WampClientBuilder builder = new WampClientBuilder();
        WampClient client = builder
                .withConnectorProvider(connectorProvider)
                .withConnectionConfiguration(connectionConfiguration)
                .withUri("wss://api.poloniex.com")
                .withRealm("realm1")
                .withInfiniteReconnects()
                .withReconnectInterval(1, TimeUnit.SECONDS)
                .build();

        client.statusChanged().subscribe(new Action1<WampClient.State>() {
            @Override
            public void call(WampClient.State t1) {
                System.out.println("Session status changed to " + t1);
            }
        });

        client.open();
        Thread.sleep(100000);
    }
}

MessagePack for sending binary

Does jawampa support sending binary using MessagePack ? I mean if I have picture data for example and I want to sand it using MessagePack - is it possible? I am asking it because README says "jawampa does not properly support the transmission of binary values as required in the WAMP specification" and now it is not clear all possibilities of sending binaries. Sorry

wamp.error.no_such_procedure returns args null and null instead of [] and {}

My client code gets tripped up whenever a wamp.error.no_such_procedure is received, because it seems to always get null as both the array arguments and keyval arguments.
Is this intended, because WAMP allows RPC calls to optionally omit empty array and keval arguments? In that case, perhaps the client should be giving me empty JSON nodes ([] or {}) by default if I try to fetch the nonexistent arguments.

maximum size for "arguments" of wampCliet.publish(token, args) ?

Is there a maximum size for the arguments when publishing an event?

I use this code (java): wampClient.publish(token, response.toString());

response.toString()) is a long json-string in my case. It has about 70.000 characters. I have the suspicion that the event does not get published, because when I replace response.toString with a short string, the event gets published as expected.

I dont know much about the internals of Wamp and an initial debugging session into the code did not provide me with much insight. As I said above, I think that the long string is causing some problems.

Any help/feedback is welcome. If you need more information, please let me know.

Does it support connection through a proxy server?

Does the library support connection through a proxy server? I want to run the library behind a proxy server to connect to a WAMP router, but it hangs on the connecting phase...Thank you in advance!

BTW: the http_proxy environment variable is set.

Run Jawampa in Apache Tomcat 8?

Hi -
I'm interested in supporting Autobahn JS on the client side from a Tomcat server - how big a deal would it be to rehost Jawama from Netty to Tomcat's Websockets API? I've done some programming against Tomcat's inner APIs (e.g., a custom realm), but know essentially nothing about Netty.

Thanks!

Make transport pluggable

Not sure if this is already possible, but it should be fairly easy to remove any hard Netty dependencies and instead have the transport be pluggable.

RPC custom object

Hey,

I'm working on a project and I'm testing some ideas for messaging.

The code below works, I am now trying to do the same thing with the PRC but I can not send a Chat Message, this is not impossible?

 Subscription subscribed = client.makeSubscription(eventName.trim().toLowerCase(), ChatMessage.class).subscribe(new Subscriber<ChatMessage>() {

            @Override
            public void onNext(final ChatMessage message) {

                Log.d("andre","Message say: " + message.getTextMessage());
            }

            @Override
            public void onError(Throwable throwable) {
                System.out.println("onError : " + throwable);
            }

            @Override
            public void onCompleted() {
                System.out.println("onCompleted ");
            }
});

Call client.publish(mChatMessage.getUserId().trim().toLowerCase(), mChatMessage);

thks!

Is it possible to run jawampa from insilde WildFly

I am interested in using javampa to allow WildFly services and Java Clients to communicate together. Currently I am using CORBA but am looking for viable alternatives. Can javampa be run inside of WildFly? Thanks, David

Android implementation of jawampa not working

Hi Matthias n team!
thanks for this work!

I need to use on Android, I have referenced this lib using gradle,

All seems to work...
Im following this java example:
https://github.com/Matthias247/jawampa_crossbar/blob/master/src/main/java/ws/wamp/jawampa/CrossbarExample.java

the code is importing and almost working,
but
on

WampClient.Status

I get : "Cannot resolve symbol 'Status' "

is an import missing?

please find my hello world android app [intellij] on,

https://github.com/mako34/WampDroid

this is the only bug to fix so I can have the protocol working for android, how to fix?

Thanks

How to Make WampRouter Deal with Backend Logic?

Hi, I wanna do some backend logic in Router while procedures being called between Clients. What I am thinking is to extend the WampRouter class so CallMessage handller could be override. However it seems impossible now without changing WampRouter class, is it?

Distribute the library

Hello,

I'm working on a project where we use gradle to manage dependencies, which the possibility of obtaining jawampa via gradle?

thank you!

Issue with JSON serialization

I am getting the following error when I try to publish a json msg({"test":"message"}) through crossbar.io and try to process the msg using jawampa. I also noticed that after this failure, jawampa unsubscribes to the the topic.Why is the subscription cancelled even if one message is incorrect? Please let me know if you see any issues with this code.

code to subscribe:

Observable<String> msgSubscription = wampclient.makeSubscription(topicName, String.class);
msgSubscription.subscribe(processMessageAction);

logs:

16:51:33.621 [WampClientEventLoop] DEBUG w.w.j.t.n.WampDeserializationHandler - Deserialized Wamp Message: [36,7054800803741494,1958756529312882,{},[{"test":"message"}]]
16:51:33.634 [WampClientEventLoop] ERROR ws.wamp.jawampa.WampClient -  error
java.lang.IllegalArgumentException: Can not deserialize instance of java.lang.String out of START_OBJECT token
 at [Source: N/A; line: -1, column: -1]
    at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:2774) ~[jackson-databind-2.4.4.jar:2.4.4]
    at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:2700) ~[jackson-databind-2.4.4.jar:2.4.4]
    at ws.wamp.jawampa.WampClient$3.call(WampClient.java:389) [classes/:na]
    at ws.wamp.jawampa.WampClient$3.call(WampClient.java:1) [classes/:na]
    at rx.internal.operators.OperatorMap$1.onNext(OperatorMap.java:55) [rxjava-1.0.8.jar:1.0.8]
at ws.wamp.jawampa.client.SessionEstablishedState.onMessage(SessionEstablishedState.java:365) [classes/:na]
17:02:35.867 [WampClientEventLoop] DEBUG w.w.j.t.n.WampSerializationHandler -  Wamp Message: ws.wamp.jawampa.WampMessages$UnsubscribeMessage@41df3cc2

UriValidator is inappropriately case sensitive.

I ran into an issue using jawampa against a WAMP server which includes uppercase characters in certain topics. This provokes an exception from UriValidator. Any chance you can throw some A-Z into the regexps and/or disable the matcher's case sensitivity?

I am trying to test jawampa on android but having problem

Below is my client code;

final WampClient client1;
WampClientBuilder builder = new WampClientBuilder();

    try {
        builder.witUri("ws://192.168.1.96:8080/ws")
               .withRealm("realm1")
               .withInfiniteReconnects()
               .withReconnectInterval(1, TimeUnit.SECONDS);
        client1 = builder.build();
    } catch (WampError e) {
        e.printStackTrace();
        return;
    }


    client1.statusChanged().subscribe(new Action1<WampClient.Status>() {
        private Subscription procSubscription;

        public void call(WampClient.Status t1) {
            Log.d("info","Session status changed to " + t1);

            if (t1 == WampClient.Status.Connected) {
                Log.d("info","Connected");                   
            }
        }
    });

    client1.open();

The output is always;
Session status changed to Connecting..
Session status changed to Disconnecting..

And it goes on..

My server is;
https://github.com/tavendo/AutobahnPython/blob/master/examples/twisted/wamp/basic/rpc/slowsquare/backend.py

can you please help..

An exception is thrown when the port number is omitted

When the port number in the server URL is omitted, e.g ws://localhost/ws, the following exception is rised:

18:53:22,974 INFO  [stdout] (WampClientEventLoop) rx.exceptions.OnErrorNotImplementedException: port out of range:-1
18:53:22,975 INFO  [stdout] (WampClientEventLoop)   at rx.Observable$36.onError(Observable.java:8416) ~[rxjava-core-0.20.4.jar:na]
18:53:22,975 INFO  [stdout] (WampClientEventLoop)   at rx.observers.SafeSubscriber._onError(SafeSubscriber.java:128) ~[rxjava-core-0.20.4.jar:na]
18:53:22,975 INFO  [stdout] (WampClientEventLoop)   at rx.observers.SafeSubscriber.onError(SafeSubscriber.java:97) ~[rxjava-core-0.20.4.jar:na]
18:53:22,975 INFO  [stdout] (WampClientEventLoop)   at rx.internal.operators.NotificationLite.accept(NotificationLite.java:144) ~[rxjava-core-0.20.4.jar:na]
18:53:22,975 INFO  [stdout] (WampClientEventLoop)   at rx.subjects.SubjectSubscriptionManager$SubjectObserver.emitNext(SubjectSubscriptionManager.java:254) ~[rxjava-core-0.20.4.jar:na]
18:53:22,975 INFO  [stdout] (WampClientEventLoop)   at rx.subjects.BehaviorSubject.onError(BehaviorSubject.java:135) ~[rxjava-core-0.20.4.jar:na]
18:53:22,976 INFO  [stdout] (WampClientEventLoop)   at ws.wamp.jawampa.WampClient.performDispose(WampClient.java:243) ~[jawampa-0.1.0.jar:na]
18:53:22,976 INFO  [stdout] (WampClientEventLoop)   at ws.wamp.jawampa.WampClient.beginConnect(WampClient.java:418) ~[jawampa-0.1.0.jar:na]
18:53:22,976 INFO  [stdout] (WampClientEventLoop)   at ws.wamp.jawampa.WampClient.access$000(WampClient.java:92) ~[jawampa-0.1.0.jar:na]
18:53:22,976 INFO  [stdout] (WampClientEventLoop)   at ws.wamp.jawampa.WampClient$2.run(WampClient.java:271) ~[jawampa-0.1.0.jar:na]
18:53:22,976 INFO  [stdout] (WampClientEventLoop)   at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:380) ~[netty-common-4.0.23.Final.jar:4.0.23.Final]
18:53:22,976 INFO  [stdout] (WampClientEventLoop)   at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:357) [netty-transport-4.0.23.Final.jar:4.0.23.Final]
18:53:22,976 INFO  [stdout] (WampClientEventLoop)   at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116) [netty-common-4.0.23.Final.jar:4.0.23.Final]
18:53:22,977 INFO  [stdout] (WampClientEventLoop)   at java.lang.Thread.run(Thread.java:745) [na:1.8.0_25]
18:53:22,977 INFO  [stdout] (WampClientEventLoop) Caused by: java.lang.IllegalArgumentException: port out of range:-1
18:53:22,977 INFO  [stdout] (WampClientEventLoop)   at java.net.InetSocketAddress.checkPort(InetSocketAddress.java:143) ~[na:1.8.0_25]
18:53:22,977 INFO  [stdout] (WampClientEventLoop)   at java.net.InetSocketAddress.<init>(InetSocketAddress.java:224) ~[na:1.8.0_25]
18:53:22,977 INFO  [stdout] (WampClientEventLoop)   at io.netty.bootstrap.Bootstrap.connect(Bootstrap.java:96) ~[netty-transport-4.0.23.Final.jar:4.0.23.Final]
18:53:22,977 INFO  [stdout] (WampClientEventLoop)   at ws.wamp.jawampa.transport.WampClientChannelFactoryResolver$1.createChannel(WampClientChannelFactoryResolver.java:110) ~[jawampa-0.1.0.jar:na]
18:53:22,977 INFO  [stdout] (WampClientEventLoop)   at ws.wamp.jawampa.WampClient.beginConnect(WampClient.java:384) ~[jawampa-0.1.0.jar:na]

Firefox issue- cannot open a websocket connection to a Jawampa Router

Hi,
Thank you very much for your project. It is very helpful to my work. I have a problem. Could you please help me?

Router: I open a jawampa websocket router.
Client:
case 1: I open a jawampa websocket client. Pub/Sub, Call Procedure work perfectly.
case 2: I create a HTML page with Javascipt code to open a websocket client.
- 2a. Pub/Sub, Call Procedure work perfectly with Chrome and Opera
- 2b. Firefox has problem. It cannot open a connection (So Pub/Sub, Call Procedure of course do not work)

QUESTION: Why if I open the HTML page in Firefox, it cannot open a connection?
ANSWER:
In browser debug mode, I see that Chrome & Opera send "ws://uri" request
GET ws://localhost:8980/websocketdemo HTTP/1.1
Host: localhost:8980
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: null
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Sec-WebSocket-Key: FvZnlLuQeKMH+k8XMiDQMg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Protocol: wamp.2.json

Meanwhile, Firefox sends "http://uri" request. As a consequence, in the server, we go to the method ws.wamp.jawampa.transport.SimpleWampWebsocketListener.WebSocketServerHandler.handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) (In the SimpleWampWebsocketListener.java file) to process this http request. Therefore, there is no handshake and no connection.

I can fix the problem by tricky work. In the method handleHttpRequest above, I call method tryWebsocketHandshake (of course, I change tryWebsocketHandshake from private to public) from the WampServerWebsocketHandler object that you add to the pipeline (pipeline.addLast(new WampServerWebsocketHandler(uri.getPath(), router))). But this is tricky and not good. By the way, I do not really understand websocket, just know how to use it.

SO, COULD YOU PLEASE UPGRADE YOUR CODE so that it can also work with Firefox???

Forward slashes in URIs?

I notice that your internal URI validator only accepts "a.b.c..." with no acceptance for forward slashes. Since the WAMP specification says that a realm name can be any URI, I naturally assumed that slashes could be included, such as "a.b/c" and I was surprised to find that jawampa didn't support it. While designing the library, how did you arrive at your decision to exclude forward slashes? Did you find any resources that specified more strict requirements for the URIs, and if so can you point me to them?

Result of subscribing to topic inaccessible when calling makeSubscription causing race condition

Currently when you subscribe to a topic via makeSubscription, there's no way to know if/when the subscription succeeded, as the observable returned only provides the PubSubData when something is published to the topic.

I have the following scenario where I create a "dynamic" topic, Ie. the topic has some filter information associated with it. So basically I subscribe to the topic from my client with a generated topic name, then I call a procedure registered on the router supplying the topic URI and my filter information, allowing the publisher to start publishing data to my dynamic topic. This results in a race though. Consider this:

  1. Client calls WampClient.makeSubscription with "topic_uri", WampClient sends the subscription request on the event loop thread. Subscription result processed on WampClient scheduler thread.
  2. Client calls "createSubscription" procedure via WampClient.call that has been registered, providing "topic_uri" and some filter information, request sent on the event loop thread.
  3. Other client that provides "createSubscription" immediately publishes information to "topic_uri", pubsubdata received on event loop thread.

Now the published event is received by the subscribed client, but can dropped because subscription on client has not been fully created, due to the result of the initial subscription request being processed on another thread in WampClient (scheduler) where it adds it to the map. The event can be received before or after the subscribe result is processed, resulting in a race.

If I knew when the subscription actually we finished successfully, I could then wait to call "createSubscription" to start getting data, but I can't because I don't know anything about the status of the subscription internally.

I also don't really have a good suggestion about how to fix this either.

Debug flag?

Is it possible to set a debug flag in Jawampa? In Autobahn's Python, JavaScript and C++ clients one can do that and get more detailed console output. This is helpful for debugging.

Customize object mapper for WampClient

Hello, I'm using this library in my project for retrieve data by WebSocket. I have Pojos and use Jackson to convert results of rest calls (I also have rest calls) to them. One of the objects came also from WebSocket, and jawampa has it's own objectMapper (default object mapper) to provide converting from json to Pojo.

The problem is, that I have to setup my objectMapper by introducing CAMEL_CASE_NAMING_STRATEGY and also setting FAIL_ON_UNKNOWN_PROPERTIES to false, to provide not full mapping from json to objects (my objects are far less, than response json from server). So it would be nice If I could configure objectMapper in jawampa.

For example, It would be greate if WampClientBuilder had method objectMapper(ObjectMapper objectMapper) or something like that.

Thank you.

no callback on successfull rpc-register and pubsub-subscribe

the Observable returned by registerProcedure and makeSubscription, call Subscriber.onError if the registration/subscription fails. but there is no callback to tell that the call is successfull.

I dont know how Observable can be used for such callback. can you provide your insight on this.

Expose ObjectMapper in WampClient

If a client is using custom Jackson serializers for its objects, it would be useful to be able to register them with the ObjectMapper used in the WampClient, so that objects with custom serializers are serialized/deserialized properly - see external serializer example using modules on http://wiki.fasterxml.com/JacksonHowToCustomSerializers. Currently because it's not exposed, it's limited to using POJO or annotations, and cannot support modules.

Could we either expose the objectMapper field in the WampClient through a getter, or allow a custom ObjectMapper to be passed when building the client?

I can probably create a pull request for either, but wanted to discuss it first.

Issue in load testing

I have application server in Java and I'm using WAMP for communication between client and server. Everything works fine, But now I want to load test this application , So I have written simple Java program, which creates unique thread for each server request. When I run this program a weird thing happens and I have no clue why it is happening this way, When I create more than 120 threads, for many threads I get statusChanged as disconnected. Am I missing something here? Below 120 threads every thread gets connected to server. Can anyone guide me?

How to specify registration options when registering a procedure

Hi, when registering a procedure (i.e. registerProcedure method) I would like to specify registration options for shared registrations (one of 'single' / 'roundrobin' / 'random' / 'first' / 'last') and matching (one of 'exact' / 'prefix' / 'wildcard').
The latter seems possible with event subscriptions via SubscriptionFlags enum but not with procedure registrations.
I'm trying to take advantage of the crossbar.io implemented shared procedure registrations:
http://crossbar.io/docs/Shared-Registrations/
and pattern based procedure registrations:
http://crossbar.io/docs/Pattern-Based-Registrations/
Is this possible today or planned for the near future? Thanks.

msgpack or json

I trying to understand where I have to set the type of data - msgpack or json and do not found how I can change it. Is it available or not ?

Deploy

Hello,

When I run the project on my local machine works okay, but when I try to deploy on an external tomcat doesn't work. I really do not know how to perform the deploy correctly.

Here is my gradle file:

 buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.bmuschko:gradle-cargo-plugin:2.0.3'
    }
 }

 apply plugin: 'java'
 apply plugin: 'eclipse'
 apply plugin: 'war'
 apply plugin: 'com.bmuschko.cargo'

sourceCompatibility = 1.6
version = '1.0'
jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version
    }
}

repositories {
    //Required repos
    mavenCentral()
    maven { url "https://raw.github.com/Matthias247/jawampa/mvn-repo/"}
}

cargo {
    containerId = 'tomcat7x'
    port = 8080

    deployable {
        context = 'router'
    }

    remote {
        hostname = 'remote_ip'
        username = '******'
        password = '*******'
    }
}

dependencies {
    compile("com.google.guava:guava:17.0")
    compile ("javax.inject:javax.inject:1")
    compile 'ws.wamp.jawampa:jawampa:0.1.2'

    def cargoVersion = '1.4.5'
    cargo "org.codehaus.cargo:cargo-core-uberjar:$cargoVersion",
          "org.codehaus.cargo:cargo-ant:$cargoVersion"
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

test {
    systemProperties 'property': 'value'
}

uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}

server method to kill/destroy a client

is there a method, which can be used by a server to destroy a client?

currently, only the client can unsubscribe/unregister from the WAMP server by itself. however, if the client crashes, the subscribe id stays occupied by a dead client. hence, we need to change the subscribe id manually and restart the client, which is not feasible in a productive environment.

Large Byte Array Serialization

It seems that if I try to serialize a large byte array in a publish message the subscriber side disconnects and fails.

Broker should only acknowledge PUBLISHes if a flag is set

It says here that clients who send PUBLISH messages only receive PUBLISHED (or ERROR / PUBLISH) messages back if they have included "acknowledge" : true in the Options dict. If I'm not mistaken, the jawampa broker is ignoring this flag and is always returning PUBLISHED acknowledgements no matter what.

In theory, this is probably not a breaking bug in most cases (an extra websocket message shouldn't hurt anybody), but I just thought I'd point this out as an easy change you can make if you want to more closely conform to the basic profile.

Single node server only? ( Router )

Hello,

I was wondering if this solution is a single server solution... I could not find anything talking about how to run a cluster.

Thanks!

-sean

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.