Code Monkey home page Code Monkey logo

ratelimitj's People

Contributors

bsideup avatar ctoomey avatar danbar74-afterpay avatar igorbolic avatar lanwen avatar ls-cyril-grosset avatar mokies avatar ofirnk avatar timbotetsu avatar unev 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

ratelimitj's Issues

Unusual current rate limit conditions sometimes occur

This is a great project,But I have encountered some problems in use it, i change ReidsClient to RedisClusterClient and 'StatefulRedisConnection' and to StatefulRedisClusterConnection, such as below code:

    RedisClusterClient client = RedisClusterClient.create(getRedisUri(properties.getRedisCluster())); 
     GenericObjectPool<StatefulRedisClusterConnection<String, String>> pool = ConnectionPoolSupport
         .createGenericObjectPool(() -> client.connect(), new GenericObjectPoolConfig());
 
     try {
       StatefulRedisClusterConnection<String, String> connection = pool.borrowObject()
Set<RequestLimitRule> rules = Collections.singleton(RequestLimitRule.of(1, TimeUnit.SECONDS, 1000).withPrecision(2));

Occasionally, the current limit will occur. I have determined that the maximum flow rate has not been reached. It often happens around 1 second,my question is why and how to fix it ? thanks.

the server env:

Redis Cluster:  3.x   (3 master and 3 slave)
CentOS 7.x

Don't see any log related to @RateLimited

Hi

I am using RateLimitJ - Dropwizard lib to maintain the distributed rate limit in one of my projects but I observe nothing is logged on limit details.

Am I missing something ? kindly help me out. Thanks in advance.

In-Memory Rate Limiter allows twice as much requests when using default precision

Hi,

I may be confused with the 'precision' but I don't get the expected results when I use the default precision.

When using default precision (i.e. precision = duration)

When I set a single rule for a limit of X requests per Y seconds, the rate limiter actually allows 2X requests per Y seconds to pass through. More interesting, it seems like this problem decays and disappears when we wait enough time or there are enough requests - i'm not sure.

When using precision = 1

When I use precision = 1, the results I get are much more what I'm looking for but it feels like a "fixed window" algorithm rather than a sliding one.
Also,

I've created a very simple test application for testing this issue and made a CSV output of the results.
I also made a scatter chart in Excel to visualize the problem very clearly.

You can download these Excel files from here:

Test 1: duration = 5 seconds, limit = 1, precision = 3 (default)
https://www.dropbox.com/s/llk479gbq5e3c4t/out1.xlsx?dl=0

Test 2: duration = 3 seconds, limit = 3, precision = 3 (default)
https://www.dropbox.com/s/yi88xd6e9zp9m46/out2.xlsx?dl=0

Test 3: duration = 3 seconds, limit = 3, precision = 1
https://www.dropbox.com/s/ag61g3osuteor9v/out3.xlsx?dl=0

Here is the test application code:

public class Application {

  private final static long TEST_DURATION_MILLIS = 120000;

  public static void main(String[] args) throws InterruptedException, IOException {
    Set<RequestLimitRule> rules = Collections
        .singleton(RequestLimitRule.of(Duration.ofSeconds(3), 3).withPrecision(1));
    RequestRateLimiter requestRateLimiter = new InMemorySlidingWindowRequestRateLimiter(rules);

    try (PrintWriter writer = new PrintWriter(new FileWriter("out.csv"))) {
      writer.write("timestamp,delta time,time from last 'under limit',under limit\r\n");

      long startTime = time();
      long currentTime = 0;
      long previousTime = time();
      long previousUnderLimitTime = time();
      while ((currentTime - startTime) < TEST_DURATION_MILLIS) {
        currentTime = time();
        Timestamp timestamp = new Timestamp(currentTime);

        boolean underLimit = !requestRateLimiter.overLimitWhenIncremented("x");

        writer.write(timestamp
            + ","
            + (currentTime - previousTime)
            + ","
            + (currentTime - previousUnderLimitTime)
            + ","
            + underLimit
            + "\r\n");

        if (underLimit) {
          previousUnderLimitTime = currentTime;
        }
        previousTime = currentTime;

        Thread.sleep(100);
      }
    }

  }

  private static long time() {
    return System.currentTimeMillis();
  }

}

And one more probably related question

Why when I set the 2 following rules I get only 2 requests under-limit per each 10 seconds (and not 4)?

RequestLimitRule.of(Duration.ofSeconds(10), 4).withPrecision(1)
RequestLimitRule.of(Duration.ofSeconds(2), 2).withPrecision(1)

Thank you very much!
Asaf

Next release plan?

Hi Craig, when are you planning to do the next release with the changes since M2?

Redis key expiration and index of the database

Thanks for the great lib.

May I know if it is possible to:

  1. set the expiration on the key?
  2. set the index of the database used by this connection factory? I'd like to use a different index from my other caches.

The library fails to handle a missing script

Hi!

We use RateLimitJ with AWS ElastiCache. Today we had an outage because AWS ElastiCache performed a maintenance and for some reason (we're going to check with AWS guys as well) the Lua scripts disappeared.
We started getting this error:

NOSCRIPT No matching script. Please use EVAL.

Would be great if the library will handle it gracefully and re-upload the scripts if they're missing.
I assume this is easy to test by uploading the scripts, running FLUSHALL and then attempting to apply the rate limiting.
Shameless plug: you can use http://github.com/testcontainers/testcontainers-java to test that ☺️

Return which limit rule was reached

We love your published library and very likely to use it.

One idea to improve is to return not just a simple boolean but a rich object of type {isOverLimit:boolean, limitingRule:RequestLimitRule} or similar.
Exact type is not critical, it's just the idea of letting the user (be it internal or external) - which limit was reached.

So currently - the user would just get false when she's limited.
After - she'd know what exactly blocked her access.

WDYT?

p.s
I know it's a major refactor to internal logic + interface.. but it can be done in a non-breaking mode

Rate limit does not work when working with other bundles

I have following bundle setup for my application, it seems that if we have other bundle added into the BootStrap, the rate limit is not working and would throw NullPointerException

 LOGGER.info("starting to do initialization...");
    LOGGER.info("adding ratelimit factory bundle");
    RequestRateLimiterFactory factory = new InMemoryRateLimiterFactory();
    bootstrap.addBundle(new RateLimitBundle(factory));
    LOGGER.info("added ratelimit factory bundle");
    guiceBundle = GuiceBundle.<KrylovMasterConfiguration>newBuilder()
        .addModule(new ConfigModule())
        .addModule(new ResourceModule())
        .addModule(new CoreServiceModule())
        .addModule(new AuthModule())
        // .addModule(new CommonModule()) // Needed for workflow and workspace, do not remove
        //.addModule(new ZookeeperClientModule()) //Needed for workflow and workspace, do not remove
        .addModule(new DefaultServerModule())
        .addModule(new DefaultWorkspaceModule())
        .addModule(new ExtensionModule())
        .addModule(new RateLimitModule())
        //.enableAutoConfig(getClass().getPackage().getName())  // disable this to prevent accidental registration.
        .setConfigClass(KrylovMasterConfiguration.class)
        .build();
// if we add this line of code. rate limit won't work
    bootstrap.addBundle(guiceBundle);

the exception is

2020-12-28 08:41:58 thread:[dw-47 - GET /api/hello-world?name=zhonwu] ERROR e.m.r.d.f.RateLimit429EnforcerFilter - Error occurred checking rate-limit. Assuming under limit
java.lang.NullPointerException: null
	at es.moki.ratelimij.dropwizard.filter.RateLimit429EnforcerFilter.filter(RateLimit429EnforcerFilter.java:51)
	at org.glassfish.jersey.server.ContainerFilteringStage.apply(ContainerFilteringStage.java:132)
	at org.glassfish.jersey.server.ContainerFilteringStage.apply(ContainerFilteringStage.java:68)
	at org.glassfish.jersey.process.internal.Stages.process(Stages.java:197)
	at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:318)
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
	at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
	at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
	at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473)
	at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341)
	at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228)
	at io.dropwizard.jetty.NonblockingServletHolder.handle(NonblockingServletHolder.java:49)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1655)
	at io.dropwizard.servlets.ThreadNameFilter.doFilter(ThreadNameFilter.java:35)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)
	at io.dropwizard.jersey.filter.AllowedMethodsFilter.handle(AllowedMethodsFilter.java:45)
	at io.dropwizard.jersey.filter.AllowedMethodsFilter.doFilter(AllowedMethodsFilter.java:39)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)
	at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:89)
	at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:120)
	at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:135)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:533)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1317)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1219)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
	at com.codahale.metrics.jetty9.InstrumentedHandler.handle(InstrumentedHandler.java:239)
	at io.dropwizard.jetty.RoutingHandler.handle(RoutingHandler.java:52)
	at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:724)
	at io.dropwizard.jetty.BiDiGzipHandler.handle(BiDiGzipHandler.java:67)
	at org.eclipse.jetty.server.handler.RequestLogHandler.handle(RequestLogHandler.java:56)
	at org.eclipse.jetty.server.handler.StatisticsHandler.handle(StatisticsHandler.java:169)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
	at org.eclipse.jetty.server.Server.handle(Server.java:531)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:352)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:281)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102)
	at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126)
	at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:762)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:680)
	at java.lang.Thread.run(Thread.java:748)

