Giter VIP home page Giter VIP logo

gofast's Introduction

gofast GoDoc Go Report Card Travis CI results GitHub Action Test result

gofast is a FastCGI "client" library written purely in golang.

Contents

What does it do, really?

In FastCGI specification, a FastCGI system has 2 components: (a) web server; and (b) application server. A web server should hand over request information to the application server through socket. The application server always listens to the socket and response to socket request accordingly.

visitor → web server → application server → web server → visitor

gofast help you to write the code on the web server part of this picture. It helps you to pass the request to application server and receive response from it.

You may think of gofast as a "client library" to consume any FastCGI application server.

Why?

Many popular languages (e.g. Python, PHP, nodejs) has FastCGI application server implementations. With gofast, you may mix using the languages in a simple way.

Also, this is fun to do :-)

How to Use?

You basically would use the Handler as http.Handler. You can further mux it with default ServeMux or other compatible routers (e.g. gorilla, pat). You then serve your fastcgi within this golang http server.

Simple Example

Please note that this is only the web server component. You need to start your application component elsewhere.

// this is a very simple fastcgi web server
package main

import (
	"log"
	"net/http"
	"os"

	"github.com/yookoala/gofast"
)

func main() {
	// Get fastcgi application server tcp address
	// from env FASTCGI_ADDR. Then configure
	// connection factory for the address.
	address := os.Getenv("FASTCGI_ADDR")
	connFactory := gofast.SimpleConnFactory("tcp", address)

	// route all requests to a single php file
	http.Handle("/", gofast.NewHandler(
		gofast.NewFileEndpoint("/var/www/html/index.php")(gofast.BasicSession),
		gofast.SimpleClientFactory(connFactory),
	))

	// serve at 8080 port
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Advanced Examples

Normal PHP Application

To serve normal PHP application, you'd need to:

  1. Serve the static assets from file system; and
  2. Serve only the path with relevant PHP file.
Code
package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/yookoala/gofast"
)

func main() {
	// Get fastcgi application server tcp address
	// from env FASTCGI_ADDR. Then configure
	// connection factory for the address.
	address := os.Getenv("FASTCGI_ADDR")
	connFactory := gofast.SimpleConnFactory("tcp", address)

	// handles static assets in the assets folder
	http.Handle("/assets/",
		http.StripPrefix("/assets/",
			http.FileServer(http.FileSystem(http.Dir("/var/www/html/assets")))))

	// route all requests to relevant PHP file
	http.Handle("/", gofast.NewHandler(
		gofast.NewPHPFS("/var/www/html")(gofast.BasicSession),
		gofast.SimpleClientFactory(connFactory),
	))

	// serve at 8080 port
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Customizing Request Session with Middleware

Each web server request will result in a gofast.Request. And each gofast.Request will first run through SessionHandler before handing to the Do() method of gofast.Client.

The default gofast.BasicSession implementation does nothing. The library function like gofast.NewPHPFS, gofast.NewFileEndpoint are gofast.Middleware implementations, which are lower level middleware chains.

So you may customize your own session by implemention gofast.Middleware.

Code
package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/yookoala/gofast"
)

func main() {
	// Get fastcgi application server tcp address
	// from env FASTCGI_ADDR. Then configure
	// connection factory for the address.
	address := os.Getenv("FASTCGI_ADDR")
	connFactory := gofast.SimpleConnFactory("tcp", address)

	// a custom authentication handler
	customAuth := func(inner gofast.SessionHandler) gofast.SessionHandler {
		return func(client gofast.Client, req *gofast.Request) (*gofast.ResponsePipe, error) {
			user, err := someCustomAuth(
				req.Raw.Header.Get("Authorization"))
			if err != nil {
				// if login not success
				return nil, err
			}
			// set REMOTE_USER accordingly
			req.Params["REMOTE_USER"] = user
			// run inner session handler
			return inner(client, req)
		}
	}

	// session handler
	sess := gofast.Chain(
		customAuth,            // maps REMOTE_USER
		gofast.BasicParamsMap, // maps common CGI parameters
		gofast.MapHeader,      // maps header fields into HTTP_* parameters
		gofast.MapRemoteHost,  // maps REMOTE_HOST
	)(gofast.BasicSession)

	// route all requests to a single php file
	http.Handle("/", gofast.NewHandler(
		gofast.NewFileEndpoint("/var/www/html/index.php")(sess),
		gofast.SimpleClientFactory(connFactory),
	))

	// serve at 8080 port
	log.Fatal(http.ListenAndServe(":8080", nil))
}

FastCGI Authorizer

