Giter Site home page Giter Site logo

kornelski / http-cache-semantics Goto Github PK

View Code? Open in Web Editor NEW
244.0 7.0 27.0 197 KB

RFC 7234 in JavaScript. Parses HTTP headers to correctly compute cacheability of responses, even in complex cases

Home Page: http://httpwg.org/specs/rfc7234.html

License: BSD 2-Clause "Simplified" License

JavaScript 100.00%
response-age perspective usable revalidation http rfc rfc-7234 cache cache-control fresh

http-cache-semantics's Introduction

Can I cache this? Build Status

CachePolicy tells when responses can be reused from a cache, taking into account HTTP RFC 7234 rules for user agents and shared caches. It also implements RFC 5861, implementing stale-if-error and stale-while-revalidate. It's aware of many tricky details such as the Vary header, proxy revalidation, and authenticated responses.

Usage

Cacheability of an HTTP response depends on how it was requested, so both request and response are required to create the policy.

const policy = new CachePolicy(request, response, options);

if (!policy.storable()) {
    // throw the response away, it's not usable at all
    return;
}

// Cache the data AND the policy object in your cache
// (this is pseudocode, roll your own cache (lru-cache package works))
letsPretendThisIsSomeCache.set(
    request.url,
    { policy, response },
    policy.timeToLive()
);
// And later, when you receive a new request:
const { policy, response } = letsPretendThisIsSomeCache.get(newRequest.url);

// It's not enough that it exists in the cache, it has to match the new request, too:
if (policy && policy.satisfiesWithoutRevalidation(newRequest)) {
    // OK, the previous response can be used to respond to the `newRequest`.
    // Response headers have to be updated, e.g. to add Age and remove uncacheable headers.
    response.headers = policy.responseHeaders();
    return response;
}

It may be surprising, but it's not enough for an HTTP response to be fresh to satisfy a request. It may need to match request headers specified in Vary. Even a matching fresh response may still not be usable if the new request restricted cacheability, etc.

The key method is satisfiesWithoutRevalidation(newRequest), which checks whether the newRequest is compatible with the original request and whether all caching conditions are met.

Constructor options

Request and response must have a headers property with all header names in lower case. url, status and method are optional (defaults are any URL, status 200, and GET method).

const request = {
    url: '/',
    method: 'GET',
    headers: {
        accept: '*/*',
    },
};

const response = {
    status: 200,
    headers: {
        'cache-control': 'public, max-age=7234',
    },
};

const options = {
    shared: true,
    cacheHeuristic: 0.1,
    immutableMinTimeToLive: 24 * 3600 * 1000, // 24h
    ignoreCargoCult: false,
};

If options.shared is true (default), then the response is evaluated from a perspective of a shared cache (i.e. private is not cacheable and s-maxage is respected). If options.shared is false, then the response is evaluated from a perspective of a single-user cache (i.e. private is cacheable and s-maxage is ignored). shared: true is recommended for HTTP clients.

options.cacheHeuristic is a fraction of response's age that is used as a fallback cache duration. The default is 0.1 (10%), e.g. if a file hasn't been modified for 100 days, it'll be cached for 100*0.1 = 10 days.

options.immutableMinTimeToLive is a number of milliseconds to assume as the default time to cache responses with Cache-Control: immutable. Note that per RFC these can become stale, so max-age still overrides the default.

If options.ignoreCargoCult is true, common anti-cache directives will be completely ignored if the non-standard pre-check and post-check directives are present. These two useless directives are most commonly found in bad StackOverflow answers and PHP's "session limiter" defaults.

storable()

Returns true if the response can be stored in a cache. If it's false then you MUST NOT store either the request or the response.

satisfiesWithoutRevalidation(newRequest)

This is the most important method. Use this method to check whether the cached response is still fresh in the context of the new request.

If it returns true, then the given request matches the original response this cache policy has been created with, and the response can be reused without contacting the server. Note that the old response can't be returned without being updated, see responseHeaders().