Dynamically change the limits at runtime

I was using this library(Redis module) and really wanted to use it to dynamically rate limit APIs at runtime. But looking at the code it appears like limit rule is bound to instance created by the factory. So to make it to work dynamically, I need to create a new instance with new limit rule.
Is my understanding correct or is there a way to change limit rule on the instance already created?

Spring + AOP support

Hello!

few years ago I worked on a similar project (https://github.com/ecostanzi/spring-rate-limiter). The idea was using annotations to rate limiting method calls. Since on the rate limiter section this project seems more mature I tried to merge the two. I might use this in some project next month so let me know what you think.

Support for knowing remaining limit

There are scenarios where the client is interested in knowing the remaining limit. This information is usually passed back to client as part of response headers.
Can we add a method like
long getRemainingLimitInCurrentWindow(String key) in es.moki.ratelimitj.core.limiter.request.RequestRateLimiter
This will definitely help in utilising this library in web filters.
Please advise I am interested in taking this up.

Include backoff period for a given rule

Hey!

We have been using the library for a while (with redis) and we would like to complement it with a backoff period on top of the sliding window algorithm. We believe this would be a nice improvement on it by giving an extra punishment to whoever is misusing our systems.

"Tokens" will be used and added back like we are used to in a sliding window algorithm, but the change is:

  • If anyone crosses the limit, we will apply a backoff period of x amount of time blocking that key.

We could probably achieve that by populating the cache of a given key like time:precision:bo with the expiry of the backoff period for example, and simply return 1 early in the algorithm

This would be a backwards compatible change, probably on the RequestLimitRule adding the possibility to have the backoff only to the rules that configured it.

Do you find this interesting and would be willing to have it on the library? I can work on it and open the necessary PRs.
In case you agree, feel free to point me to any directions you may think it would be the best.

Dropwizard 2.X Support

Hi, we're using ratelimitj for quite a while now and right now it's the only thing keeping us from uprading to dropwizard 2.0.
Are there any plans to support it in the near future?
Thanks

NoClassDefFoundError: reactor/core/scheduler/Schedulers

I have the following dependencies in the gradle file.

dependencies {
. . .
// 'ratelimitj-redis-0.4.1.jar'
compile ('io.projectreactor:reactor-core:3.1.2.RELEASE')
compile ('es.moki.ratelimitj:ratelimitj-redis:0.4.1')
. . .
}
The first line throws me an NoClassDefFoundError. Any idea to workaround this?

RedisClient client = RedisClient.create(RedisURI.create("localhost", 6379));

Caused by: java.lang.NoClassDefFoundError: reactor/core/scheduler/Schedulers
at io.lettuce.core.resource.DefaultClientResources.(DefaultClientResources.java:171)
at io.lettuce.core.resource.DefaultClientResources$Builder.build(DefaultClientResources.java:461)
at io.lettuce.core.resource.DefaultClientResources.create(DefaultClientResources.java:229)
at io.lettuce.core.AbstractRedisClient.(AbstractRedisClient.java:96)
at io.lettuce.core.RedisClient.(RedisClient.java:86)
at io.lettuce.core.RedisClient.create(RedisClient.java:123)
at io.squelch.collector.api.controller.OAuthController.health(OAuthController.java:78)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)

