Code Monkey home page Code Monkey logo

curacao's Introduction

Curacao

An open source toolkit for building REST/HTTP-based integration layers on top of asynchronous servlets.

Introduction

Writing comprehensive documentation is hard, but a reasonable introduction to Curacao can be found here.

As the project matures, I intend to write out more documentation.

Latest Version

See the Releases page to find the latest version.

Compiling

Curacao requires Java 11 or later, and is packaged using Maven.

To compile & package the source:

mvn clean package

Resulting JARs and source JARs will be placed into the local dist directory.

Running the Examples

Working examples that demonstrate Curacao's flexibility can be found in the curacao-examples project.

In the spirit of "eating my own dog food", my own blog is built on Curacao and is fully open source on GitHub. If you're looking for more complex component definitions, and realistic request mapping and response handling examples, the application source of my blog will be a great start.

To compile and run the examples locally:

mvn -am -pl curacao-examples package -Pjetty-run

Then hit http://localhost:8080/curacao in your favorite browser.

License

Copyright (c) 2023 Mark S. Kolich

All code in this project is freely available for use and redistribution under the MIT License.

See LICENSE for details.

curacao's People

Contributors

markkolich avatar rtoal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

curacao's Issues

Mapper AbstractETagAwareFileReturnMapper doesn't check if a java.io.File exists

Mapper AbstractETagAwareFileReturnMapper doesn't actually check if the passed java.io.File it is supposed to render actually exists.

In other words, it never invokes entity.exists() before computing a weak ETag in getETag(final File f). We need to invoke exists() on the passed File before attempting to do anything with it.

Otherwise, we should immediately throw a PathNotFoundException. Or perhaps, create a new exception type and call ResourceNotFoundException.

EncodedRequestBodyMapper.parse() fails when buffer is empty

Consider the empty POST request:

POST /api/session.json HTTP/1.0

E.g., there is no POST body.

This triggers a somewhat expected failure in Curacao within EncodedRequestBodyMapper.parse() which is tasked with parsing a URL-encoded form body into a Multimap<String,String>.

java.lang.IllegalArgumentException: Lower bound cannot be greater than upper bound.
    at com.google.common.base.Preconditions.checkArgument(Preconditions.java:125) ~[guava-17.0.jar:na]
    at com.kolich.curacao.handlers.requests.mappers.types.body.EncodedRequestBodyMapper$Cursor.<init>(EncodedRequestBodyMapper.java:175) ~[curacao-2.6.2.jar:2.6.2]
    at com.kolich.curacao.handlers.requests.mappers.types.body.EncodedRequestBodyMapper.parse(EncodedRequestBodyMapper.java:61) ~[curacao-2.6.2.jar:2.6.2]
    at com.kolich.curacao.handlers.requests.mappers.types.body.EncodedRequestBodyMapper.resolveWithStringAndEncoding(EncodedRequestBodyMapper.java:51) ~[curacao-2.6.2.jar:2.6.2]
    at com.kolich.curacao.handlers.requests.mappers.types.body.RequestBodyAsCharsetAwareStringMapper.resolveWithBody(RequestBodyAsCharsetAwareStringMapper.java:43) ~[curacao-2.6.2.jar:2.6.2]
    at com.kolich.curacao.handlers.requests.mappers.types.body.MemoryBufferingRequestBodyMapper.resolve(MemoryBufferingRequestBodyMapper.java:112) ~[curacao-2.6.2.jar:2.6.2]
    at com.kolich.curacao.handlers.requests.CuracaoControllerInvoker.buildPopulatedParameterList(CuracaoControllerInvoker.java:241) ~[curacao-2.6.2.jar:2.6.2]
    at com.kolich.curacao.handlers.requests.CuracaoControllerInvoker.call(CuracaoControllerInvoker.java:169) ~[curacao-2.6.2.jar:2.6.2]
    at java.util.concurrent.FutureTask.run(FutureTask.java:262) ~[na:1.7.0_51]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_51]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_51]
    at java.lang.Thread.run(Thread.java:744) [na:1.7.0_51]

It should be easy enough to avoid this exception, and just check the length of the buffer before trying to parse it.

CuracaoReflectionUtils incorrectly matches .class files for compiled inner classes and lambdas

The "fix" for this issue #8 left us with broken class matching that would incorrectly match .class files for anonymous inner classes and Java 8 lambdas.

This method is just wrong:

private static final String getCanonicalClassFromInputFqn(final String input) {
  // Split on "$" for FQN's that look like:
  //   com.a.b.c.D$Foobar.class
  // Even if there's no "$" to split on, index zero of the resulting
  // split should just be the original input string, and never null.
  String s = input.split("\\$")[0];
  // If the the splitted string ends with ".class" remove the suffix.
  //   com.a.b.c.Foobar.class
  // So we'll be left with just com.a.b.c.Foobar as desired.
  if (s.endsWith(CLASS_EXTENSION)) {
    s = s.substring(0, s.length() - 6);
  }
  return s;
}

Not sure how I didn't catch this before, but this was doomed to fail. The reason being is that the Java compiler splits out anonymous inner classes and Java 8 lambdas into their own .class files. For instance, this class:

public class Foo {
  // ...
  public static class Bar {
    // ...
  }
}

Produces these .class files once compiled:

  • Foo.class
  • Foo$Bar.class

The bug here is that if Curacao's dependency injection mechanism asks for class Foo the existing method getCanonicalClassFromInputFqn incorrectly matches Foo$Bar and tells org.reflections to pick the wrong class file. That leads to fun exceptions like this:

com.kolich.curacao.exceptions.reflection.ComponentInstantiationException: Failed to instantiate component instance: Foo
    at com.kolich.curacao.components.ComponentTable.buildComponentTable(ComponentTable.java:119)
    at com.kolich.curacao.components.ComponentTable.<init>(ComponentTable.java:87)
    at com.kolich.curacao.CuracaoContextListener.contextInitialized(CuracaoContextListener.java:111)
Caused by:
java.lang.NoSuchMethodException: Foo$Bar.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getConstructor(Class.java:1825)
    at com.kolich.curacao.components.ComponentTable.instantiate(ComponentTable.java:135)
    at com.kolich.curacao.components.ComponentTable.instantiate(ComponentTable.java:182)

Dependency org.reflections:0.9.10 Pulls in outdated JSR305 findbugs JAR

In Curacao 3.0, we upgraded to org.reflections:0.9.10 which pulled in another transitive dependency which conflicts with our own:

[info] Checking every *.class/*.jar file's SHA-1.
[info] Merging files...
[warn] Merging 'META-INF/MANIFEST.MF' with strategy 'discard'
[trace] Stack trace suppressed: run last *:assembly for the full output.
[error] (*:assembly) deduplicate: different file contents found in the following:
[error] /..../.ivy2/cache/com.google.code.findbugs/annotations/jars/annotations-2.0.1.jar:javax/annotation/Syntax.class
[error] /.../.ivy2/cache/com.google.code.findbugs/jsr305/jars/jsr305-2.0.3.jar:javax/annotation/Syntax.class
[error] Total time: 9 s, completed Jul 25, 2015 6:46:24 PM

Need to add exclude("com.google.code.findbugs", "annotations") to Curacao's Build.scala.

ComponentMappingTable erroneously reports "circular dependency"

In Curacao 2.2, consider these:

@Component
public final class A {
  public A() { /* ... */ }
}
@Component
public final class B {
  @Injectable
  public B(final A a) { /* ... */ }
}
@Component
public final class C {
  @Injectable
  public C(final A a, final B b) { /* ... */ }
}

Although not a circular dependency, Curacao's DI mechanism was erroneously throwing a "circular dependency detected" exception when attempting to instantiate C.

ResponseTypeMappingHandlerTable default mappers clobber user defined mappers with the same type

In release 2.5.1, and earlier, Curacao's own ResponseTypeMappingHandlerTable default mapper set will clobber any user defined mappers tied to the same types.

https://github.com/markkolich/curacao/blob/2.5.1/curacao/src/main/java/com/kolich/curacao/handlers/responses/ResponseTypeMappingHandlerTable.java#L211

That is, ResponseTypeMappingHandlerTable defines a default mapper to handle a vanilla Object. However, if a user defines their own mapper to handle Object, the user defined mapper will never be used given that the call to mappers.putAll here ResponseTypeMappingHandlerTable.java#L211 blindly clobbers any existing entries for the same Class<?> type.

Yeah, that's a Map<K,V> not a List<T>putAll is clearly wrong. Oops.

`AppendableCuracaoEntity` wrongly closes its Writer which closes the underlying OutputStream

After running a Curacao backed app in Tomcat, it became clear that Curacao's own AppendableCuracaoEntity was wrongly closing its OutputStreamWriter which then closes the underlying Servlet containers' response OutputStream.

This is bad, which leads to NPE's like:

java.lang.NullPointerException: null
    at org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:129) ~[tomcat-coyote.jar:8.0.15]
    at org.apache.coyote.http11.InternalNioOutputBuffer.flushBuffer(InternalNioOutputBuffer.java:244) ~[tomcat-coyote.jar:8.0.15]
    at org.apache.coyote.http11.AbstractOutputBuffer.endRequest(AbstractOutputBuffer.java:378) ~[tomcat-coyote.jar:8.0.15]
    at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:724) ~[tomcat-coyote.jar:8.0.15]
    at org.apache.coyote.Response.action(Response.java:179) ~[tomcat-coyote.jar:8.0.15]
    at org.apache.catalina.connector.OutputBuffer.close(OutputBuffer.java:306) ~[catalina.jar:8.0.15]
    at org.apache.catalina.connector.CoyoteOutputStream.close(CoyoteOutputStream.java:151) ~[catalina.jar:8.0.15]
    at sun.nio.cs.StreamEncoder.implClose(StreamEncoder.java:320) ~[na:1.7.0_71]
    at sun.nio.cs.StreamEncoder.close(StreamEncoder.java:149) ~[na:1.7.0_71]
    at java.io.OutputStreamWriter.close(OutputStreamWriter.java:233) ~[na:1.7.0_71]
    at com.kolich.curacao.entities.AppendableCuracaoEntity.write(AppendableCuracaoEntity.java:59) ~[curacao-2.8.2.jar:2.8.2]
    at com.kolich.curacao.handlers.responses.mappers.RenderingResponseTypeMapper.renderEntity(RenderingResponseTypeMapper.java:68) ~[curacao-2.8.2.jar:2.8.2]

The offending code is:

@Override
public final void write(final OutputStream os) throws Exception {
  try(final OutputStreamWriter w = new OutputStreamWriter(os, charsetName_)) {
    toWriter(w);
  }
}

The OutputStreamWriter closes the OutputStream when execution leaves the try auto-closeable block leading to bugs and other awesomeness.

Encoded request body mapper is mapping empty string form values to null

When a POST request body contains a parameter whose value maps to an empty-string ("") the resulting map contains a null value for the key.

That is wrong:

if (StringUtils.isNotBlank(entry.getKey()) && StringUtils.isNotBlank(entry.getValue())) {

Empty strings are entirely legal form values and should not be translated to null in the mapper.

CC // @KaviSachania

If context initialization fails before CuracaoContextListener sets request/response pools, app throws NPE

In Curacao 2.2, if context initialization fails due to a circular dependency failure, Curacao hits an NPE in CuracaoContextListener due to the fact that the component mapping table, request thread pool, and response thread pool are unset and still null.

Caused by: com.kolich.curacao.exceptions.CuracaoException: CIRCULAR DEPENDENCY DETECTED! While trying to instantiate @Component: com.****.aws.****AwsCredentials it depends on the other components ([class com.***.aws.****AwsClientFactory, class com.****.aws.config.****AwsConfig])
    at com.kolich.curacao.handlers.components.ComponentMappingTable.instantiate(ComponentMappingTable.java:164)
    at com.kolich.curacao.handlers.components.ComponentMappingTable.instantiate(ComponentMappingTable.java:205)
    at com.kolich.curacao.handlers.components.ComponentMappingTable.buildMappingTable(ComponentMappingTable.java:120)
    ... 15 more

Leads to...

SEVERE: Exception sending context destroyed event to listener instance of class com.kolich.curacao.CuracaoContextListener
java.lang.NullPointerException
    at com.kolich.curacao.CuracaoContextListener.contextDestroyed(CuracaoContextListener.java:158)
    at org.apache.catalina.core.StandardContext.listenerStop(StandardContext.java:4980)
    at org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5626)
    at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:160)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:633)
    at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:983)
    at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1660)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

Curacao Dependency Injection fails miserably with Java Interfaces

Consider this component interface with its corresponding implementation:

public interface Foobar {
  public void foo();
}
@Component
public final class FoobarImpl implements Foobar {
  @Override
  public void foo() {
    // ...
  }
}

Anytime another component were to reference the interface Foobar, and not the concrete class FoobarImpl, Curacao's dependency injection would fail miserably. For example:

@Component
public final class AnotherComponent {
  @Injectable
  public AnotherComponent(final Foobar foo) {
    // ...
  }
}

In this case, Curacao's DI mechanism wouldn't understand the interface Foobar and would inject null instead of FoobarImpl.