If it returns false, then the response may not be matching at all (e.g. it's for a different URL or method), or may require to be refreshed first (see revalidationHeaders()).

responseHeaders()

Returns updated, filtered set of response headers to return to clients receiving the cached response. This function is necessary, because proxies MUST always remove hop-by-hop headers (such as TE and Connection) and update response's Age to avoid doubling cache time.

cachedResponse.headers = cachePolicy.responseHeaders(cachedResponse);

timeToLive()

Returns approximate time in milliseconds until the response becomes stale (i.e. not fresh).

After that time (when timeToLive() <= 0) the response might not be usable without revalidation. However, there are exceptions, e.g. a client can explicitly allow stale responses, so always check with satisfiesWithoutRevalidation(). stale-if-error and stale-while-revalidate extend the time to live of the cache, that can still be used if stale.

toObject()/fromObject(json)

Chances are you'll want to store the CachePolicy object along with the cached response. obj = policy.toObject() gives a plain JSON-serializable object. policy = CachePolicy.fromObject(obj) creates an instance from it.

Refreshing stale cache (revalidation)

When a cached response has expired, it can be made fresh again by making a request to the origin server. The server may respond with status 304 (Not Modified) without sending the response body again, saving bandwidth.

The following methods help perform the update efficiently and correctly.

revalidationHeaders(newRequest)

Returns updated, filtered set of request headers to send to the origin server to check if the cached response can be reused. These headers allow the origin server to return status 304 indicating the response is still fresh. All headers unrelated to caching are passed through as-is.

Use this method when updating cache from the origin server.

updateRequest.headers = cachePolicy.revalidationHeaders(updateRequest);

revalidatedPolicy(revalidationRequest, revalidationResponse)

Use this method to update the cache after receiving a new response from the origin server. It returns an object with two keys:

  • policy — A new CachePolicy with HTTP headers updated from revalidationResponse. You can always replace the old cached CachePolicy with the new one.
  • modified — Boolean indicating whether the response body has changed.
    • If false, then a valid 304 Not Modified response has been received, and you can reuse the old cached response body. This is also affected by stale-if-error.
    • If true, you should use new response's body (if present), or make another request to the origin server without any conditional headers (i.e. don't use revalidationHeaders() this time) to get the new resource.
// When serving requests from cache:
const { oldPolicy, oldResponse } = letsPretendThisIsSomeCache.get(
    newRequest.url
);

if (!oldPolicy.satisfiesWithoutRevalidation(newRequest)) {
    // Change the request to ask the origin server if the cached response can be used
    newRequest.headers = oldPolicy.revalidationHeaders(newRequest);

    // Send request to the origin server. The server may respond with status 304
    const newResponse = await makeRequest(newRequest);

    // Create updated policy and combined response from the old and new data
    const { policy, modified } = oldPolicy.revalidatedPolicy(
        newRequest,
        newResponse
    );
    const response = modified ? newResponse : oldResponse;

    // Update the cache with the newer/fresher response
    letsPretendThisIsSomeCache.set(
        newRequest.url,
        { policy, response },
        policy.timeToLive()
    );

    // And proceed returning cached response as usual
    response.headers = policy.responseHeaders();
    return response;
}

Yo, FRESH

satisfiesWithoutRevalidation

Used by

Implemented

  • Cache-Control response header with all the quirks.
  • Expires with check for bad clocks.
  • Pragma response header.
  • Age response header.
  • Vary response header.
  • Default cacheability of statuses and methods.
  • Requests for stale data.
  • Filtering of hop-by-hop headers.
  • Basic revalidation request
  • stale-if-error

Unimplemented

  • Merging of range requests, If-Range (but correctly supports them as non-cacheable)
  • Revalidation of multiple representations

Trusting server Date

Per the RFC, the cache should take into account the time between server-supplied Date and the time it received the response. The RFC-mandated behavior creates two problems:

  • Servers with incorrectly set timezone may add several hours to cache age (or more, if the clock is completely wrong).
  • Even reasonably correct clocks may be off by a couple of seconds, breaking max-age=1 trick (which is useful for reverse proxies on high-traffic servers).