some docs required

This Lib looks exactly to what I was looking for, but the lack of documentation makes it difficult to get started.
A quick example on how to use the APIs to limit REST calls should be enough. Also Spring Boot integration will be quite handy.

java.lang.NoSuchMethodError when using lettuce 6.x

It looks like lettuce 6.x introduced a binary incompatibility that makes ratelimitj break at runtime.

The breakage we're running into is that RedisScriptLoader calls RedisScriptingReactiveCommands.scriptLoad, and this changed from being a single method with the signature Mono<String> scriptLoad(V script) to a pair of methods, Mono<String> scriptLoad(String script) and Mono<String> scriptLoad(byte[] script).

This change appears to be source compatible with ratelimitj. I tried changing ratelimitj's dependency on lettuce from io.lettuce:lettuce-core:5.3.4.RELEASE to io.lettuce:lettuce-core:6.0.2.RELEASE, and ./gradlew ratelimitj-redis:check builds and passes with the new version of lettuce.

Working with Redis cluster

I am wondering if this could be use with Redis cluster.
As far as I know, LUA scripts does not work with Redis cluster.
Can someone explain this to me please.

Hard redis Dependency

If redis is down the whole reactive app stops working . This is due to the fact that there is no timeout setup

image

To fix the issue

public static final ExecutorService CANCEL_EXECUTOR = Executors.newCachedThreadPool(); .cancelOn(Schedulers.fromExecutor(RedisCacheApiConfiguration.CANCEL_EXECUTOR)) .timeout(Duration.ofMillis(config.getReadTimeout()))
Notice we will need a different scheduler for cancel because redis has a bug where on cancel it blocks the core thread.

