Coder Social home page Coder Social logo

netty-incubator-codec-quic's Introduction

Build project

Netty Project

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

Links

How to build

For the detailed information about building and developing Netty, please visit the developer guide. This page only gives very basic information.

You require the following to build Netty:

Note that this is build-time requirement. JDK 5 (for 3.x) or 6 (for 4.0+ / 4.1+) is enough to run your Netty-based application.

Branches to look

Development of all versions takes place in each branch whose name is identical to <majorVersion>.<minorVersion>. For example, the development of 3.9 and 4.1 resides in the branch '3.9' and the branch '4.1' respectively.

Usage with JDK 9+

Netty can be used in modular JDK9+ applications as a collection of automatic modules. The module names follow the reverse-DNS style, and are derived from subproject names rather than root packages due to historical reasons. They are listed below:

  • io.netty.all
  • io.netty.buffer
  • io.netty.codec
  • io.netty.codec.dns
  • io.netty.codec.haproxy
  • io.netty.codec.http
  • io.netty.codec.http2
  • io.netty.codec.memcache
  • io.netty.codec.mqtt
  • io.netty.codec.redis
  • io.netty.codec.smtp
  • io.netty.codec.socks
  • io.netty.codec.stomp
  • io.netty.codec.xml
  • io.netty.common
  • io.netty.handler
  • io.netty.handler.proxy
  • io.netty.resolver
  • io.netty.resolver.dns
  • io.netty.transport
  • io.netty.transport.epoll (native omitted - reserved keyword in Java)
  • io.netty.transport.kqueue (native omitted - reserved keyword in Java)
  • io.netty.transport.unix.common (native omitted - reserved keyword in Java)
  • io.netty.transport.rxtx
  • io.netty.transport.sctp
  • io.netty.transport.udt

Automatic modules do not provide any means to declare dependencies, so you need to list each used module separately in your module-info file.

netty-incubator-codec-quic's People

Contributors

abezhovets avatar atropnikov avatar dependabot[bot] avatar djtheredstoner avatar dries-c avatar hyperxpro avatar jbou avatar jorsol avatar kachayev avatar lostk1ng avatar netty-project-bot avatar normanmaurer avatar olegdokuka avatar poisonriver avatar r-asou avatar simonatan avatar thomdev avatar violetagg avatar wuhaocn avatar wujincheng2333 avatar zhining-lu 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

netty-incubator-codec-quic's Issues

Sending ByteBuf example

The netty quic example uses HTTP 0.9 as an application protocol but seems to, in effect, send and receive a bytebuf msg.
Does the application protocol have an effect on the byteBuf?
Is the underlying quic/quiche code capable of handling plain byte payloads rather than http?
I have tried altering the netty example to just wrap a byte array and set the application protocol to null, but this does not work. (Using a LengthFieldBasedFrameDecoder).
Is the use of Unpooled.copiedBuffer significant?
If it is possible to just send byteBufs in a stream how should this be done?
An example or comment in the docs might assist users to handle plain byte/bytebuf payloads if the capability exists.

QuicheQuicStreamChannel should implement toString()

QuicheQuicStreamChannel.toString() calls Object.toString(). Before 0.0.8.Final, QuicheQuicStreamChannel used to extend AbstractChannel, which was providing a "free" implementation of toString() showing the ChannelID and both endpoints. This was handy when using LoggingHandler inside the stream pipeline.

We should implement a better QuicheQuicStreamChannel.toString() that shows the ChannelID, endpoints and stream ID.

How to keep the `QuicChannel` alive?

When creating a QuicChannel, set maxIdleTimeout to 5m. It means that QuicChannel (QuicheQuicChannel) will only survive for 5 seconds without data transmission, right?
If so, what should I do to keep the QuicChannel alive?
Can only by not setting this parameter(use max value)?

Sanity check for QuicServerCodecBuilder options

A few days back I accidentally caused it to crash and burn with a rust panic -- but I couldn't repro it with the maven SNAPSHOT. However while playing around with the options trying to remember what I did the other day that broke it (it was a null pointer), I found a different one by setting initialMaxStreamsBidirectional to -1. Trying to initChannel like that resulted in the following:

thread '<unnamed>' panicked at 'internal error: entered unreachable code', src/octets.rs:656:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fatal runtime error: failed to initiate panic, error 5
Subprocess failed (exit code: 134)

No java stacktrace, and the jvm exited too.

Of course this isn't something one'd do accidentally. Should I put up a pr to add some more option validations?

QuicServerCodecBuilder should copy attributes/options when building a handler

Attributes and options are stored as maps in QuicServerCodecBuilder and they are passed to QuicheQuicServerCodec using Quic.attributesArray()/Quic.optionsArray(), which copies the map entries. If an attribute or option is later modified in the QuicServerCodecBuilder, the map entry is updated in place and the change affects all the instances of QuicheQuicServerCodec that have already been created.

How do I send a ping frame and a goaway frame?

Hi,
What should I do if I want to keep the connection alive? I did not find a way to send a ping frame.
In addition, I may also want to send and receive a goaway frame, is this possible?
Thank you!

closeFuture is never realized for QuicChannel in QuicClientExample

When running QuicClientExample from tests against QUIC server, this seems to hang forever:

channel.closeFuture.sync()

Even though Quiche debug shows (ChannelInactive on stream handler is called in this case):

20:54:23.422 [nioEventLoopGroup-2-1] DEBUG io.netty.incubator.codec.quic.Quiche - quiche: bc8f5e3c5d25354f0b2d7a5deab6fc9591fe1d44 tx frm APPLICATION_CLOSE err=0 reason=[6b, 74, 68, 78, 62, 79, 65]
20:54:23.422 [nioEventLoopGroup-2-1] DEBUG io.netty.incubator.codec.quic.Quiche - quiche::recovery: bc8f5e3c5d25354f0b2d7a5deab6fc9591fe1d44 timer=969.107511ms latest_rtt=103.697754ms srtt=Some(156.233158ms) min_rtt=103.697754ms rttvar=75.90181ms loss_time=[None, None, None] loss_probes=[0, 0, 0] cwnd=14520 ssthresh=18446744073709551615 bytes_in_flight=96 app_limited=true congestion_recovery_start_time=None delivered=1359 delivered_time=500.699489ms recent_delivered_packet_sent_time=611.802777ms app_limited_at_pkt=1359  hystart=window_end=Some(0) last_round_min_rtt=None current_round_min_rtt=None rtt_sample_count=0 lss_start_time=None  
20:54:24.801 [nioEventLoopGroup-2-1] DEBUG io.netty.incubator.codec.quic.Quiche - quiche: bc8f5e3c5d25354f0b2d7a5deab6fc9591fe1d44 draining timeout expired

Same happens when trying to connect to invalid host/port pair.

Add support to delay stream creation when stream limit is reached

At the moment when a user tries to create a stream and the limit is reached we just fail. It would be nice to support buffering / delay of stream creation if the limit is reached and just try again once we have some room again. We do something similar in our h2 implementation

Use Quiche debug logging

Quiche can produce debug logging (using quiche_enable_debug_logging()) that show incoming and outgoing QUIC frames. It would be nice to expose it to Java. Even though the logging handler is global, we should be able to use thread-local variables to control logging per QUIC connection, and show the underlying channel on each log line.

How often is QuicStreamChannel created?

In the RPC framework invocation scenario, do you need to create a QuicStreamChannel for each request?
In the case of heavy traffic, I don't think this is an efficient approach. But if you reuse the original QuicStreamChannel, how do you cache it?

Crash on Android when receiving messages: Cause: null pointer dereference

PR #242 introduces support for Android, but there is still a native crash I haven't been able to solve yet.
Sending messages from the android client to the server works fine, but receiving messages from the server crashes the client app.

Here is the tombstone:

tombstone_07.txt
2021-03-14 14:26:19.243 19042-19042/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2021-03-14 14:26:19.243 19042-19042/? A/DEBUG: Build fingerprint: 'OnePlus/OnePlus6/OnePlus6:10/QKQ1.190716.003/2101210427:user/release-keys'
2021-03-14 14:26:19.243 19042-19042/? A/DEBUG: Revision: '0'
2021-03-14 14:26:19.243 19042-19042/? A/DEBUG: ABI: 'arm64'
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG: Timestamp: 2021-03-14 14:26:19+0100
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG: pid: 18885, tid: 19039, name: nioEventLoopGro  >>> net.patzleiner.nettyquicdemo <<<
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG: uid: 10112
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x10
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG: Cause: null pointer dereference
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     x0  0000000000000000  x1  00000074957886a0  x2  0000000000000027  x3  0000000000000800
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     x4  00000074957886c7  x5  0000000000000027  x6  74612021676e6f50  x7  72614d206e755320
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     x8  323a343120343120  x9  5445432038313a36  x10 614d206e75532074  x11 3a34312034312072
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     x12 45432038313a3632  x13 0a0d313230322054  x14 0000000000000000  x15 0000000000000008
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     x16 00000074f3353ab8  x17 0000007586989380  x18 0000007490c70000  x19 00000074f2e7a048
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     x20 00000074f2454900  x21 0000000000000800  x22 00000074f6d20980  x23 0000000000000027
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     x24 0000000000000000  x25 0000000000000027  x26 00000074f247e610  x27 0000000000000800
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     x28 00000074956abd40  x29 00000074f247e600
2021-03-14 14:26:19.249 19042-19042/? A/DEBUG:     sp  00000074f2e79d70  lr  00000074f31b43ec  pc  0000007586989248
2021-03-14 14:26:19.255 19042-19042/? A/DEBUG: backtrace:
2021-03-14 14:26:19.255 19042-19042/? A/DEBUG:       #00 pc 000000000007f248  /apex/com.android.runtime/lib64/bionic/libc.so (__memcpy+56) (BuildId: a6a4a6a4e20240bbe3173fe560b161af)
2021-03-14 14:26:19.255 19042-19042/? A/DEBUG:       #01 pc 00000000002253e8  /data/app/net.patzleiner.nettyquicdemo-tUGRVEyJAKvfux0DneK9_A==/base.apk!libnetty_quiche_android_arm64-v8a-0.0.8.Final-SNAPSHOT.so (offset 0x1000)
2021-03-14 14:26:19.255 19042-19042/? A/DEBUG:       #02 pc 00000000002093c4  /data/app/net.patzleiner.nettyquicdemo-tUGRVEyJAKvfux0DneK9_A==/base.apk!libnetty_quiche_android_arm64-v8a-0.0.8.Final-SNAPSHOT.so (offset 0x1000)
2021-03-14 14:26:19.255 19042-19042/? A/DEBUG:       #03 pc 0000000000042194  /data/app/net.patzleiner.nettyquicdemo-tUGRVEyJAKvfux0DneK9_A==/base.apk!libnetty_quiche_android_arm64-v8a-0.0.8.Final-SNAPSHOT.so (offset 0x1000)
2021-03-14 14:26:19.255 19042-19042/? A/DEBUG:       #04 pc 000000000003c47c  /data/app/net.patzleiner.nettyquicdemo-tUGRVEyJAKvfux0DneK9_A==/base.apk!libnetty_quiche_android_arm64-v8a-0.0.8.Final-SNAPSHOT.so (offset 0x1000)
2021-03-14 14:26:20.632 1267-1267/? E//system/bin/tombstoned: Tombstone written to: /data/tombstones/tombstone_02

I compiled a native library with debug symbols to decrypt the stacktrace using this command: $ANDROID_NDK_HOME/ndk-stack -sym ~/projects/netty-incubator-codec-quic/target/native-build/.libs/ -dump tombstone_07.txt > tombstone_07_dec.txt and got a stacktrace with linenumbers. This is the result:

tombstone_07.dec.txt
********** Crash dump: **********
Build fingerprint: 'OnePlus/OnePlus6/OnePlus6:10/QKQ1.190716.003/2101210427:user/release-keys'
#00 0x000000000007f248 /apex/com.android.runtime/lib64/bionic/libc.so (__memcpy+56) (BuildId: a6a4a6a4e20240bbe3173fe560b161af)
#01 0x00000000002253e8 /data/app/net.patzleiner.nettyquicdemo-tUGRVEyJAKvfux0DneK9_A==/base.apk!libnetty_quiche_android_arm64-v8a-0.0.8.Final-SNAPSHOT.so (offset 0x1000)
			   core::intrinsics::copy_nonoverlapping::h79f83260169f5d1f
			   /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b/library/core/src/intrinsics.rs:1866:14
			   core::slice::_$LT$impl$u20$$u5b$T$u5d$$GT$::copy_from_slice::h4ca9121dcb820b34
			   /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b/library/core/src/slice/mod.rs:2782:13
			   quiche::stream::RecvBuf::emit::_$u7b$$u7b$closure$u7d$$u7d$::h6ef508ca8431ca20
			   /home/gabri/projects/netty-incubator-codec-quic/target/quiche-source/src/stream.rs:850:26
			   quiche::stream::RangeBuf::with::h2c377cdba1f8c961
			   /home/gabri/projects/netty-incubator-codec-quic/target/quiche-source/src/stream.rs:1520:9
			   quiche::stream::RecvBuf::emit::h2471220cd34e9efb
			   /home/gabri/projects/netty-incubator-codec-quic/target/quiche-source/src/stream.rs:850:13
#02 0x00000000002093c4 /data/app/net.patzleiner.nettyquicdemo-tUGRVEyJAKvfux0DneK9_A==/base.apk!libnetty_quiche_android_arm64-v8a-0.0.8.Final-SNAPSHOT.so (offset 0x1000)
			   quiche::Connection::stream_recv::hcb09c21b9df22e4b
			   /home/gabri/projects/netty-incubator-codec-quic/target/quiche-source/src/lib.rs:2984:27
#03 0x0000000000042194 /data/app/net.patzleiner.nettyquicdemo-tUGRVEyJAKvfux0DneK9_A==/base.apk!libnetty_quiche_android_arm64-v8a-0.0.8.Final-SNAPSHOT.so (offset 0x1000)
			   quiche_conn_stream_recv
			   /home/gabri/projects/netty-incubator-codec-quic/target/quiche-source/src/ffi.rs:574:36
#04 0x000000000003c47c /data/app/net.patzleiner.nettyquicdemo-tUGRVEyJAKvfux0DneK9_A==/base.apk!libnetty_quiche_android_arm64-v8a-0.0.8.Final-SNAPSHOT.so (offset 0x1000)
			   netty_quiche_conn_stream_recv
			   ??:0:0
Crash dump is completed

The error says signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x10 with Cause: null pointer dereference, it happens in netty_quiche_conn_stream_recv. It's strange that it's only happening on Android, not on linux or windows. And only when receiving messages, not when sending. Even the opening and closing of the connection isn't crashing the app, only using ctx.writeAndFlush(buffer). Even sending an empty buffer doesn't crash the app.

The code I'm using can be found here. These lines on the server side produce the crash, I have commented them out so it's not crashing and only sends an empty buffer.

As I have no experience with C / Rust, I would kindly ask your help to solve this problem. How can I help to debug this crash, or what other information do you need?

More fine-grained exception hierarchy

At the moment we basically only use SSLException, SSLHandshakeException and IOException for QUIC. It would be better to be a bit more fine-grained here to make it easier for people to understand why something failed.

Build for Android

Hello, I'm really looking forward into this awesome project, especially the connection migration. I tried the example code on windows, it's really nice.

Is there a chance to build this package for android? Would like to use it in an android app. I already tried to build the windows package myself, and wanted to add an android build later, but I'm still struggling with building BetterSSL using cmake, cl.exe and so on...

Do you have any plans to add support for Android in the near future?

Missed QUICHE_ERR_BUFFER_TOO_SHORT in QuicError enum

io.netty.incubator.codec.quic.Quiche#QUICHE_ERR_BUFFER_TOO_SHORT is missed in io.netty.incubator.codec.quic.QuicError.

That causes the following error:

java.lang.IllegalArgumentException: unknown QuicError code: -2
        at io.netty.incubator.codec.quic.QuicError.valueOf(QuicError.java:71) ~[vpnendpoint.jar:?]
        at io.netty.incubator.codec.quic.Quiche.errorAsString(Quiche.java:514) ~[vpnendpoint.jar:?]
        at io.netty.incubator.codec.quic.QuicheQuicCodec.channelRead(QuicheQuicCodec.java:173) ~[vpnendpoint.jar:?]
        at io.netty.incubator.codec.quic.QuicheQuicCodec.channelRead(QuicheQuicCodec.java:134) ~[vpnendpoint.jar:?]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [vpnendpoint.jar:?]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [vpnendpoint.jar:?]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) [vpnendpoint.jar:?]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) [vpnendpoint.jar:?]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [vpnendpoint.jar:?]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [vpnendpoint.jar:?]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) [vpnendpoint.jar:?]
        at io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:93) [vpnendpoint.jar:?]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719) [vpnendpoint.jar:?]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) [vpnendpoint.jar:?]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) [vpnendpoint.jar:?]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) [vpnendpoint.jar:?]
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) [vpnendpoint.jar:?]
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [vpnendpoint.jar:?]
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [vpnendpoint.jar:?]
        at java.lang.Thread.run(Thread.java:832) [?:?]

More efficient of handling stream writability

At the moment we use quiche_conn_writable to check for writable streams. That can be quite wasteful when you have a lot of streams as usually streams are most of the times writable. We should use an alternative implementation.

Make token verification optional

Toke verification is optional and add an extra round-trip. For some use-cases this is not needed at all, so it should be optional

Add support for configurable local connection ID length

Currently, all connection IDs must have size 20 for things to work. We should be able to use shorter IDs, including zero-length if the connection ID is not needed to route to the correct endpoint.

  • On the server side, we should replace the static QuicheQuicServerCodec.MAX_LOCAL_CONN_ID (= 20) with a field from the codec.
  • On the client side, we can already specify an arbitrary connection ID in QuicChannelBootstrap.connectionAddress(), but the parsing of incoming short packets fail if the length is not 20 since we call Quiche.quiche_header_info() with Quiche.QUICHE_MAX_CONN_ID_LEN. In order to properly parse short packets, we need the connection IDs in the codec to all have the same size. I think we should add a QuicheQuicCodec.localConnIdLen field, shared by client and server.

Quic decouples from TLS

QUIC and TLS are a good match, but I don't think they should be coupled.
For example, the RPC framework for the Intranet, the network environment is secure. Data transmission does not need to be encrypted, which can degrade performance.
So, for a scenario like this, is it currently possible that they are not bound?

QuicChannel should respect AUTO_READ

QuicChannel itself should respect AUTO_READ which means if the user asks to stop reading we should not create any more streams while still multiple data to the existing streams. The existing streams will do their own flow-control management. Beside this we should also respect AUTO_READ for datagram extension here.

Investigate if we can use quiche_conn_new_with_tls to interface with our SslContext implementation

We should investigate if we can use quiche_conn_new_with_tls and so add some "tighter" integration with what we already have in netty.
https://github.com/cloudflare/quiche/blob/master/include/quiche.h#L235

Doing so we may be able to reuse some logic that we have to hook in TrustManagerFactory / KeyManagerFactory etc.

One downside with this would be that we need to be careful to use the same BoringSSL version all the time and so we need to match netty-incubator-codec-quic versions with netty-tcnative-boringssl-static versions. That said it may still be worthwhile to do.

Some other related links:

https://github.com/jiegec/nginx-http3/blob/1dd466ea6f534e58beab221409b1662754c45305/src/event/ngx_event_quic.c
https://github.com/cloudflare/quiche/blob/master/src/lib.rs#L1198

Consider stripping debug symbols

We should consider stripping debug symbols (which is not the default on linux) to reduce the library size.

This should be as easy as using strip --strip-debug during build

Consider always using QuicStreamFrame

#62 added support to use QuicStreamFrame. We may want to consider to always use QuicStreamFrame and do the ByteBuf conversation / semantic changes in a ChannelHandler that users can just put in the pipeline. This may remove some complexity in the QuicheQuicStreamChannel and follow more the design of netty in general.

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.