Previous versions of this library had an option to ignore the server date if it was "too inaccurate". To support the max-age=1 trick the library also has to ignore dates that pretty accurate. There's no point of having an option to trust dates that are only a bit inaccurate, so this library won't trust any server dates. max-age will be interpreted from the time the response has been received, not from when it has been sent. This will affect only RFC 1149 networks.

http-cache-semantics's People

Contributors

adityapatadia avatar goofballlogic avatar jiangfengming avatar kornelski avatar lukechilds avatar robhanlon22 avatar sithmel 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

http-cache-semantics's Issues

List Got in "Used by" section of readme

Got depends on this indirectly through cacheable-request. In fact, I specifically wrote cacheable-request for use in Got.

Just letting you know as I'm not sure if you're aware and it's quite a popular project so you might want to list it here.

Are empty header values equivalent to absent ones?

There are a lot of falsey checks in this library rather than undefined checks. Here's one, for example. I'm wondering if these should be switched to null/undefined checks (x == null) as it appears that empty header values are valid according to the HTTP RFC. I'm wondering this is that I'm working with an API that is behaving weirdly (returns Pragma: no-cache alongside Cache-Control: ) when I send over a Cache-Control: max-age=3600 header in the request, which is causing this library to consider the response to not be cacheable. Thx!

How to use this in proxy?

I am trying to implement cache in a custom Node.js proxy that is running across multiple machines. The problem is the satisfiesWithoutRevalidation method – it assumes that CachePolicy object is aware of previous requests, which might not be the case.

What would be the risk of using CachePolicy without satisfiesWithoutRevalidation? Effectively, if I want to rely only on Cache-Control as dictated by the response.

113 warning on heuristic usage

The spec requires that a 113 warning code be added to the response if revalidation used heuristics. There doesn't seem to be any way for external libraries to detect whether http-cache-semantics used these heuristics, either.

timeToLive() returns floats

Not always of course, but in some cases timeToLive() returns floats.
Which makes sense since in maxAge() there's a division by 1000 and then later a multiplication by 1000 done by timeToLive(), yet the code ignores computers' struggle with float arithmetic.
Since the doc mentions timeToLive() returns time in milliseconds, one would expect it to return an integer.

Math.round() in the correct spot or some other solution would be appreciated, let me know what you think.

What unit is immutableMinTimeToLive in?

The readme says that "immutableMinTimeToLive is a number of milliseconds", but it's compared against values which are in seconds (e.g. Math.max(defaultMinTtl, (expires - dateValue) / 1000)) and returned from maxAge which on other paths returns values in seconds.

In my opinion, the implementation is reasonable so it's the documentation which is wrong; what do you think?

The default value is 24 * 3600 * 1000, which is either one day or 1,000 days depending on the answer to this question. I don't see anything in the RFC guiding how clients should behave if max-age is not set but immutable is. Since "hours * seconds * milliseconds" order makes a little more sense than "hours * seconds * days", I'm guessing it was supposed to be one day, in which case the default value is wrong too.

While you're looking at the documentation for this option:

  • The link to "per RFC" is dead. I believe at this point you want to link to https://tools.ietf.org/html/rfc8246.
  • Please document the default value for this option, whatever you decide it should have been.

Revalidated policy contains new response headers even if not modified

ORIGINAL POLICY {
  'content-type': 'application/json',
  'last-modified': 'Wed, 27 Nov 2019 01:56:57 GMT',
  'content-encoding': 'gzip',
  age: '449457',
  date: 'Sun, 08 Dec 2019 14:21:24 GMT',
  warning: '113 - "rfc7234 5.5.4"'
}
REVALIDATED POLICY modified=false {
  age: '449457',
  date: 'Sun, 08 Dec 2019 14:21:24 GMT'
}

I just placed

console.log('ORIGINAL POLICY', CachePolicy.fromObject(revalidate.cachePolicy).responseHeaders());
console.log('REVALIDATED POLICY', `modified=${revalidatedPolicy.modified}`,  revalidatedPolicy.policy.responseHeaders());