Outdated reactor-core version

I'm using Ratelimitj with my Spring Boot application. In Spring Boot I use spring-boot-starter-webflux:2.4.1 and it requires io.projectreactor:reactor-core. The problem is that ratelimitj-redis:0.7.0-RC3 needs reactor-core:3.3.8.RELEASE and spring-boot-starter-webflux:2.4.1 needs reactor-core:3.4.1 and there are some breaking changes between these 2 versions.

To be more specific I'm getting the following error: java.lang.NoSuchMethodError: 'reactor.core.publisher.Flux reactor.core.publisher.Flux.retry(long, java.util.function.Predicate)' in RedisSlidingWindowRequestRateLimiter.java:139. I think the error is quite self-explanatory.

@igorbolic already created a PR in which he fixes the problem: #50

@mokies any chance to merge it anytime soon and publish a new release?

Thanks in advance!

Cool off period

I’ve a use case where the provider wants us to throttle the calls not by some fixed rate/second, but by individual user ids. In other words, a user must wait for a period x before making a call again. So it’s more like a cool off period than the usual rate limiting.

One obvious way to approach this problem is to create a rate limiter per user. But there will be many such limiters, and very short lived. Are there better ways?

Java 1.7

Hi,

Many thanks for your library! I'm trying to use it within one Tibco Business Works project but unfortunately Business Works does not support Java 8. Is it possible to have ratelimitj build with 1.7?

