Git Product home page Git Product logo

mproxy's Introduction

mProxy

Go Report Card License

mProxy is an MQTT proxy.

It is deployed in front of an MQTT broker and can be used for authorization, packet inspection and modification, logging and debugging and various other purposes.

Usage

git clone https://github.com/absmach/mproxy.git
cd mproxy
make
./build/mproxy

Architecture

mProxy starts TCP and WS servers, offering connections to devices. Upon the connection, it establishes a session with a remote MQTT broker. It then pipes packets from devices to the MQTT broker, inspecting or modifying them as they flow through the proxy.

Here is the flow in more detail:

  • The Device connects to mProxy's TCP server
  • mProxy accepts the inbound (IN) connection and establishes a new session with the remote MQTT broker (i.e. it dials out to the MQTT broker only once it accepts a new connection from a device. This way one device-mProxy connection corresponds to one mProxy-MQTT broker connection.)
  • mProxy then spawns 2 goroutines: one that will read incoming packets from the device-mProxy socket (INBOUND or UPLINK), inspect them (calling event handlers) and write them to mProxy-broker socket (forwarding them towards the broker) and other that will be reading MQTT broker responses from mProxy-broker socket and writing them towards device, in device-mProxy socket (OUTBOUND or DOWNLINK).

mProxy can parse and understand MQTT packages, and upon their detection, it calls external event handlers. Event handlers should implement the following interface defined in pkg/mqtt/events.go:

// Handler is an interface for mProxy hooks
type Handler interface {
    // Authorization on client `CONNECT`
    // Each of the params are passed by reference, so that it can be changed
    AuthConnect(ctx context.Context) error

    // Authorization on client `PUBLISH`
    // Topic is passed by reference, so that it can be modified
    AuthPublish(ctx context.Context, topic *string, payload *[]byte) error

    // Authorization on client `SUBSCRIBE`
    // Topics are passed by reference, so that they can be modified
    AuthSubscribe(ctx context.Context, topics *[]string) error

    // After client successfully connected
    Connect(ctx context.Context)

    // After client successfully published
    Publish(ctx context.Context, topic *string, payload *[]byte)

    // After client successfully subscribed
    Subscribe(ctx context.Context, topics *[]string)

    // After client unsubscribed
    Unsubscribe(ctx context.Context, topics *[]string)

    // Disconnect on connection with client lost
    Disconnect(ctx context.Context)
}

An example of implementation is given here, alongside with it's main() function.

Deployment

mProxy does not do load balancing - just pure and simple proxying with TLS termination. This is why it should be deployed right in front of it's corresponding MQTT broker instance: one mProxy for each MQTT broker instance in the MQTT cluster.

Usually, this is done by deploying mProxy as a side-car in the same Kubernetes pod alongside with MQTT broker instance (MQTT cluster node).

LB tasks can be offloaded to a standard ingress proxy - for example, NginX.

Example Setup & Testing of mProxy

Requirements

  • Golang
  • Mosquitto MQTT Server
  • Mosquitto Publisher and Subscriber Client

Example Setup of mProxy

mProxy is used to proxy requests to a backend server. For the example setup, we will use Mosquitto server as the backend for MQTT, and MQTT over Websocket and an HTTP echo server for HTTP.

  1. Start the Mosquitto MQTT Server with the following command. This bash script will initiate the Mosquitto MQTT server with WebSocket support. The Mosquitto Server will listen for MQTT connections on port 1883 and MQTT over WebSocket connections on port 8000.

    examples/server/mosquitto/server.sh
  2. Start the HTTP Echo Server:

    go run examples/server/http-echo/main.go
  3. Start the OCSP/CRL Mock responder:

     go run examples/ocsp-crl-responder/main.go
  4. Start the example mProxy servers for various protocols:

    go run cmd/main.go

    The cmd/main.go Go program initializes mProxy servers for the following protocols:

    • mProxy server for MQTT protocol without TLS on port 1884
    • mProxy server for MQTT protocol with TLS on port 8883
    • mProxy server for MQTT protocol with mTLS on port 8884
    • mProxy server for MQTT over WebSocket without TLS on port 8083
    • mProxy server for MQTT over WebSocket with TLS on port 8084
    • mProxy server for MQTT over WebSocket with mTLS on port 8085 with prefix path /mqtt
    • mProxy server for HTTP protocol without TLS on port 8086 with prefix path /messages
    • mProxy server for HTTP protocol with TLS on port 8087 with prefix path /messages
    • mProxy server for HTTP protocol with mTLS on port 8088 with prefix path /messages