after this line:

https://github.com/lukechilds/cacheable-request/blob/147924989daf078b071be3aa4ad67cc6702512f0/src/index.js#L87

I will include some reproducible code soon.

Version 3.8.0 throws error in npm run build

using reference:
"http-cache-semantics": "3.8.0",
causes the following error:
image

ERROR in bundle.js from UglifyJs
SyntaxError: Unexpected character '`' [./~/http-cache-semantics/node4/index.js:279,0]
[13:41:33] Finished 'buildWebpack' after 39 s
[13:41:33] Finished 'html' after 40 s

Support stale-while-revalidate and stale-if-error

Hi @kornelski, thanks for your work on this library, it looks fantastic.

For my use case, I would need the library to support these two Cache-Control directives. From MDN:

  • stale-while-revalidate=<seconds>: Indicates that the client is willing to accept a stale response while asynchronously checking in the background for a fresh one. The seconds value indicates for how long the client is willing to accept a stale response.
  • stale-if-error=<seconds>: Indicates that the client is willing to accept a stale response if the check for a fresh one fails.

I think it could be implemented in the following way:

  • satisfiesWithoutRevalidation() would check the stale-if-error directive and return true if the response is a 5XX and the stale response can be used.
  • A new method satisfiesWithRevalidation() would check the stale-while-revalidate directive and return true if the stale response can be used while revalidating in the background. The caller would be expected to make the request while also using the cached response.

If you're open to these enhancements, I'd be happy to work on them.

pragma=no-cache is ignored when cache-control is present

cache-control: max-age=600, must-revalidate, post-check=0, pre-check=0, public
date: Mon, 15 Jul 2019 11:42:17 GMT
pragma: no-cache

From this website:

https://www.odeon.co.uk/booking/init/MjkxMjQwMDAwMjNJV0ZDT1BGIzEwMiMxODQ5Ng==/

Not sure what is the expected behaviour here, but I would expect that given that pragma: no-cache is present, then it should't be safe to cache the response regardless of cache-control. Meanwhile,

> cachePolicy.storable()
true
> cachePolicy.timeToLive()
600000

breaks if Cache-Control is array

curl -v https://www.reservaentradas.com/cines
< HTTP/1.1 200 OK
< Date: Wed, 05 Feb 2020 18:51:26 GMT
< Server: Apache/2.2.22 (Debian)
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
< Vary: Accept-Encoding
< Cache-Control: max-age=0, no-cache
< Transfer-Encoding: chunked
< Content-Type: text/html; charset=utf-8
<

Produces error:

TypeError: header.trim is not a function
          at parseCacheControl (/Users/gajus/Documents/dev/rayroute/rayman/node_modules/http-cache-semantics/index.js:62:26)
          at new CachePolicy (/Users/gajus/Documents/dev/rayroute/rayman/node_modules/http-cache-semantics/index.js:119:23)
          at action (/Users/gajus/Documents/dev/rayroute/rayman/src/interceptors/createCacheInterceptor/index.js:68:27)
          at Object.responseActions (/Users/gajus/Documents/dev/rayroute/rayman/node_modules/@rayroute/raygun/src/factories/createInterceptorActions.js:152:22)
          at processTicksAndRejections (internal/process/task_queues.js:97:5)
          at handleRequest (/Users/gajus/Documents/dev/rayroute/rayman/node_modules/@rayroute/raygun/src/factories/createRequestHandler.js:69:28)

Implement interface for invalidation after POST/PUT

A cache MUST invalidate the effective Request URI (Section 5.5 of [RFC7230]) as well as the URI(s) in the Location and Content-Location response header fields (if present) when a non-error status code is received in response to an unsafe request method.

However, a cache MUST NOT invalidate a URI from a Location or Content-Location response header field if the host part of that URI differs from the host part in the effective request URI (Section 5.5 of [RFC7230]). This helps prevent denial-of-service attacks.