Kind Regards,
Milan

Method for checking if over limit w/o updating

Hi, your library looks great, with the best-of-breed sliding window algorithm and the pluggable backends!

I've got a use case that it doesn't support very well though, where I need to check if the limit has been reached without incrementing it if not. We're going to track login failures, and once we've reached the limit, disallow further login attempts. So we first need to check if the failure rate has reached limit, and only then allow the request and increment the count if it fails.

So I'm thinking of adding another method to RequestRateLimiter to enable this, say

    /**
     * Determine if the given key, after incrementing by the given weight, is &gt;= the configured rate limit.
     * @param key key.
     * @param weight A variable weight.
     * @return {@code true} if the key is &gt;== the limit, otherwise {@code false} .
     */
    boolean geLimit(String key, int weight);

Looking at the implementation code, this would be easily implemented since it's a slight variant of the existing code for overlimit().

Would you be open to a pull request if I add this?

thx,
Chris

Project status

Are you considering this project still active? It looks like it has some nice features, but development seems to have stopped, and the CI status is failing. If it's still live, I'm interested in helping get some improvements made (especially Spring advice support).

Getting exception while calling overLimitWhenIncremented

Hi,
I am getting below error while calling overLimitWhenIncremented
Pleae guide me if I am doing anything wrong

2019-09-06 12:26:26.821 DEBUG 15328 --- [nio-7070-exec-2] o.s.web.servlet.DispatcherServlet        : Failed to complete request: io.lettuce.core.RedisException: Connection is closed
2019-09-06 12:26:26.832 ERROR 15328 --- [nio-7070-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is io.lettuce.core.RedisException: Connection is closed] with root cause


io.lettuce.core.RedisException: Connection is closed
	at io.lettuce.core.protocol.DefaultEndpoint.validateWrite(DefaultEndpoint.java:195) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.protocol.DefaultEndpoint.write(DefaultEndpoint.java:137) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.RedisChannelHandler.dispatch(RedisChannelHandler.java:187) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.StatefulRedisConnectionImpl.dispatch(StatefulRedisConnectionImpl.java:152) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.RedisPublisher$RedisSubscription.dispatchCommand(RedisPublisher.java:390) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.RedisPublisher$CommandDispatch$1.dispatch(RedisPublisher.java:477) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.RedisPublisher$RedisSubscription.checkCommandDispatch(RedisPublisher.java:385) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.RedisPublisher$State$2.request(RedisPublisher.java:543) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.RedisPublisher$RedisSubscription.request(RedisPublisher.java:242) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:102) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.FluxReplay$ReplaySubscriber.onSubscribe(FluxReplay.java:1148) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:64) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at io.lettuce.core.RedisPublisher$State$1.subscribe(RedisPublisher.java:522) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.RedisPublisher$RedisSubscription.subscribe(RedisPublisher.java:225) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at io.lettuce.core.RedisPublisher.subscribe(RedisPublisher.java:126) ~[lettuce-core-5.1.8.RELEASE.jar:na]
	at reactor.core.publisher.MonoFromPublisher.subscribe(MonoFromPublisher.java:43) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.FluxSourceMono.subscribe(FluxSourceMono.java:46) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.Flux.subscribe(Flux.java:7921) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.FluxReplay.connect(FluxReplay.java:1052) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.FluxAutoConnectFuseable.subscribe(FluxAutoConnectFuseable.java:62) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoNext.subscribe(MonoNext.java:40) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoMap.subscribe(MonoMap.java:55) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.Mono.subscribe(Mono.java:3852) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:128) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoFlatMapMany.subscribe(MonoFlatMapMany.java:49) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.Flux.subscribe(Flux.java:7921) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.resubscribe(FluxRetryPredicate.java:123) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.FluxRetryPredicate.subscribe(FluxRetryPredicate.java:52) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoSingle.subscribe(MonoSingle.java:58) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoMapFuseable.subscribe(MonoMapFuseable.java:56) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.MonoPeekTerminal.subscribe(MonoPeekTerminal.java:61) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at reactor.core.publisher.Mono.block(Mono.java:1517) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
	at es.moki.ratelimitj.redis.request.RedisSlidingWindowRequestRateLimiter.throwOnTimeout(RedisSlidingWindowRequestRateLimiter.java:153) ~[ratelimitj-redis-0.6.0.jar:na]
	at es.moki.ratelimitj.redis.request.RedisSlidingWindowRequestRateLimiter.overLimitWhenIncremented(RedisSlidingWindowRequestRateLimiter.java:73) ~[ratelimitj-redis-0.6.0.jar:na]
	at es.moki.ratelimitj.redis.request.RedisSlidingWindowRequestRateLimiter.overLimitWhenIncremented(RedisSlidingWindowRequestRateLimiter.java:68) ~[ratelimitj-redis-0.6.0.jar:na]
	at com.srijan.throttling.StudentResource.getStudent(StudentResource.java:26) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_221]
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_221]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:1.8.0_221]
	at java.lang.reflect.Method.invoke(Unknown Source) ~[na:1.8.0_221]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:118) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) [na:1.8.0_221]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [na:1.8.0_221]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.22.jar:9.0.22]
	at java.lang.Thread.run(Unknown Source) [na:1.8.0_221]
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:133) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
		at reactor.core.publisher.Mono.block(Mono.java:1518) ~[reactor-core-3.2.11.RELEASE.jar:3.2.11.RELEASE]
		... 58 common frames omitted