FastCGI specified an authorizer role for authorizing an HTTP request with an "authorizer application". As different from a usual FastCGI application (i.e. responder), it only does authorization check.

Summary of Spec

Before actually serving an HTTP request, a web server can format a normal FastCGI request to the Authorizer application with only FastCGI parameters (FCGI_PARAMS stream). This application is responsible to determine if the request is properly authenticated and authorized for the request.

If valid,

  • The authorizer application should response with HTTP status 200 (OK).

  • It may add additional variables (e.g. SOME-HEADER) to the subsequence request by adding Variable-SOME-HEADER header field to its response to web server.

  • The web server will create a new HTTP request from the old one, appending the additional header variables (e.g. Some-Header), then send the modified request to the subquence application.

If invalid,

  • The authorizer application should response with HTTP status that is NOT 200, and the content to display for failed login.

  • The webserver will skip the responder and directly show the authorizer's response.

Code
package main

import (
	"net/http"
	"time"

	"github.com/yookoala/gofast"
)

func myApp() http.Handler {
  // ... any normal http.Handler, using gofast or not
	return h
}

func main() {
	address := os.Getenv("FASTCGI_ADDR")
	connFactory := gofast.SimpleConnFactory("tcp", address)
	clientFactory := gofast.SimpleClientFactory(connFactory)

	// authorization with php
	authSess := gofast.Chain(
		gofast.NewAuthPrepare(),
		gofast.NewFileEndpoint("/var/www/html/authorization.php"),
	)(gofast.BasicSession)
	authorizer := gofast.NewAuthorizer(
		authSess,
		gofast.SimpleConnFactory(network, address)
	)

	// wrap the actual app
	http.Handle("/", authorizer.Wrap(myApp()))

	// serve at 8080 port
	log.Fatal(http.ListenAndServe(":8080", nil))
}

FastCGI Filter

FastCGI specified a filter role for filtering web server assets before sending out. As different from a usual FastCGI application (i.e. responder), the requested data is on the web server side. So the web server will pass those data to the application when requested.

Code
package main

import (
	"net/http"
	"time"

	"github.com/yookoala/gofast"
)

func main() {
	address := os.Getenv("FASTCGI_ADDR")
	connFactory := gofast.SimpleConnFactory("tcp", address)
	clientFactory := gofast.SimpleClientFactory(connFactory)

	// Note: The local file system "/var/www/html/" only need to be
	// local to web server. No need for the FastCGI application to access
	// it directly.
	connFactory := gofast.SimpleConnFactory(network, address)
	http.Handle("/", gofast.NewHandler(
		gofast.NewFilterLocalFS("/var/www/html/")(gofast.BasicSession),
		clientFactory,
	))

	// serve at 8080 port
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Pooling Clients

To have a better, more controlled, scaling property, you may scale the clients with ClientPool.

Code
package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/yookoala/gofast"
)

func main() {
	// Get fastcgi application server tcp address
	// from env FASTCGI_ADDR. Then configure
	// connection factory for the address.
	address := os.Getenv("FASTCGI_ADDR")
	connFactory := gofast.SimpleConnFactory("tcp", address)

	// handles static assets in the assets folder
	http.Handle("/assets/",
		http.StripPrefix("/assets/",
			http.FileSystem(http.Dir("/var/www/html/assets"))))

	// handle all scripts in document root
	// extra pooling layer
	pool := gofast.NewClientPool(
		gofast.SimpleClientFactory(connFactory),
		10, // buffer size for pre-created client-connection
		30*time.Second, // life span of a client before expire
	)
	http.Handle("/", gofast.NewHandler(
		gofast.NewPHPFS("/var/www/html")(gofast.BasicSession),
		pool.CreateClient,
	))

	// serve at 8080 port
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Full Examples

Please see the example usages:

Author

This library is written by Koala Yeung.

Contributing

Your are welcome to contribute to this library.

To report bug, please use the issue tracker.

To fix an existing bug or implement a new feature, please:

  1. Check the issue tracker and pull requests for existing discussion.
  2. If not, please open a new issue for discussion.
  3. Write tests.
  4. Open a pull request referencing the issue.
  5. Have fun :-)

Licence

This library is release under a BSD-like licence. Please find the LICENCE file in this repository

gofast's People

Contributors

cseufert avatar fangdingjun avatar hilyjiang avatar ijt avatar kochurovro avatar marcel-dancak avatar mpdroog avatar mylxsw avatar ruudk avatar yookoala 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

gofast's Issues

License issues

Hi :-).