A cache MUST invalidate the effective request URI (Section 5.5 of [RFC7230]) when it receives a non-error response to a request with a method whose safety is unknown.

http://httpwg.org/specs/rfc7234.html#invalidation

Cache response without header if no-cache specifies a field-name

The cache control RFC states in section 5.2.2.2:

If the no-cache response directive specifies one or more field-names, then a cache MAY use the response to satisfy a subsequent request, subject to any other restrictions on caching. However, any header fields in the response that have the field-name(s) listed MUST NOT be sent in the response to a subsequent request without successful revalidation with the origin server. This allows an origin server to prevent the re-use of certain header fields in a response, while still allowing caching of the rest of the response.

Specifically, from what I have seen, if a no-cache value appears in the Cache-Control header then it does not appear to be cached, even if the no-cache value specifies a header field. There is an opportunity to abide by the MAY clause in the spec, where the response can still be cached (except for the specified field(s)).

For example, if we see no-cache: set-cookie it is possible, according to the spec, that the response can be cached EXCEPT for Set-Cookie header(s).

I'm opening this issue to look into whether this part of the spec can be implemented in the library so that the response can still be cached.

Request cache-control: max-age header is ignored

Request headers:

{
  host: '127.0.0.1:3050',
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
    'AppleWebKit/537.36 (KHTML, like Gecko) ' +
    'Chrome/75.0.3770.100 Safari/537.36',
  accept: '*/*',
  'accept-encoding': 'gzip',
  'proxy-connection': 'Keep-Alive',
  'cache-control': 'max-age=2'
}
cachePolicy.storable() true
cachePolicy.timeToLive() 3599997

I expect timeToLive to be 2000 regardless of the response headers.

Unable to handle the Header Object

Hey, this might be because I'm using this library incorrectly, so sorry in advance if that's the case.