This needs to be fixed — Curacao should look for @Component implementations of a given interface in the boot package and inject accordingly. In this case, although @Component AnotherComponent references interface Foobar Curacao should inject FoobarImpl instead.

Perf: No point in checking java.lang.Object in RequestMappingRoutingTable.getAllRequestMappingsInHierarchy()

Performance profiling shows that countless milliseconds are wasted in Curacao's own RequestMappingRoutingTable.getAllRequestMappingsInHierarchy() method. The logic therein involves a while loop which crawls the class hierarchy of a given @Controller looking for methods annotated with the @RequestMapping annotation:

while (superClass != null) {
  // ...
}

This is fine, except that when we crawl up the class hierarchy and get to java.lang.Object we spend cycles looking through java.lang.Object for any methods annotated with @RequestMapping. Good times, because there won't be any in java.lang.Object — why spend cycles using reflection checking java.lang.Object for any @RequestMapping annotated methods when there couldn't possibly be any?

We can clean this up by adding a simple check to the while loop in question:

while (superClass != null && !Object.class.equals(superClass)) {
  // ...
}

Performance testing shows this shaves off at least 40ms per controller class (which may be many in large/complex web-applications).

Http Method Annotations should support Regexes for URI Matching

Currently each supported HTTP method in the curacao.annotations.methods package only supports a Ant style path matcher passed as a String. For example:

@GET("/{name}/**") or @POST("/api/data.json")

Using Ant path strings are fine, but it would make sense to also support regexes. That is, let users pass either a String for a vanilla Ant style path matcher, or allow them to pass a Regex to apply & match on the incoming URI.

For example, on @GET change its value field to default to "" (an empty String) and add a new regexp field that also defaults to "" (an empty String).

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {

    String value() default "";

    String regexp() default "";

    Class<? extends CuracaoRequestFilter> filter()
        default DefaultCuracaoRequestFilter.class;

}

Consumers could then use this like so:

@GET(regexp="^\/foo\/[0-9]+\/\.json")

Curacao component dependency injection of `ServletContext` fails

It seems with the introduction of Curacao 2.1, depdendency injection of the ServletContext into a component constructor is no longer possible.

@Component
public final class Foo {
  @Injectable
  public Foo(final ServletContext context) {
    // .... 'context' is always null here
  }
}

Something broke.

NPE: EncodedRequestBodyMapper.parse(EncodedRequestBodyMapper.java:69)

NPE with this request:

[mkolich@mbp]~$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
POST /curacao/api/login HTTP/1.1
Host: localhost
Content-Length: 1

f
HTTP/1.1 500 Server Error
Date: Fri, 05 Jun 2015 04:04:33 GMT
Content-Length: 0
Server: Jetty(9.2.10.v20150310)

^]
telnet> quit
Connection closed.

Reproducible every time:

2015-06-04 21:04:35,167 WARN  [curacao-examples-9] c.k.c.h.ReturnTypeMapperCallbackHandler - Failure occurred, handling exception.
java.lang.NullPointerException: null
    at java.net.URLDecoder.decode(URLDecoder.java:136) ~[na:1.8.0_40]
    at com.kolich.curacao.mappers.request.types.body.EncodedRequestBodyMapper.parse(EncodedRequestBodyMapper.java:69) ~[curacao-3.0.jar:3.0]
    at com.kolich.curacao.mappers.request.types.body.EncodedRequestBodyMapper.resolveWithStringAndEncoding(EncodedRequestBodyMapper.java:51) ~[curacao-3.0.jar:3.0]
    at com.kolich.curacao.mappers.request.types.body.RequestBodyAsCharsetAwareStringMapper.resolveWithBody(RequestBodyAsCharsetAwareStringMapper.java:42) ~[curacao-3.0.jar:3.0]
    at com.kolich.curacao.mappers.request.types.body.MemoryBufferingRequestBodyMapper.resolve(MemoryBufferingRequestBodyMapper.java:109) ~[curacao-3.0.jar:3.0]
    at com.kolich.curacao.CuracaoControllerInvoker.buildPopulatedParameterList(CuracaoControllerInvoker.java:182) ~[curacao-3.0.jar:3.0]
    at com.kolich.curacao.CuracaoControllerInvoker.call(CuracaoControllerInvoker.java:122) ~[curacao-3.0.jar:3.0]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_40]
    at com.kolich.curacao.CuracaoRunnable.run(CuracaoRunnable.java:68) [curacao-3.0.jar:3.0]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_40]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_40]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_40]

Add support for something like an `@Required` annotation