Adding code for creating rate limiter

@Bean
	public RequestRateLimiter createRequestRateLimiter() {
		RedisClient redisClient = RedisClient.create(RedisURI.create("127.0.0.1", 6379));
		RequestRateLimiter rateLimiter = null;
		try(RedisRateLimiterFactory factory = new RedisRateLimiterFactory(redisClient)){
			rateLimiter = factory.getInstance(Collections.singleton(RequestLimitRule.of(Duration.ofMinutes(1), 10)));
		}
		return rateLimiter;
	}

Adding pom.xml here

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.srijan.throttling</groupId>
	<artifactId>RateLimitJ</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>RateLimitJ</name>
	<description>Project for Demo of Throttling using RateLimitJ</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>


		<dependency>
			<groupId>es.moki.ratelimitj</groupId>
			<artifactId>ratelimitj-redis</artifactId>
			<version>0.6.0</version>
		</dependency>



	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

redis 3.0 cluster ,client can not auto failover

redis version 3.2 and cluster is build with official solution
https://redis.io/topics/cluster-tutorial
i create the cluster:
master A and slave a
master B and slave b
master C and slave c

when my application started ,client will auto create connect with master node A,B,C.
but when i killed node A , client can not failover to a( a has upgrade to Master this time)
client will always send command to A node, but the problem is, node A is down.

it should auto update the cluster infomation, and auto reconnect to the new master node a.

is requestLimitRules really needed at the time of RedisSlidingWindowRequestRateLimiter creation?

Thank you for all your hard work. Am trying to include your work and realized we must have requestLimitRules in hand before we create RedisSlidingWindowRequestRateLimiter instance. I would like to create vanilla RedisSlidingWindowRequestRateLimiter instance during application startup and inject different rules at different point in the process.

So I was looking at your code and thinking whether we can create vanilla/generic RedisSlidingWindowRequestRateLimiter without tying to limit rules and expose public method to pass in serialized rules later when needed?

Something like
public RedisSlidingWindowRequestRateLimiter(StatefulRedisConnection<String, String> connection)

Highly appreciate your thoughts on my approach.

Release 0.7.0

Hi,