It looks like this library tries to access header objects as if they were in a traditional object literal (https://github.com/kornelski/http-cache-semantics/blob/master/index.js#L137)

But, the Response object uses the Headers object. The Headers object requires developers to retrieve headers with the .get method.

This means that this library can't read headers, and thus can't determine if something can be cached properly. Take the following example:

const uri = "https://httpbin.org/response-headers?cache-control=max-age%3D604800";
const request = new Request(uri);
const response = await fetch(request);
const policy = new CachePolicy(request, response, options);
const newRequest = new Request(uri);
// Returns false because "maxAge" is 0 because the library can't read the headers
console.log(policy.satisfiesWithoutRevalidation(newRequest));

I am using this library in a NodeJS environment using Request, fetch, and Response from cross-fetch.

Am I using this wrong?

How to check if a non-blocking revalidation should happen?

Hi, love this library! A great addition would be a brief sample implementation of how to use policy.useStaleWhileRevalidate() in the readme. I haven't quite got my head around how to use it in combination with satisfiesWithoutRevalidation.

Basically, I'd like to know when to kick off a background request in order to update the cache, but it's still ok to use the value that's already in the cache.

`policy.storable()` returns false when response status is 304

I'm following your code example for revalidation https://www.npmjs.com/package/http-cache-semantics#revalidatedpolicyrevalidationrequest-revalidationresponse

My newResponse.status is 304, I pass the response to oldPolicy.revalidatedPolicy() and get a new policy in return.

Now policy.storable() returns false and policy.timeToLive() returns 0, due to this condition evaluating to false because this._status is 304.

understoodStatuses.has(this._status) &&

This makes the example code kinda funky, because you're caching the response in letsPretendThisIsSomeCache with a 0 ttl.

Since the resource was successfully revalidated, I expect storable() to return true and timeToLive() to return some non zero number. Is this intended?

Node.js v4 EOL warning

Node.js v4 official support ends on April 30th. This project follows the official long-term support schedule, so it will remove Node.js v4 compatibility on that day.

Please upgrade to Node 8 ASAP. This is not a drill.

Trying to cache authenticated github api responses

I'm making authenticated github api requests and want them cached with got and cacheable-request libraries. Github returns Cache-Control: private, but I expect it still to be cached when I pass shared: false in opts. It's not getting cached.

Here's the response headers, maybe someone can help me how I can make it cacheable?

{ server: 'GitHub.com',
  date: 'Sat, 21 Apr 2018 12:14:45 GMT',
  'content-type': 'application/json; charset=utf-8',
  'transfer-encoding': 'chunked',
  connection: 'close',
  status: '200 OK',
  'x-ratelimit-limit': '5000',
  'x-ratelimit-remaining': '4836',
  'x-ratelimit-reset': '1524313615',
  'cache-control': 'private, max-age=60, s-maxage=60',
  vary: 'Accept, Authorization, Cookie, X-GitHub-OTP',
  etag: 'W/"4876f954d40e3efc6d32aab08b9bdc47"',
  'x-oauth-scopes': 'public_repo, read:user, repo:invite, repo:status, repo_deployment',
  'x-accepted-oauth-scopes': '',
  'x-github-media-type': 'github.v3',
  link: '<https://api.github.com/user/4757745/starred?per_page=1&page=2>; rel="next", <https://api.github.com/user/4757745/starred?per_page=1&page=1120>; rel="last"',
  'access-control-expose-headers': 'ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval',
  'access-control-allow-origin': '*',
  'strict-transport-security': 'max-age=31536000; includeSubdomains; preload',
  'x-frame-options': 'deny',
  'x-content-type-options': 'nosniff',
  'x-xss-protection': '1; mode=block',
  'referrer-policy': 'origin-when-cross-origin, strict-origin-when-cross-origin',
  'content-security-policy': 'default-src \'none\'',
  'x-runtime-rack': '0.051653',
  'content-encoding': 'gzip',
  'x-github-request-id': 'C6EE:12E7:3E6CA0D:87F0004:5ADB2B35' }

ps: beginning of story: sindresorhus/got#480

Dropping trustServerDate is a breaking change

Hey, I completely agree with the justification provided in the README, but dropping support for this option is a breaking change, in the SemVer sense of "a change that requires downstream users to alter their code in order to continue working properly with the library".

This broke a few of my tests, and I now have to go and figure out whether I want to pin the version to 4.0, alter the tests to provide an accurate date header, or just live with the red CI. My case is not so horrible, since it does work fine except in the synthetic edge cases that the test is (no longer) exercising, but it's entirely possible that someone using this library had production code broken as a result.

I recommend reverting this change in a 4.1.1 release, and then re-publishing it in a 5.0.0 release. There are two reasonable ways to do this:

legacy branch

This is the approach I typically use when I am on the other end of an issue like this.

git checkout -b v4-legacy
git reset --hard eb7028f13f1b2e6978834c9a51b9d3f0d733ab77
# add `"publishConfig": { "tag": "legacy" }` to package.json
git commit -m "publish to 'legacy' tag on npm"
npm version 4.1.1
npm publish
git checkout master
npm version major # bumps to 5.0.0
npm publish

Thereafter, any patches you want to land on the v4 branch (where server date is trusted) you can do on the v4-legacy branch, and npm publish, without worrying that it clobbers the "real" latest release on npm.

revert and re-revert

This is a simpler single-branch approach if you're pretty sure you're not going to ever have to touch v4 again.

git revert ed83aec75be817967cdac2663907d060fdc6adc3
git revert 1b359803db768e0b3f5f8051aa3ea50e27c8d096
git revert 2c2fac2d9763137795cac524db2593de9cba70ac
npm version 4.1.1
npm publish
# re-land the commits that you just reverted
# note that the last one will have a conflict on the package.json version,
# which will have to be resolved by setting it back to 4.1.1 instead of 4.1.0
git cherry-pick 2c2fac2d9763137795cac524db2593de9cba70ac
git cherry-pick 1b359803db768e0b3f5f8051aa3ea50e27c8d096
git cherry-pick ed83aec75be817967cdac2663907d060fdc6adc3
npm version 5.0.0

And never look back.

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.