socketio / engine.io Goto Github PK
View Code? Open in Web Editor NEWThe engine used in the Socket.IO JavaScript server, which manages the low-level transports such as HTTP long-polling and WebSocket.
Home Page: https://socket.io
The engine used in the Socket.IO JavaScript server, which manages the low-level transports such as HTTP long-polling and WebSocket.
Home Page: https://socket.io
With the server set up like this:
var io = engine.attach(server, {allowUpgrades: false, transports: ['polling']});
And client like:
var socket = new eio.Socket({port: this.port, transports: ['polling'], upgrade: false});
Data can be sent over the socket, but as soon as the ~5 seconds is up, socket closes with an error:
engine:socket sending packet "open" ({"sid":"9wriKgPAqJzd6UqGAAAA","upgrades":[],"pingTimeout":60000}) +0ms
engine:socket flushing buffer to transport +0ms
engine:socket transport error +5s
[Error: poll connection closed prematurely]
Using Chrome 21.0.1180.75 / Mac OS X 10.8
Consider implementing a stream compatible interface on top of the socket. It is already inherting from EventEmitter so making it a writeable, and readable Stream (http://nodejs.org/api/stream.html) would be a small but potentially important step as it would allow us to write pipe-able interfaces on top of engine.io.
This allows us to free memory but still give the opportunity for the developer to subscribe to error
or close
events to create a new reference to the object and avoid its garbage collection prior to us setting it to null
.
The server send callback should execute in multipart packet
test fails about 20% of the time for me.
I think it is too sensitive.
1) server send callback should execute in multipart packet:
Error: expected 1 to equal 2
at Assertion.assert (/pinf/workspaces/github.com/c9/smith.io/node_modules/engine.io/node_modules/expect.js/expect.js:99:13)
at Assertion.equal (/pinf/workspaces/github.com/c9/smith.io/node_modules/engine.io/node_modules/expect.js/expect.js:200:10)
at Assertion.be (/pinf/workspaces/github.com/c9/smith.io/node_modules/engine.io/node_modules/expect.js/expect.js:73:24)
at Object._onTimeout (/pinf/workspaces/github.com/c9/smith.io/node_modules/engine.io/test/server.js:815:26)
I stumbled upon a network that was behind a Squid 3.1.6 transparent proxy. With default engine.IO settings, the connection from engine.IO-client to engine.IO server failed somewhere right after the handshake.
Basically, here' what happens:
It is worth mentioning that if an actual request gets made before the client tries to upgrade to websocket, everything seems fine. Problem is that all of the requests that are made slightly after trying to upgrade to websocket get killed.
I don't know if that's a standard behaviour by Squid to block HTTP requests for a few seconds to a certain host if the client tried to do something "illegal" (upgrade to websocket, in my case)?
If it is, perhaps it's thinking about - maybe there's a workaround for that? Because right now, if the client receives a 500 response from the server, it never retries to re-send the last message with a new request. Thus, the original request never gets delivered to the server. (And that results in the client application to stall - in my case. Obviously, I could go and implement some timeouts about requests and responses and check if all requests got a response, and if not, re-make those requests but that'd be a nice feature built into engine.IO client itself.)
Here's a sample of a POST request being made by the client to the server right after trying to upgrade to websocket, and you can see Squid killing the request in the middle by answering with error 500 to the client:
Request
POST http://listhings.com/engine.io/default/?uid=61636229604482789757082425058&transport=polling&sid=7449753105742354
Request Headers:
Accept:/
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:58
Content-type:text/plain;charset=UTF-8
Cookie:sessionId=[secret]; secretKey=[secret]; __utma=111177113.1031802160.1335278203.1335357532.1335420851.7; __utmb=111177113.5.10.1335420851; __utmc=111177113; __utmz=111177113.1335278203.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _chartbeat2=vc94hngfdl7uozv1.1334935107546.1335422009418.00000000000001
Host:listhings.com
Origin:http://listhings.com
Referer:http://listhings.com/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.163 Safari/535.19
Query String Parameters:
uid:61636229604482789757082425058
transport:polling
sid:7449753105742354
Request Payload:
55:4{"type":"subscribe","version":1,"channels":["u.p.ud"]}
Response Status Code: 500 Internal Server Error
Response Headers:
Connection:close
Date:Thu, 26 Apr 2012 06:33:20 GMT
Via:1.0 localhost (squid/3.1.6)
X-Cache:MISS from localhost
X-Cache-Lookup:MISS from localhost:8080
Right now, transports assume the buffers are present in the process where the connections originated.
We need to turn this into a pub/sub interface to allow those buffers to be placed in external message queues.
One of tests fails on Mac OS X Mountain Lion because:
expected 4 to equal 1
https://github.com/LearnBoost/engine.io/blob/master/test/server.js#L376
$ make test-acceptance
make: *** No rule to make target `test-acceptance'. Stop.
Not sure if test-acceptance is supposed to be there (a grep for that word doesn't yield results) and if Readme.md
is stale (or fresher than the tests), so rather than editing Readme, I put it here as an issue.
The dist-directory is not included. Therefore make fails with
$ make
/bin/sh: dist/engine.io.js: No such file or directory
make: *** [build] Error 1
Quickfix:
$ mkdir dist
or
--- a/Makefile Sun Mar 25 11:20:22 2012 -0300
+++ b/Makefile Wed Mar 28 16:37:54 2012 +0200
@@ -5,12 +5,14 @@
all: build build-dev
build:
+ @mkdir -p dist
@./node_modules/.bin/browserbuild \
-g eio \
-m engine.io-client -b lib/ \
lib > dist/engine.io.js
build-dev:
+ @mkdir -p dist
@./node_modules/.bin/browserbuild \
-g eio \
-d -m engine.io-client -b lib/ \
when using XHR-Polling the following error appears in the browser, blocking all incoming data.
XMLHttpRequest cannot load http://...... Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.
as stated by mozilla, this behavior is correct and is seen in Chrome and FF at least.
For various reasons our server is running multiple websocket servers, meaning that one of these servers is running on a different port than our main http server. This works fine in Google Chrome, but in firefox the engine.io instance running on the different port receives an 'xhr poll error' on the client and fails. This seems to be a CORS issue; the origin of our script has a different port than the port it's polling.
The engine.io spec mentions that CORS should be supported, and I see that Access-Control-Allow-Origin is being packed in the xhr polling transport, but I can't find a way to configure what origins are allowed-- it's always set to the value of the request's 'Origin' header. Is there a way to specify a list of allowed origins?
Hi,
in lib/socket.js, the mistake described in the title happens in several places.
Test snippet here : https://gist.github.com/2920372
Port back from socket.io 0.9.8+
The FlashSocket transport is broken most likely. It contains an "init" function that is never called, and there is no "manager".
Maybe this needs to be initialized (with slightly different config handling) in the Server constructor like websocket? (There would be no chance to change the policyfile port and origins afterwards)
I get approximately 1.000 left opened sockets (netstat count) after two or three days running engine.io 0.1.0 on node 0.6.19.
Restarting engine.io frees them.
The ratio "opened sockets" / "connected clients" oscillates between 1.3 and 2.0, especially when there are thousands of clients.
When the clients leave and only connect for a very short time to engine.io server, the ratio slowly goes up (to 15 after a couple of days).
I can also see two exceptions in the logs :
This one happens quite often :
Error: not opened at WebSocket.send (/xxx/node_modules/engine.io/node_modules/websocket.io/node_modules/ws/lib/WebSocket.js:175:16) at WebSocket.write (/xxx/node_modules/engine.io/node_modules/websocket.io/lib/protocols/hybi.js:101:13) at WebSocket.send (/xxx/node_modules/engine.io/node_modules/websocket.io/lib/socket.js:106:8) at WebSocket.send (/xxx/node_modules/engine.io/lib/transports/websocket.js:73:17) at Socket.flush (/xxx/node_modules/engine.io/lib/socket.js:255:20) at Socket.sendPacket (/xxx/node_modules/engine.io/lib/socket.js:241:10) at Socket.send (/xxx/node_modules/engine.io/lib/socket.js:225:8) Error: not opened at WebSocket.send (/xxx/node_modules/engine.io/node_modules/websocket.io/node_modules/ws/lib/WebSocket.js:175:16) at WebSocket.write (/xxx/node_modules/engine.io/node_modules/websocket.io/lib/protocols/hybi.js:101:13) at WebSocket.send (/xxx/node_modules/engine.io/node_modules/websocket.io/lib/socket.js:106:8) at WebSocket.send (/xxx/node_modules/engine.io/lib/transports/websocket.js:73:17) at Socket.flush (/xxx/node_modules/engine.io/lib/socket.js:255:20) at Socket.sendPacket (/xxx/node_modules/engine.io/lib/socket.js:241:10) at Object._onTimeout (/xxx/node_modules/engine.io/lib/socket.js:114:10) at Timer.ontimeout (timers.js:94:19)
And this one less often:
Error: Error: read ETIMEDOUT at WebSocket.onError (/xxx/node_modules/engine.io/lib/transport.js:77:13) at WebSocket.emit (events.js:67:17) at Socket. (/xxx/node_modules/engine.io/node_modules/websocket.io/lib/socket.js:29:12) at Socket.emit (events.js:88:20) at Array.0 (net.js:301:14) at EventEmitter._tickCallback (node.js:190:39)
EDIT: show whole lines of stack traces
Avoid buffers at the transport level.
Like Isaacs says I believe t is not possibled in npm (yet)? Because this is still alpha?
alfred@alfred-AMILO-Pi-2515:~$ npm info engine.io
npm http GET https://registry.npmjs.org/engine.io
npm http 404 https://registry.npmjs.org/engine.io
npm ERR! 404 'engine.io' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, or http url, or git url.
npm ERR!
npm ERR! System Linux 3.0.0-15-generic
npm ERR! command "node" "/home/alfred/.nvm/v0.6.8/bin/npm" "info" "engine.io"
npm ERR! cwd /home/alfred
npm ERR! node -v v0.6.8
npm ERR! npm -v 1.1.0-2
npm ERR! code E404
npm ERR! message 404 Not Found: engine.io
npm ERR! errno {}
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR! /home/alfred/npm-debug.log
npm not ok
While developing engine.io for java I discovered that I've been able to crash the server when I'm submitting a wrong transport as get parameter.
This happens when I'm switching from polling to websocket transport when the url to the websocket contains "transport=polling"
/Users/tox/src/websockettest/engine/engine.io/lib/transports/index.js:32
if ('string' == typeof req.query.j) {
^
TypeError: Cannot read property 'j' of undefined
at new polling (/Users/tox/src/websockettest/engine/engine.io/lib/transports/index.js:32:35)
at Server.onWebSocket (/Users/tox/src/websockettest/engine/engine.io/lib/server.js:228:23)
at Server.<anonymous> (native)
at Server.emit (events.js:67:17)
at Server.handleUpgrade (/Users/tox/src/websockettest/engine/engine.io/node_modules/websocket.io/lib/server.js:71:10)
at Server.handleUpgrade (/Users/tox/src/websockettest/engine/engine.io/lib/server.js:208:11)
at Server.<anonymous> (/Users/tox/src/websockettest/engine/engine.io/lib/engine.io.js:134:16)
at Server.emit (events.js:88:20)
at Socket.<anonymous> (http.js:1425:14)
at TCP.onread (net.js:354:27)
The Makefile
is pointing to a test
folder, which is not available in the current source tree.
Is it missing or is it a todo?
I'd like to see engine.io
support a feature called conflation, i.e. the removal of some messages based on criteria like
So a more general version of the volatile
feature in socket.io
.
Conflation is especially beneficial to performance of both server and client, when broadcasting and multicasting (e.g. rooms) are frequently used, and protects the server from getting bogged down memory-wise by a single slow consumer who'll cause the buffer to grow to heaven (or until the heartbeat kills the connection, which could still be a lot depending on the msg frequency / heart beat frequency rate and the number of slow consumers). [1]
In case my description above failed to convey the usefulness of conflation, http://magmasystems.blogspot.jp/2006/08/conflation.html has a brief description of that feature and its application to the distribution of price quotes in finance. IBM, too, use conflation for the same purpose: http://publib.boulder.ibm.com/infocenter/imds/v3r0/index.jsp?topic=/com.ibm.imds.doc/welcome.html .
If engine.io wants to enable conflation feature based on the client's performance consuming messages, it has to get support from the engine.io layer, because that feature depends on the client's state (connection open and drained?), which is - understandably - hidden from the application layer. Conflation based on message frequency alone can obviously be done completely in the application layer, as the application has control over how often it calls emit
and can throttle it without the help of engine.io.
There's a rather straightforward way to implement it so that it is both flexible in terms of the conflation logic, yet does not require complex logic inside engine.io itself, and I've actually already implemented it in socket.io
v0.9.8.
Here's a simplified pseudo code diff, leaving out a couple of intermediate steps:
before
io.emit(myJavascriptObject);
transport.write(encodePacket(myJavascriptObject))
after
io.set('conflater', function(messages) { /* for example: */ return [messages[messages.length - 1]]; });
io.emit(myJavascriptObject);
conflationBuffer.push(myJavascriptObject);
.onDrained(function() { transport.actualWriteMessages(encodePackets(io.get('conflater')(conflationBuffer))); }
If no conflater function has been configured during initialization, no buffering or calling conflater
will be done.
The conflater
message can
The above is simplification of the algorithm is not the whole truth, however: In reality, the functions in socket.io/transport are NOT given the myJavascriptObject
, as provided by the client, but the already encoded version of it. They only get to see encodePacket(myJavascriptObject)
. There are 2 good reasons for this:
myJavascriptObject
will be encoded only once (in SocketNamespace.packet(..)
)Now, I would not want to hand the encoded message into the application layer for the following 2 reasons:
I have an idea how to solve this in a way that
The question is - is the conflation feature deemed important enough, and is it further considered impossible to implement without adding changing engine.io, as I believe? In that case, I would actually like to prepare a pull request.
Happens when running mocha tests - mocha ver is 1.3.2 node ver is 0.8.3
When i set transport to polling only, the connection eventually fails.
Using :
ruxkor@668f2cb
ruxkor@5ff8a70
fixes the issue nicely (at least on chromium 17 and ff10)
Defaulting path
to /engine.io
will not work in our setup after all. We want to keep a prefix that mounts at the root.
If we make the path prefix a constant on the module it will apply to all engine.io instances in the app which is probably not desirable. It seems that it needs to stay as an option.
It could be an undocumented option or have a different name than path
but we will continue to need it.
What do you think?
I see that there are no ways to authenticate a connection. One could argue that it's authentication should be done by a higher level api on top of engine.io and overriding the Server.prototype.verify
method but this method is called in a sync way, so that wouldn't be possible either.
So what I would suggest is either:
It'd be great if you could explain a little about the differences right now. I think it's clear that engine.io will focus on the transport layer, but is it ready for use?
I noticed that socket.io doesn't list engine.io as a dependency so that makes me wonder what stage this project is in or if the transport layer has been fully extracted from socket.io. Some clarity on this issue would be awesome, thanks!
There is a WebSocket sub-protocol called MBWS which allows exactly that, by using sequence numbers and a 'resync' protocol when reconnecting a failed connection:
http://tools.ietf.org/html/draft-hapner-hybi-messagebroker-subprotocol-01
Currently, iinm, if there is any error doing a send / doWrite(), the package is lost.
This feature does not require the WebSocket protocol, but can be generalized to cover the polling / streaming cases as well. And it doesn't necessarily need to be done in the format specified by MBWS, it won't be in the non-WebSocket cases anyway.
This would have to be in the protocol right from v1.0, otherwise it'll be hard to add later.
What's missing from MBWS though is the possibility to send volatile packages, packages, that are not buffered and will not be resent (and, as socket.io does, will not be sent in case the connection is not open and drained)
flush
gets called prior to the writeBuffer
getting committed.
drain
upon the writeBuffer
being set to []
It gets called on the server with the socket as first parameter, and on the socket itself.
Needs docs.
I'm seeing this error taking down engine.io in a real-world load test of ~20k clients. I can't reproduce it with a simple stress test - there seems to be something about clients in the wild that cause it. Is it possible that the transport is throwing two errors before clearTransport() is called?
events.js:66
throw arguments[1]; // Unhandled 'error' event
^
Error: reserved fields must be empty
at WebSocket.Transport.onError (/home/ec2-user/node_modules/engine.io/lib/transport.js:77:13)
at WebSocket.EventEmitter.emit (events.js:91:17)
at Receiver.self._receiver.onerror (/home/ec2-user/node_modules/engine.io/node_modules/ws/lib/WebSocket.js:542:10)
at Receiver.error (/home/ec2-user/node_modules/engine.io/node_modules/ws/lib/Receiver.js:301:8)
at Receiver.processPacket (/home/ec2-user/node_modules/engine.io/node_modules/ws/lib/Receiver.js:187:10)
at Receiver.add (/home/ec2-user/node_modules/engine.io/node_modules/ws/lib/Receiver.js:93:24)
at Socket.firstHandler (/home/ec2-user/node_modules/engine.io/node_modules/ws/lib/WebSocket.js:500:22)
at Socket.EventEmitter.emit (events.js:88:17)
at TCP.onread (net.js:403:14)
BREAKING change
$ make bench
module.js:340
throw err;
^
Error: Cannot find module 'colors'
at Function.Module._resolveFilename (module.js:338:15)
at Function.Module._load (module.js:280:25)
at Module.require (module.js:362:17)
at require (module.js:378:17)
at Object. (engine.io/bench/runner.js:6:14)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.runMain (module.js:492:10)
make: *** [bench] Error 1
Adding
"colors": "*"
to the dev-dependencies and another npm install
did not do the trick:
$ make bench
engine.io/bench/runner.js:56
console.log('\n Starting benchmark '.grey + first.yellow);
^
TypeError: Cannot read property 'yellow' of undefined
at Object. (engine.io/bench/runner.js:56:51)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.runMain (module.js:492:10)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)
make: *** [bench] Error 1
I'm seeing some exceptions (but not a lot) :
UNCAUGHT EXCEPTION
[Inside 'uncaughtException' event] Error: poll connection closed prematurely
at XHR.onError (/home/test/project/node_modules/engine.io/lib/transport.js:71:13)
at IncomingMessage.onClose (/home/test/project/node_modules/engine.io/lib/transports/polling.js:82:12)
at IncomingMessage.emit (events.js:64:17)
at abortIncoming (http.js:1386:11)
at Socket.<anonymous> (http.js:1459:5)
at Socket.emit (events.js:88:20)
at Array.0 (net.js:335:10)
at EventEmitter._tickCallback (node.js:192:41)
The error event is sent by lib/transports/pollings.js#Polling.prototype.onPollRequest inner onClose() and is not handled by Socket's this.transport.once('error', this.onError.bind(this));
Also the same onPollRequest inner onClose() function doesn't call cleanup() as onDataRequest's onClose() does.
Regards
Our approach:
data = this.head + JSON.stringify(data) + this.foot;
albeit elegant, is wrong.
With #22, we would need to either:
a) Provide abstract interfaces for talking to other clients, since they could be in other processes or computers.
b) Get rid of .clients
altogether, and let messaging between clients be handled by the developer.
This will signal the client whether it needs to force a "sync xhr" to make disconnections be registered faster for polling mechanisms. Otherwise it'll default to being off.
See:
What is the preferred way to set cache headers for Engine.io server's GET responses, to prevent mobile clients such as Android racing rapidly when trying to connect (xhr will otherwise come from browser cache)? Client's forceBust
(timestamping the query) is not yet finalized I think (not carried from constructor options).
To test this, send a message from the server on connect, and initialize the client with {upgrade: false, transports: ['polling']}
too see it clearly. The socket.onmessage on the client (such as Android) will start triggering in a rapid loop with the initial message. Desktop browsers work ok, they are not as eager to cache requests.
Check for probe
qs parameter in Server#handleRequest and trigger an .upgrade
This way if an error occurs the developer still has access to the unflushed buffers.
As engine.io server responds to form posts with Content-Type': 'text/plain
(in polling.js onDataRequest), Nokia N8 will show a download dialog on submit. Setting Content-Type': 'text/html
instead fixes this. I hope the change wouldn't cause any problems elsewhere.
This is from the browser console. No errors are thrown on the server. The onError messages are my logging.
GET http://chanjs.aws.af.cm/vein/default/?uid=5662024393677787497326917946&transport=polling&sid=725856132894867 500 (Internal Server Error)
onError: {description: 500, message: "xhr poll error", type: "TransportError"}
WebSocket is closed before the connection is established.
POST http://chanjs.aws.af.cm/vein/default/?uid=5662024393677787497326917946&transport=polling&sid=725856132894867 500 (Internal Server Error)
onError: {description: 500, message: "xhr post error", type: "TransportError"}
This is from the server console.
/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/transports/websocket.js:69
this.socket.send(data);
^
TypeError: Object #<IncomingMessage> has no method 'send'
at WebSocket.send (/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/transports/websocket.js:69:17)
at Socket.flush (/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/socket.js:232:20)
at Socket.sendPacket (/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/socket.js:218:10)
at Socket.onOpen (/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/socket.js:49:8)
at new Socket (/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/socket.js:30:8)
at Server.handshake (/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/server.js:180:16)
at Server.handleRequest (/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/server.js:161:10)
at Server.<anonymous> (/mnt/var/vcap.local/dea/apps/chanjs-0-9dcc0f3a6cee0cf7093994fa5a92ee97/app/node_modules/vein/node_modules/engine.io/lib/engine.io.js:129:14)
at Server.emit (events.js:70:17)
at HTTPParser.onIncoming (http.js:1572:12)
Browser is Chromium 19.0.1084.52 Arch x64. App is hosted on AppFog (which uses CloudFoundry). Apparently CloudFoundry doesn't support websockets but shouldn't polling still work?
JSONP transport should check the j
parameter like in Socket.IO 0.9.6. Maybe this is also an issue for engine.io...
https://github.com/racker/socket.io/commit/df51a20a9dfa620507aba62e7759dc363d2a8b16
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.