Example testing of mProxy

Test mProxy server for MQTT protocols

Bash scripts available in examples/client/mqtt directory help to test the mProxy servers running for MQTT protocols

  • Script to test mProxy server running at port 1884 for MQTT without TLS

    examples/client/mqtt/without_tls.sh
  • Script to test mProxy server running at port 8883 for MQTT with TLS

    examples/client/mqtt/with_tls.sh
  • Script to test mProxy server running at port 8884 for MQTT with mTLS

    examples/client/mqtt/with_mtls.sh

Test mProxy server for MQTT over WebSocket protocols

Go programs available in examples/client/websocket/*/main.go directory helps to test the mProxy servers running for MQTT over WebSocket protocols

  • Go program to test mProxy server running at port 8083 for MQTT over WebSocket without TLS

    go run examples/client/websocket/without_tls/main.go
  • Go program to test mProxy server running at port 8084 for MQTT over WebSocket with TLS

    go run examples/client/websocket/with_tls/main.go
  • Go program to test mProxy server running at port 8085 for MQTT over Websocket with mTLS

    go run examples/client/websocket/with_mtls/main.go

Test mProxy server for HTTP protocols

Bash scripts available in examples/client/http directory help to test the mProxy servers running for HTTP protocols

  • Script to test mProxy server running at port 8086 for HTTP without TLS

    examples/client/http/without_tls.sh
  • Script to test mProxy server running at port 8087 for HTTP with TLS

    examples/client/http/with_tls.sh
  • Script to test mProxy server running at port 8088 for HTTP with mTLS

    examples/client/http/with_mtls.sh

Configuration

The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values.

Variable Description Default
MPROXY_MQTT_WITHOUT_TLS_ADDRESS MQTT without TLS inbound (IN) connection listening address :1884
MPROXY_MQTT_WITHOUT_TLS_TARGET MQTT without TLS outbound (OUT) connection address localhost:1883
MPROXY_MQTT_WITH_TLS_ADDRESS MQTT with TLS inbound (IN) connection listening address :8883
MPROXY_MQTT_WITH_TLS_TARGET MQTT with TLS outbound (OUT) connection address localhost:1883
MPROXY_MQTT_WITH_TLS_CERT_FILE MQTT with TLS certificate file path ssl/certs/server.crt
MPROXY_MQTT_WITH_TLS_KEY_FILE MQTT with TLS key file path ssl/certs/server.key
MPROXY_MQTT_WITH_TLS_SERVER_CA_FILE MQTT with TLS server CA file path ssl/certs/ca.crt
MPROXY_MQTT_WITH_MTLS_ADDRESS MQTT with mTLS inbound (IN) connection listening address :8884
MPROXY_MQTT_WITH_MTLS_TARGET MQTT with mTLS outbound (OUT) connection address localhost:1883
MPROXY_MQTT_WITH_MTLS_CERT_FILE MQTT with mTLS certificate file path ssl/certs/server.crt
MPROXY_MQTT_WITH_MTLS_KEY_FILE MQTT with mTLS key file path ssl/certs/server.key
MPROXY_MQTT_WITH_MTLS_SERVER_CA_FILE MQTT with mTLS server CA file path ssl/certs/ca.crt
MPROXY_MQTT_WITH_MTLS_CLIENT_CA_FILE MQTT with mTLS client CA file path ssl/certs/ca.crt
MPROXY_MQTT_WITH_MTLS_CERT_VERIFICATION_METHODS MQTT with mTLS certificate verification methods, if no value or unset then mProxy server will not do client validation ocsp
MPROXY_MQTT_WITH_MTLS_OCSP_RESPONDER_URL MQTT with mTLS OCSP responder URL, it is used if OCSP responder URL is not available in client certificate AIA http://localhost:8080/ocsp
MPROXY_MQTT_WS_WITHOUT_TLS_ADDRESS MQTT over Websocket without TLS inbound (IN) connection listening address :8083
MPROXY_MQTT_WS_WITHOUT_TLS_TARGET MQTT over Websocket without TLS outbound (OUT) connection address ws://localhost:8000/
MPROXY_MQTT_WS_WITH_TLS_ADDRESS MQTT over Websocket with TLS inbound (IN) connection listening address :8084
MPROXY_MQTT_WS_WITH_TLS_TARGET MQTT over Websocket with TLS outbound (OUT) connection address ws://localhost:8000/
MPROXY_MQTT_WS_WITH_TLS_CERT_FILE MQTT over Websocket with TLS certificate file path ssl/certs/server.crt
MPROXY_MQTT_WS_WITH_TLS_KEY_FILE MQTT over Websocket with TLS key file path ssl/certs/server.key
MPROXY_MQTT_WS_WITH_TLS_SERVER_CA_FILE MQTT over Websocket with TLS server CA file path ssl/certs/ca.crt
MPROXY_MQTT_WS_WITH_MTLS_ADDRESS MQTT over Websocket with mTLS inbound (IN) connection listening address :8085
MPROXY_MQTT_WS_WITH_MTLS_PATH_PREFIX MQTT over Websocket with mTLS inbound (IN) connection path /mqtt
MPROXY_MQTT_WS_WITH_MTLS_TARGET MQTT over Websocket with mTLS outbound (OUT) connection address ws://localhost:8000/
MPROXY_MQTT_WS_WITH_MTLS_CERT_FILE MQTT over Websocket with mTLS certificate file path ssl/certs/server.crt
MPROXY_MQTT_WS_WITH_MTLS_KEY_FILE MQTT over Websocket with mTLS key file path ssl/certs/server.key
MPROXY_MQTT_WS_WITH_MTLS_SERVER_CA_FILE MQTT over Websocket with mTLS server CA file path ssl/certs/ca.crt
MPROXY_MQTT_WS_WITH_MTLS_CLIENT_CA_FILE MQTT over Websocket with mTLS client CA file path ssl/certs/ca.crt
MPROXY_MQTT_WS_WITH_MTLS_CERT_VERIFICATION_METHODS MQTT over Websocket with mTLS certificate verification methods, if no value or unset then mProxy server will not do client validation ocsp
MPROXY_MQTT_WS_WITH_MTLS_OCSP_RESPONDER_URL MQTT over Websocket with mTLS OCSP responder URL, it is used if OCSP responder URL is not available in client certificate AIA http://localhost:8080/ocsp
MPROXY_HTTP_WITHOUT_TLS_ADDRESS HTTP without TLS inbound (IN) connection listening address :8086
MPROXY_HTTP_WITHOUT_TLS_PATH_PREFIX HTTP without TLS inbound (IN) connection path /messages
MPROXY_HTTP_WITHOUT_TLS_TARGET HTTP without TLS outbound (OUT) connection address http://localhost:8888/
MPROXY_HTTP_WITH_TLS_ADDRESS HTTP with TLS inbound (IN) connection listening address :8087
MPROXY_HTTP_WITH_TLS_PATH_PREFIX HTTP with TLS inbound (IN) connection path /messages
MPROXY_HTTP_WITH_TLS_TARGET HTTP with TLS outbound (OUT) connection address http://localhost:8888/
MPROXY_HTTP_WITH_TLS_CERT_FILE HTTP with TLS certificate file path ssl/certs/server.crt
MPROXY_HTTP_WITH_TLS_KEY_FILE HTTP with TLS key file path ssl/certs/server.key
MPROXY_HTTP_WITH_TLS_SERVER_CA_FILE HTTP with TLS server CA file path ssl/certs/ca.crt
MPROXY_HTTP_WITH_MTLS_ADDRESS HTTP with mTLS inbound (IN) connection listening address :8088
MPROXY_HTTP_WITH_MTLS_PATH_PREFIX HTTP with mTLS inbound (IN) connection path /messages
MPROXY_HTTP_WITH_MTLS_TARGET HTTP with mTLS outbound (OUT) connection address http://localhost:8888/
MPROXY_HTTP_WITH_MTLS_CERT_FILE HTTP with mTLS certificate file path ssl/certs/server.crt
MPROXY_HTTP_WITH_MTLS_KEY_FILE HTTP with mTLS key file path ssl/certs/server.key
MPROXY_HTTP_WITH_MTLS_SERVER_CA_FILE HTTP with mTLS server CA file path ssl/certs/ca.crt
MPROXY_HTTP_WITH_MTLS_CLIENT_CA_FILE HTTP with mTLS client CA file path ssl/certs/ca.crt
MPROXY_HTTP_WITH_MTLS_CERT_VERIFICATION_METHODS HTTP with mTLS certificate verification methods, if no value or unset then mProxy server will not do client validation ocsp
MPROXY_HTTP_WITH_MTLS_OCSP_RESPONDER_URL HTTP with mTLS OCSP responder URL, it is used if OCSP responder URL is not available in client certificate AIA http://localhost:8080/ocsp

mProxy Configuration Environment Variables

Server Configuration Environment Variables

  • ADDRESS : Specifies the address at which mProxy will listen. Supports MQTT, MQTT over WebSocket, and HTTP proxy connections.
  • PATH_PREFIX : Defines the path prefix when listening for MQTT over WebSocket or HTTP connections.
  • TARGET : Specifies the address of the target server, including any prefix path if available. The target server can be an MQTT server, MQTT over WebSocket, or an HTTP server.

TLS Configuration Environment Variables

  • CERT_FILE : Path to the TLS certificate file.
  • KEY_FILE : Path to the TLS certificate key file.
  • SERVER_CA_FILE : Path to the Server CA certificate file.
  • CLIENT_CA_FILE : Path to the Client CA certificate file.
  • CERT_VERIFICATION_METHODS : Methods for validating certificates. Accepted values are ocsp or crl. For the ocsp value, the tls.Config attempts to retrieve the OCSP responder/server URL from the Authority Information Access (AIA) section of the client certificate. If the client certificate lacks an OCSP responder URL or if an alternative URL is preferred, you can override it using the environmental variable OCSP_RESPONDER_URL.
    For the crl value, the tls.Config attempts to obtain the Certificate Revocation List (CRL) file from the CRL Distribution Point section in the client certificate. If the client certificate lacks a CRL distribution point section, or if you prefer to override it, you can use the environmental variables CRL_DISTRIBUTION_POINTS and CRL_DISTRIBUTION_POINTS_ISSUER_CERT_FILE. If no CRL distribution point server is available, you can specify an offline CRL file using the environmental variables OFFLINE_CRL_FILE and OFFLINE_CRL_ISSUER_CERT_FILE.

OCSP Configuration Environment Variables

  • OCSP_DEPTH : Depth of client certificate verification in the OCSP method. The default value is 0, meaning there is no limit, and all certificates are verified.
  • OCSP_RESPONDER_URL : Override value for the OCSP responder URL present in the Authority Information Access (AIA) section of the client certificate. If left empty, it expects the OCSP responder URL from the AIA section of the client certificate.

CRL Configuration Environment Variables

  • CRL_DEPTH: Depth of client certificate verification in the CRL method. The default value is 1, meaning only the leaf certificate is verified.
  • CRL_DISTRIBUTION_POINTS : Override for the CRL Distribution Point value present in the certificate's CRL Distribution Point section.
  • CRL_DISTRIBUTION_POINTS_ISSUER_CERT_FILE : Path to the issuer certificate file for verifying the CRL retrieved from CRL_DISTRIBUTION_POINTS.
  • OFFLINE_CRL_FILE : Path to the offline CRL file, which can be used if the CRL Distribution point is not available in either the environmental variable or the certificate's CRL Distribution Point section.
  • OFFLINE_CRL_ISSUER_CERT_FILE : Location of the issuer certificate file for verifying the offline CRL file specified in OFFLINE_CRL_FILE.

Adding Prefix to Environmental Variables

mProxy relies on the caarlos0/env package to load environmental variables into its configuration. You can control how these variables are loaded by passing env.Options to the config.EnvParse function.

To add a prefix to environmental variables, use env.Options{Prefix: "MPROXY_"} from the caarlos0/env package. For example:

package main
import (
  "github.com/caarlos0/env/v11"
  "github.com/absmach/mproxy"
)

mqttConfig := mproxy.Config{}
if err := mqttConfig.EnvParse(env.Options{Prefix:  "MPROXY_" }); err != nil {
    panic(err)
}
fmt.Printf("%+v\n")

In the above snippet, mqttConfig.EnvParse expects all environmental variables with the prefix MPROXY_. For instance:

  • MPROXY_ADDRESS
  • MPROXY_PATH_PREFIX
  • MPROXY_TARGET
  • MPROXY_CERT_FILE
  • MPROXY_KEY_FILE
  • MPROXY_SERVER_CA_FILE
  • MPROXY_CLIENT_CA_FILE
  • MPROXY_CERT_VERIFICATION_METHODS
  • MPROXY_OCSP_DEPTH
  • MPROXY_OCSP_RESPONDER_URL
  • MPROXY_CRL_DEPTH
  • MPROXY_CRL_DISTRIBUTION_POINTS
  • MPROXY_CRL_DISTRIBUTION_POINTS_ISSUER_CERT_FILE
  • MPROXY_OFFLINE_CRL_FILE
  • MPROXY_OFFLINE_CRL_ISSUER_CERT_FILE

License

Apache-2.0

mproxy's People

Contributors

alvarowolfx avatar arvindh123 avatar blokovi avatar dborovcanin avatar dependabot[bot] avatar drasko avatar felixgateru avatar ianmuchyri avatar manuio avatar nmarcetic avatar nyagamunene avatar rodneyosodo avatar sammyoina 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

Watchers

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

mproxy's Issues

Replace Client with Context

Client is rather simple and can be replaced with values that we put into context. There are two options: put the Client object to it or put fields directly to the context.

MQTT over WS is broken

BUG REPORT

  1. What were you trying to achieve?
    I'm trying to send an MQTT message using Websockets.

  2. What are the expected results?
    I expect it to succeed.

  3. What are the received results?
    I'm unable to connect using WS and I don't see any logs on the MQTT adapter.

  4. What are the steps to reproduce the issue?

  5. Run the Magistrala composition. Provision Thing and Channel, connect them and try to send MQTT message over WS.

  6. In what environment did you encounter the issue?
    I use the latest Magistrala, in the latest Docker env, but the bug seems to be related to mProxy.

  7. Additional information you deem important:
    N/A

Remove dependency to mainflux

Hello,

Thank you for providing such a nice project!

I wanted to use mproxy within my own project but got into problems because of mproxy dependency to mainflux: my project need a specific version of GoCQL and it's a different one that mainflux is requiring.

Would you accept a PR that:
1- Copy the interface from Mainflux here to remove that dependeny.
2- Use simple go errors instead of mainflux pkg/errors ?

Thanks for your time & consideration.

'mqtt over ws' with authorization in header

ENHANCEMENT / Question

For separation of concern and to align with existing authorization implementation, it seems a good idea to allow authorization via an authorization-token (such as jwt) even for mqtt over ws.

Currently, the session-password is set to token if proxy is ws (https://github.com/absmach/mproxy/blob/main/pkg/websockets/websockets.go#L31-L54), and for 'mqtt over ws' the usual mqtt username/password is used. When working with authorization token, one could store the jwt as password when connecting as mqtt. However, by doing so, the http-socket has already been upgraded to ws. In addition, from a client-perspective, it is a more standard way to send a jwt token with the usual header-authorization vs the mqtt-password.

That being said, I haven't found a lot of resources about token-based-authorization for mqtt anyway.

Support for user properties

FEATURE REQUEST

  1. No, there's no other issue or PR on this

  2. Allow to inspect user properties in the MQTT PUBLISH/SUBSCRIBE message and eventually change them.
    (Moreover, currently mproxy sends an incorrect packet to the upstream MQTT proxy. Try for example the following: mosquitto_pub -m "hello1" -t "this_is_hello" -h localhost -p 1883 --property PUBLISH user-property shop 'storeName:mall'. )

  3. It's important for my project to intercept user properties to remove or add them depending on the environment where the proxy runs (production, test, etc).

Make mProxy PEP for Magistrala

mProxy should be a PEP (Policy Enhancement Point) - i.e. it should intercept all traffic - HTTP, WS, MQTT and CoAP and send it for authorization - i.e. we should remove communication with authorization service from adapters to mProxy.

It should also do the TLS/DTLS termination.

Check if MQTT broker is available before establishing connection

Before establishing a TCP connection with the MQTT broker, mProxy should issue a health check request to the broker. The broker might be up and running, but not ready to establish a connection yet due to the internal cluster synchronization, DB initialization, or any other reason. In that case, mProxy can establish a TCP connection with the broker that's not ready and end up dropping that connection later on when message exchange starts.

Investigate Load Balancing with mProxy

Investigate and verify that we do not have to add load balancing in mProxy, but that we can offload that to any L4 LB (NginX for example) that we can put behind our mProxy.

Also verify that this is automatically done in K8s by the Envoy at the entrance of the VerneMQ cluster pod.

Request support of MQTT Last Will and Testament (LWT)

Issue

Since the introduction of the mproxy in Mainflux (and now Magistrala), it lacks support of listening MQTT Last Will messages from MQTT broker.

Case

We have the case of devices reporting connection status everytime the connect/disconnect to the network through a simple MQTT publish. To cover edge cases of devices that are forcefully unplugged from the power, we also setup Last Will on every connection to report that the device is disconnected if not properly terminated.

This worked perfectly with Mainflux 0.10.0 that we have been using for a while, though after upgrading to mainflux 0.12.0 that has been using mproxy, these messages we not received.

Investigation results

After investigating the source code of mproxy, we ended up to two keypoints that prevent this feature to function with the current mproxy architecture.

a) at the two streams of each session, only messages comming from client to the broker are notified (passed onto nats). (

if dir == Up {
)

b) the Last Will messages are generated from the MQTT broker only if there are active subscribers at that time. Since the mproxy is only packet forwarer, it is not a subscriber so in the general case, those messages are never generated.

Our workaround

To restore the functionality in our deployment:

a) when a publish message has been received we added a hardcoded check whether the suffix of the topic matches "/connectionStatus" that we use, to call notify regardless the stream direction

b) added dummy subscribers listening to those messages, to force MQTT broker to generate Last Wills when needed.

Proxy use external code

Hi. Good work.
I already have my code on java or other languages. I can use webhooks of vernemq and its work8ng gooe. But with more and more connections it gets slow in general.
Thus i am just guessing here please correct me. That taking auth or payload logging outside the broker will keep the broker lightweight and more efficient.
And that brings me to the proxy, is it possible to auth decline and log payload using code from different language than go. Learning go would be beneficial but am time limited i would really appreciate an example if possible with other languages.
Moreover will using the proxy for the mentioned tasks above make the broker more efficient. In case of 10k concurrent connections just an example here.
All appreciated. Thanks.

Add disconnect event

In order to support all the MQTT adapter's features, a disconnect event should be fired when the client disconnects from the mProxy.

Add tests

ENHANCEMENT

  1. Describe the enhancement you are requesting.
    We need tests; as simple as that.

  2. Indicate the importance of this enhancement to you (must-have, should-have, nice-to-have).
    This is a must-have.

Update mProxy Handler methods for improved testability

Currently, Mainflux mProxy hooks for intersecting different kinds of MQTT packets are written in such a way that we often don't return values or errors. While this is fine due to the nature of those methods, it makes sense that we return errors, values, or both to improve code testability.

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.