Today, with Curacao's dependency injection model, if you annotate a constructor with @Injectable Curacao will try to find the set of components to be passed into the constructor when the component is instantiated. E.g.,

public class Foo {
  @Injectable
  public Foo(Bar bar, Baz baz) {
    // .. whatever
  }
}

In the case above, if no component could be found for typed arguments Bar or Baz, Curacao just passes in null. That's OK, except this means that you now have checkNotNull checks all over the place:

@Injectable
public Foo(Bar bar, Baz baz) {
  if (bar == null || baz == null) {
    throw new IllegalArgumentException("Can't be null, sucka.");
  }
}

Here's proposing implementing support for a @Required annotation. When you annotate an injectables constructor arguments with @Required if Curacao was unable to find a suitable component at instantiation time, instead of passing null Curacao should just throw some type of exception for the application.

This way, one could avoid the == null checks in the constructor:

@Injectable
public Foo(@Required Bar bar, @Required Baz baz) {
  // ... cleaner.
}

Add support for "static content" serving into native Curacao

The serving of static content is a typical need of most applications. Curacao should be able to do this — that is, serve static files or resources from the file system or classpath.

I'm already doing this elsewhere, see:

https://github.com/markkolich/blog/blob/master/src/main/java/com/kolich/blog/mappers/resources/ETagAwareFileResponseMapper.java

Classpath resources can be served using com.google.common.io.Resources:
http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/io/Resources.html

In any event, this probably should be folded into Curacao natively.

ControllerRoutingTable fails to consider super (parent) classes in class hierarchy when looking for @RequestMapping annotations

Consider the following @Controller and abstract parent class.

public abstract class AbstractBaseController {
  @RequestMapping("^\\/foo$")
  public final String foo() {
    return "foo";
  }
}
@Controller
public final class RealController extends AbstractBaseController {
  @RequestMapping("^\\/bar$")
  public final String bar() {
    return "bar";
  }
}

In this case, Curacao's ControllerRoutingTable fails to identify the @RequestMapping on method foo in the AbstractBaseController super class and only finds the bar method. This is clearly incorrect.

https://github.com/markkolich/curacao/blob/2.5.2/curacao/src/main/java/com/kolich/curacao/handlers/requests/ControllerRoutingTable.java#L114

The ControllerRoutingTable needs to be updated to scan the discovered @Controller class and all super (parent) classes for methods annotated with @RequestMapping annotations.

CuracaoReflectionUtils mistakenly matches two components with similar names

In 2.3, CuracaoReflectionUtils mistakenly matches two components with similar names.

For instance, take classes "a.b.c.D" and "a.b.c.DConfig".

When Curacao's DI mechanism scans the classpath looking for "a.b.c.DConfig" it could mistakenly find "a.b.c.D" instead given that "a.b.c.DConfig".startsWith("a.b.c.D") == true.

This is due to the incredibly hacky String startsWith at CuracaoReflectionUtils.java#L75 that doesn't accurately compare the fully qualified name of the classes in question.

NPE: UrlPathHelper.removeSemicolonContent:93

This needs to be investigated/fixed:

java.lang.NullPointerException: null
    at com.kolich.curacao.util.helpers.UrlPathHelper.removeSemicolonContent(UrlPathHelper.java:93) ~[curacao-2.9-M1.jar:2.9-M1]
    at com.kolich.curacao.util.helpers.UrlPathHelper.decodeAndCleanUriString(UrlPathHelper.java:88) ~[curacao-2.9-M1.jar:2.9-M1]
    at com.kolich.curacao.util.helpers.UrlPathHelper.getRequestUri(UrlPathHelper.java:81) ~[curacao-2.9-M1.jar:2.9-M1]
    at com.kolich.curacao.util.helpers.UrlPathHelper.getPathWithinApplication(UrlPathHelper.java:58) ~[curacao-2.9-M1.jar:2.9-M1]
    at com.kolich.curacao.util.helpers.UrlPathHelper.getPathWithinApplication(UrlPathHelper.java:47) ~[curacao-2.9-M1.jar:2.9-M1]
    at com.kolich.curacao.CuracaoControllerInvoker.call(CuracaoControllerInvoker.java:69) ~[curacao-2.9-M1.jar:2.9-M1]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_31]
    at com.kolich.curacao.util.MDCAwareRunnableWrapper.run(MDCAwareRunnableWrapper.java:70) [curacao-2.9-M1.jar:2.9-M1]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_31]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_31]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_31]

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.