I read through your license file and noticed a couple of things:

  1. It's spelled "LICENCE" instead of "LICENSE". This can make automatic LICENSE detectors miss your file
  2. You (Yeung Shu Hung) share the copyright with "the Go Authors", but aren't you the sole author of this project?

It'd be nice if you can change the spelling from "LICENCE" to "LICENSE", as this may make it easier for automatic license detectors to find your license file.

Number 2 isn't a big deal, it's your project and you get to assign copyright to whoever you want of course. I just thought it was curious :-).

Cheers!

gofast hangs on error_log() in php script

I made a script like this:

$ cat index.php 
<?php error_log('uh oh');

When I ran my gofast app with php-fpm on this script, and then hit it with curl localhost:8081, it issued the following error and then no longer responded to requests:

2018/07/05 03:23:39 gofast: error stream from application process PHP message: uh oh

Lack a way to do simple request

The current library presume library-users are working on a proxy to application server (as described in the FastCGI specification). But some library (e.g. https://github.com/tomasen/fcgi_client) works as a simple request-response interface for the application server.

gofast should also support this.

Conceptually, it should probably look like this

...

address := os.Getenv("FASTCGI_ADDR")

// A client to directly connect FCGI backend
f := gofast.SimpleClientFactory(gofast.SimpleConnFactory("tcp", address), 0)
client := gofast.NewDirectClient(f, ...)

// Request specifying "SCRIPT_FILENAME" and
// a small subset of usual header by default
// (user can supply more parameters)
req := gofast.NewDirectRequest(...)

// make the direct request
var resp http.Response := client.Do(req)

...
...

Connections are closed by the end of every session. Should be pretty straightforward to use.

Add Responder example for node-fastcgi

Its a nice thing that node-fastcgi implements all 3 roles of fastcgi application: Responder, Authorizer and Filter.

I would be great to have it in example collection.
And it is great to have full example on using all 3 roles in future.

Related to #2.

Implements all 3 roles in FastCGI protocol

There are 3 roles in FastCGI request:

  1. Responder
  2. Authorizer
  3. Filter

Responder is the most common case around. Need to find an application framework that supports the other 2 roles. Then test all 3 cases against it.

Or, if there is no such thing, sit back and forget the 1.0 specification.

Lack go native testing on Authorizer and Filter role

The testing on Authorizer and Filter depends on node-fastcgi.

It would be preferable to have golang native testing. We, however, cannot depend on the net/http/fcgi library because it only implemented the Responder role. Helps to resolve #2.

TODO

  • expand the protocol, if needed, so we can write dummy fcgi application (probably without real socket connection).
  • write dummy fcgi Authorizer application.
  • write dummy fcgi Filter application.
  • test Authorizer and Filter client implementations.

MapHeader middleware issue with multiple header value

The current implementation of MapHeader only map the first value of any header field. If there are multiple values, some of the are lost.

According to a stackoverflow post, RFC 2616 stated that multiple header values should be concat with comma (,) before sending. In that spirit, I think we should use , for preserving multiple header value to get a better code compatibility.

pool.go doesn't have any explanation of what it's for

I'm trying to decide whether to use a PoolClient, but pool.go doesn't say what benefit it has. I guess it's supposed to be for performance but I don't see any benchmarks that would give evidence for that.

I hope this isn't taken as too critical. I'm glad you wrote this library and I'm getting nice results from it.

No method to close the connect

get := func(path string) (w *httptest.ResponseRecorder, err error) {
	h := php.NewHandler("", "tcp", "127.0.0.1:9000")
	r, err := http.NewRequest("GET", path, nil)
	if err != nil {
		return
	}
	w = httptest.NewRecorder()
	h.ServeHTTP(w, r)

	return
}

How to close ServeHTTP conn?

Example setup with a PHP Framework?

Hi there, love the work you are doing with this library!

Do you by chance have an example of a full blown MVC style framework like Laravel, Yii, or Symfony that is using this project? I would like to see a basic setup to make sure I am on the right path.

HTTP_HOST missing

It is my understanding that MapHeader()'s intention is to map each request header into HTTP_<header name> param:

func MapHeader(inner SessionHandler) SessionHandler {

HTTP_HOST was listed as "No need to do" in #27 because "So if the request header has a Host field, it will be passed on as HTTP_HOST", but that's not true because Go removes ("promotes") the Host: ... header from http.Request.Header:

For incoming requests, the Host header is promoted to the Request.Host field and removed from the Header map.

(Source)

My proposal to fix this is to just add:

// we've to handle "Host: ..." header separately, because Go "promotes" (= removes) this from Header map
if r.Host != "" {
  req.Params["HTTP_HOST"] = r.Host
}

Please let me know, if you'd prefer for me to submit a PR for this!

PHP-FPM max_children

If I load the page five times or more, I run out of process managers.

php_1 | [31-Jan-2018 14:43:10] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it

You can view the repo if you need to see what I'm doing.

Is gofast closing the FastCGI connections? Reusing them? Any help is appreciated :)

How to intercept response header

like nginx X-Accel-Redirect will be intercept and remove before responed to client by nginx

ResponsePipe just write to http.Response

Doesn't work with the original libfcgi

The original libfcgi has:

libfcgi/fcgiapp.c, in ProcessBeginRecord():

if(requestId == 0 || data->contentLen != sizeof(body)) {
    return FCGX_PROTOCOL_ERROR;
}

Which means that it rejects requests with RequestID == 0.

Changing gofast/client.go line 101:
for i := uint16(0); i < maxID; i++ {

to:
for i := uint16(1); i < maxID; i++ {

will resolve this.

Additionally: I'm concerned because it appears that this will run out of ids at 65k and it's not clear what happens then.

Unable to handle empty content

Client doesn't work with empty stdin content. Need to inject artificial stdin in the proxy example in order to work properly.

Need fix.

crash in client.go

The gofast is setup to interact with the php7.4-fpm and I get the crash of the app with the following log messages:

2021/09/22 11:24:31 gofast: error writing error buffer to response: gofast: copy error: write tcp 10.0.255.150:443->54.36.148.120:37375: write: connection reset by peer 2021/09/22 11:24:31 gofast: error stream from application process gofast: timeout or canceled panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x2939d4]

goroutine 72774 [running]:
github.com/yookoala/gofast.(*client).readResponse.func1(0x400051e000, 0x40003fa440, 0x40001a4a80, 0x400007aae0)
/root/go/pkg/mod/github.com/yookoala/[email protected]/client.go:213 +0x24
created by github.com/yookoala/gofast.(*client).readResponse
/root/go/pkg/mod/github.com/yookoala/[email protected]/client.go:210 +0x84
exit status 2

Need better script routing

In the current version of client.go, @hilyjiang help added properly translated SCRIPT_FILENAME, PATH_TRANSLATED to fastcgi parameters.

The translation suits older PHP routing paradigm, where server route request to the PHP file that matches the directory-file path within a document root (i.e. root). PHP frameworks now in favour of routing all requests to same PHP file, where further routing logic is done by PHP code. Some examples,

To interoperate with PHP application of both paradigm we should decouple the path translation into a separated component (instead of a fix string parameter root). Then we should have sensible default implementation to route for both paradigm.

Content-Type and Content-Length

In session.go, CONTENT_TYPE and CONTENT_LENGTH environment variables are set (as empty) even if Content-Type and Content-Length request headers are not specified in the original HTTP request. This is likely to cause certain back ends to fail as these env vars would contain empty values.

Is it possible to set these headers if and only if these are supplied on the request?

Invalid memory address or nil pointer dereference

I'm seeing this error in production in combination with skipper. Unfortunately I wasn't able to get the exact request yet.
Do you maybe have some pointers (pun intended) how to debug this?

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0xa9dcd9]
goroutine 57768117 [running]:
github.com/yookoala/gofast.(*client).readResponse.func1(0xc00ac4e000, 0xc00a84aea0, 0xc00a81d6c0, 0xc004484540)
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:211 +0x29
created by github.com/yookoala/gofast.(*client).readResponse
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:208 +0xad

Relevant code segment:
https://github.com/yookoala/gofast/blob/v0.4.0/client.go#L199-L239

Serious security vulnerability: path traversal

Why no responsible disclosure

First off, I wanted to proceed with a responsible disclosure but:

  • I didn't find a security policy for this repo
  • The only contact info I found was a Twitter account that was restricted to only followers so I could not send a DM or tweet

The vulnerability

req.Params["SCRIPT_FILENAME"] = path.Join(fs.DocRoot, urlPath)

This line takes remote user controllable path from HTTP request and appends it via filepath.Join() (it does relative-to-absolute translation) to a file path, now making it a legit file path from now on.

Now, given user code:

	handler := gofast.NewHandler(
		gofast.NewPHPFS("/baikal/html/"),
		gofast.SimpleClientFactory(gofast.SimpleConnFactory("tcp", ":8080"), 0),
	)

And an attacker sending a HTTP request:

$ curl --path-as-is http://localhost/../../etc/passwd

(curl would normalize the path without --path-as-is)

Because the handler also serves static files, I am able to steal anything from the filesystem (even ENV vars from /proc/self/environ that sometimes are used to store secrets).

If I add logging, the above request would show up as:

incoming URL path=/../../etc/passwd SCRIPT_FILENAME=/etc/passwd

More on this vulnerability: https://owasp.org/www-community/attacks/Path_Traversal

Other observations

Ineffectual code

req.Params["SCRIPT_FILENAME"] = filepath.Join(fs.DocRoot, fastcgiScriptName)

This is ineffectual, as req.Params["SCRIPT_FILENAME"] is overwritten a few lines later

Performance observation

pathinfoRe := regexp.MustCompile(`^(.+\.php)(/?.+)$`)

This compiles a regex on every request. It might be more performant to compile the regex only once (outside of this function). There's another place also that compiles a regex on every request.

Memory leak issue with massive use of disposable client

Potential problem here is that the idpool channel in these "disposable" clients never got proper GC and recovery.

As reported by @adri in this comment about this usage of the library:

I'm not working often in Golang, please forgive me if what I say makes no sense. I've been looking for potential memory that is not cleaned up.

  1. Maybe c.ids.Release(reqID) is not called in certain error cases
    The ID is allocated and later released. What if there is an error like this, will the ID be released back in the pool?

  2. Looking at one Skipper pod I see the typical saw-tooth graph of a memory leak.
    I'm not sure if this has anything to do with the gofast usage or something in Skipper. If you have a tip how I can find out more please let me know.
    Screen Shot 2020-09-15 at 09 14 19
    I can see goroutines increasing:
    Screen Shot 2020-09-15 at 09 07 56
    And live objects:
    Screen Shot 2020-09-15 at 09 15 02

And in this comment:

The design of gofast is to have the ClientFactory reused, not the Client.

@yookoala mentioned that the ClientFactory should be reused in the comment.

However, if I read it right, the ClientFactory is created on every request.

@yookoala @ruudk Should this not be changed in Skipper to only create the ClientFactory once?
See this line in skipper.


I think the problem is with creating new request IDs. When the ClientFactory is not reused, I guess that there are new ID pools created on every request?

Explanation
When getting a diff between two goroutine dumps I see many goroutines in newIDs.func1:

goroutine 75451 [chan send, 10 minutes]:
github.com/yookoala/gofast.newIDs.func1(0xc001323020, 0xffff)
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:102 +0x43
created by github.com/yookoala/gofast.newIDs
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:100 +0x78

goroutine 68511 [chan send, 11 minutes]:
github.com/yookoala/gofast.newIDs.func1(0xc002043500, 0xffff)
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:102 +0x43
created by github.com/yookoala/gofast.newIDs
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:100 +0x78

goroutine 53003 [chan send, 12 minutes]:
github.com/yookoala/gofast.newIDs.func1(0xc001f52f60, 0xffff)
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:102 +0x43
created by github.com/yookoala/gofast.newIDs
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:100 +0x78

goroutine 120002 [chan send, 6 minutes]:
github.com/yookoala/gofast.newIDs.func1(0xc001a0cba0, 0xffff)
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:102 +0x43
created by github.com/yookoala/gofast.newIDs
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:100 +0x78

goroutine 169080 [chan send, 2 minutes]:
github.com/yookoala/gofast.newIDs.func1(0xc000039bc0, 0xffff)
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:102 +0x43
created by github.com/yookoala/gofast.newIDs
	/go/pkg/mod/github.com/yookoala/[email protected]/client.go:100 +0x78

How to dump

  1. Configure -enable-profile for Skipper
  2. Proxy to a Skipper pod kubectl port-forward skipper-ingress-pzx2b 9911:9911
  3. Go to http://127.0.0.1:9911/debug/pprof/ and download a dump full goroutine stack dump, wait a few minutes, and download another dump
  4. Install goroutine-inspect and run it $GOPATH/bin/goroutine-inspect
  5. Make a diff, (l = only in original, c = in both, r = in original 2)
    original = load("goroutine.dump")
    original2 = load("goroutine2.dump")
    l, c, r = original.diff(original2)
    c.show()
    

gofast prefixes the $_SERVER['REQUEST_URI'] with the script name

I used this:

    cf := gofast.SimpleConnFactory("unix", pathToPhpFpmSocket)
    var h http.Handler = gofast.NewHandler(
            gofast.NewFileEndpoint(router)(gofast.BasicSession),
            gofast.SimpleClientFactory(cf, 0),
    )

In my script called index.php I had

 <?php
 echo $_SERVER['REQUEST_URI']

Expected output:

/

Actual output:

/index.php/

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.