We noticed that 0.7.0-RC3 has been released a while ago. Assuming things are pretty stable, would it be possible to final release?

Also, it seems that ratelimitj-inmemory is only available as 0.7.0-RC1

Bug in computing blockId

The computation of blockId is as follows:
blockId = Math.floor(time_now/precision);

Currently, while computing the blockId in all the test cases the BanditSupplier starts the timer at perfectly rounded count of 1000000. So whenever a delta time is added to this count the blockId works as expected.

Consider the following situation:
If the BanditSupplier was started with a counter of 1000007 and the Ratelimiter rule of (duration=10sec, limit = 5, precision = 10).
The window starts with the blockId of (1000007/10) i.e 100000

With an elapse of 3 seconds, the timer count now is 1000010 and the corresponding blockId is 100001 which results in a different window and defeats the purpose of FixedWindowRateLimiting

In memory sliding window seems to be not honouring the limits

I have very simple example of in memory sliding window. Limit is one call for a give key and duration is 10 seconds. It starts of with limiting one request but after 10th second two calls are being allowed.

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import es.moki.ratelimitj.core.limiter.request.RequestLimitRule;
import es.moki.ratelimitj.core.limiter.request.RequestRateLimiter;
import es.moki.ratelimitj.inmemory.request.InMemorySlidingWindowRequestRateLimiter;

public class RateLimitExample {

    public static void main(final String... strings) {

        final Duration precisionDuration = Duration.of(1, ChronoUnit.SECONDS);

        // 1 request per 10 seconds if address is same
        final Duration addressDuration = Duration.of(10, ChronoUnit.SECONDS);
        final RequestLimitRule addressRule = RequestLimitRule.of(addressDuration, 1).withPrecision(precisionDuration);
        final RequestRateLimiter addressRuleRateLimiter = new InMemorySlidingWindowRequestRateLimiter(addressRule);

        while(true) {
            final UserObject user = UserObject.builder().age(11).name("ABC").address("NewYork").build();
            processStudent(user, addressRuleRateLimiter);
        }
    }

    private static void processStudent(final UserObject user, final RequestRateLimiter addressRuleRateLimiter) {
        if (!addressRuleRateLimiter.overLimitWhenIncremented(user.getAddress())) {
            user.callStudent();
        }
    }
}
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@EqualsAndHashCode
@Builder
public class UserObject {
    String name;
    int age;
    String address;
    boolean isProcessed;

    static DateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");

    public void callStudent() {
        Date currentDate = new Date(System.currentTimeMillis()); 
        System.out.println(dateFormat.format(currentDate)+"  "+this.toString());
    }
}

Output

10 Mar 2020 14:26:17  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:27  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:27  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:37  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:37  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:47  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:47  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:57  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:26:57  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:27:07  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)
10 Mar 2020 14:27:07  UserObject(name=ABC, age=11, address=NewYork, isProcessed=false)

As you can see first call for key NewYork was allowed only once for 10 seconds. But after that it allowed two calls.

Also how actively this library is maintained?

Sliding windows?

Hi,
It seems that windows doesn't slides in inMemory mode. Maybe is it a bug?
In InMemorySlidingWindowRequestRateLimiter.java line 136:
replace == by <=

Regards

RedisRateLimiterFactory should be able to work with just a connection (rather than a RedisClient)

The RedisClient docs say:

RedisClient is an expensive resource. It holds a set of netty's EventLoopGroup's that use multiple threads. Reuse this instance as much as possible or share a ClientResources instance amongst multiple client instances.

RedisRateLimiterFactory makes it difficult to share a single RedisClient, because its close() method closes the client. (That is, it effectively "takes ownership" of the client.)

Taking a RedisClient also makes it incompatible with connection pooling, as typically one does not have visibility to the RedisClient itself outside of the pool's construction.

Given that RedisRateLimiterFactory only uses a single connection, could an additional constructor that takes a StatefulRedisConnection<String, String> be added?

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.