Giter VIP home page Giter VIP logo

conjure-java-runtime's Introduction

Autorelease

CircleCI Build Status

Conjure Java Runtime (formerly http-remoting)

This repository provides an opinionated set of libraries for defining and creating RESTish/RPC servers and clients based on Feign as a client and Dropwizard/Jersey with JAX-RS service definitions as a server. Refer to the API Contract section for details on the contract between clients and servers. This library requires Java 8.

Core libraries:

  • conjure-java-jaxrs-client: Clients for JAX-RS-defined service interfaces
  • conjure-java-jersey-server: Configuration library for Dropwizard/Jersey servers
  • conjure-java-runtime-api: API classes for service configuration, tracing, and error propagation

Usage

Maven artifacts are published to Maven Central. Example Gradle dependency configuration:

repositories {
  mavenCentral()
}

dependencies {
  compile "com.palantir.conjure.java.runtime:conjure-java-jaxrs-client:$version"
  compile "com.palantir.conjure.java.runtime:conjure-java-jersey-server:$version"
}

conjure-java-jaxrs-client

Provides the JaxRsClient factory for creating Feign-based clients for JAX-RS APIs. SSL configuration is mandatory for all clients, plain-text HTTP is not supported. Example:

SslConfiguration sslConfig = SslConfiguration.of(Paths.get("path/to/trustStore"));
UserAgent userAgent = UserAgent.of(UserAgent.Agent.of("my-user-agent", "1.0.0"));
ClientConfiguration config = ClientConfigurations.of(
        ImmutableList.of(SERVER_URI),
        SslSocketFactories.createSslSocketFactory(sslConfig),
        SslSocketFactories.createX509TrustManager(sslConfig));
HostMetricsRegistry hostMetricsRegistry = new HostMetricsRegistry();  // can call .getMetrics() and then collect them to a central metrics repository
MyService service = JaxRsClient.create(MyService.class, userAgent, hostMetricsRegistry, config);

The JaxRsClient#create factory comes in two flavours: one for creating immutable clients given a fixed ClientConfiguration, and one for creating mutable clients whose configuration (e.g., server URLs, timeouts, SSL configuration, etc.) changes when the underlying ClientConfiguration changes.

conjure-java-jersey-server

Provides Dropwizard/Jersey configuration for handling conjure types, and also exception mappers for translating common runtime exceptions as well as our own ServiceException (see the errors section) to appropriate HTTP error codes. A Dropwizard server is configured for conjure as follows:

public class MyServer extends Application<Configuration> {
    @Override
    public final void run(Configuration config, final Environment env) throws Exception {
        env.jersey().register(ConjureJerseyFeature.INSTANCE);
        env.jersey().register(new MyResource());
    }
}

tracing

Provides Zipkin-style call tracing libraries. All JaxRsClient instances are instrumented by default. Jersey server instrumentation is enabled via the ConjureJerseyFeature (see above).

Please refer to tracing-java for more details on the tracing library usage.

service-config (conjure-java-runtime-api)

Provides utilities for setting up service clients from file-based configuration. Example:

# config.yml
services:
  security:
    # default truststore for all clients
    trustStorePath: path/to/trustStore.jks
  myService:  # the key used in `factory.get("myService")` below
    uris:
      - URI
    # optionally set a myService-specific truststore
    # security:
    #   trustStorePath: path/to/trustStore.jks
ServiceConfigBlock config = readFromYaml("config.yml");
ServiceConfigurationFactory factory = ServiceConfigurationFactory.of(config);
HostMetricsRegistry hostMetricsRegistry = new HostMetricsRegistry();
MyService client = JaxRsClient.create(MyService.class, UserAgents.parse("my-agent"), hostMetricsRegistry, ClientConfigurations.of(factory.get("myService")));

keystores and ssl-config (conjure-java-runtime-api)

Provides utilities for interacting with Java trust stores and key stores and acquiring SSLSocketFactory instances using those stores, as well as a configuration class for use in server configuration files.

The SslConfiguration class specifies the configuration that should be used for a particular SSLContext. The configuration is required to include information for creating a trust store and can optionally be provided with information for creating a key store (for client authentication).

The configuration consists of the following properties:

  • trustStorePath: path to a file that contains the trust store information. The format of the file is specified by the trustStoreType property.
  • trustStoreType: the type of the trust store. See section below for details. The default value is JKS.
  • (optional) keyStorePath: path to a file that contains the key store information. If unspecified, no key store will be associated with this configuration.
  • (optional) keyStorePassword: password for the key store. Will be used to read the keystore provided by keyStorePath (if relevant for the format), and will also be used as the password for the in-memory key store created by this configuration. Required if keyStorePath is specified.
  • (optional) keyStoreType: the type of the key store. See section below for details. The default value is JKS.
  • (optional) keyStoreAlias: specifies the alias of the key that should be read from the key store (relevant for file formats that contain multiple keys). If unspecified, the first key returned by the store is used.

An SslConfiguration object can be constructed using the static of() factory methods of the class, or by using the SslConfiguration.Builder builder. SslConfiguration objects can be serialized and deserialized as JSON.

Once an SslConfiguration object is obtained, it can be passed as an argument to the SslSocketFactories.createSslSocketFactory method to create an SSLSocketFactory object that can be used to configure Java SSL connections.

Store Types

The following values are supported as store types:

  • JKS: a trust store or key store in JKS format. When used as a trust store, the TrustedCertificateEntry entries are used as certificates. When used as a key store, the PrivateKeyEntry specified by the keyStoreAlias parameter (or the first such entry returned if the parameter is not specifeid) is used as the private key.
  • PEM: for trust stores, an X.509 certificate file in PEM format, or a directory of such files. For key stores, a PEM file that contains a PKCS#1 RSA private key or PKCS#8 followed by the certificates that form the trust chain for the key in PEM format, or a directory of such files. In either case, if a directory is specified, every non-hidden file in the directory must be a file of the specified format (they will all be read).
  • PKCS12: a trust store or key store in PKCS12 format. Behavior is the same as for the JKS type, but operates on stores in PKCS12 format.
  • Puppet: a directory whose content conforms to the Puppet SSLdir format. For trust stores, the certificates in the certs directory are added to the trust store. For key stores, the PEM files in the private_keys directory are added as the private keys and the corresponding files in certs are used as the trust chain for the key.

errors (conjure-java-runtime-api)

Provides utilities for relaying service errors across service boundaries (see below).

API Contract

conjure-java-runtime makes the following opinionated customizations to the standard Dropwizard/Feign behavior.

Object serialization/deserialization

All parameters and return values of application/json endpoints are serialized/deserialized to/from JSON using a Jackson ObjectMapper with GuavaModule, ShimJdk7Module (same as Jackson’s Jdk7Module, but avoids Jackson 2.6 requirement) and Jdk8Module. Servers must not expose parameters or return values that cannot be handled by this object mapper.

Error propagation

Servers should use the ServiceException class to propagate application-specific errors to its callers. The ServiceException class exposes standard error codes that clients can handle in a well-defined manner; further, ServiceException implements SafeLoggable and thus allows logging infrastructure to handle "unsafe" and "safe" exception parameters appropriately. Typically, services define its error types as follows:

class Errors {
  private static final ErrorType DATASET_NOT_FOUND =
    ErrorType.create(ErrorType.Code.INVALID_ARGUMENT, "MyApplication:DatasetNotFound");

  static ServiceException datasetNotFound(DatasetId datasetId, String userName) {
    // Both the safe and unsafe params will be sent back to the client; the client needs
    // to decide themselves if any of these are safe to log. We're only marking things as
    // safe or unsafe for this server to log.
    return new ServiceException(
            DATASET_NOT_FOUND, SafeArg.of("datasetId", datasetId), UnsafeArg.of("userName", userName));
  }
}

void someMethod(String datasetId, String userName) {
  if (!exists(datasetId)) {
    throw Errors.datasetNotFound(datasetId, userName);
  }
}

The ConjureJerseyFeature installs an exception mapper for ServiceException. The exception mapper sets the response media type to application/json and returns as response body a JSON representation of a SerializableError capturing the error code, error name, and error parameters. The resulting JSON response is:

{
  "errorCode": "INVALID_ARGUMENT",
  "errorName": "MyApplication:DatasetNotFound",
  "errorInstanceId": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx",
  "parameters": {
    "datasetId": "123abc",
    "userName": "yourUserName"
  }
}

JaxRsClient intercepts non-successful HTTP responses and throw a RemoteException wrapping the deserialized server-side SerializableError. The error codes and names of the ServiceException and SerializableError are defined by the service API, and clients should handle errors based on the error code and name:

try {
    service.someMethod();
catch (RemoteException e) {
    if (e.getError().errorName().equals("MyApplication:DatasetNotFound")) {
        handleError(e.getError().parameters().get("datasetId"));
    } else {
        throw new RuntimeException("Failed to call someMethod()", e);
    }
}

Frontends receiving such errors should use a combination of error code, error name, and parameters to display localized, user friendly error information. For example, the above error could be surfaced as "The requested dataset with id 123abc could not be found".

To support legacy server implementations, the ConjureJerseyFeature also installs exception mappers for IllegalArgumentException, NoContentException, RuntimeException and WebApplicationException. The exceptions typically yield SerializableErrors with exceptionClass=errorCode=<exception classname> and message=errorName=<exception message>. Clients should refrain from displaying the message or errorName fields to user directly. Services should prefer to throw ServiceExceptions instead of the above, since they are easier to consume for clients and support transmitting exception parameters in a safe way.

RemoteException vs ServiceException vs SerializableError vs ErrorType
  • ErrorType is a record type, meant to be used as 'compile time constants' - essentially used by services to define the 'enum' of their service exceptions
  • SerializableError defines the wire format for serializing ServiceExceptions in HTTP response bodies and contains the error code, error instance id, and application-defined parameters
  • ServiceException is a final subclass of Exception, thrown by the server
  • RemoteException is what the client sees if a remote call results in the server internally throwing a ServiceException

The workflow is:

  • Server code throws an instance of ServiceException, containing some ErrorType
  • The com.palantir.conjure.java.server.jersey.ServiceExceptionMapper exception mapper
    • determines the response code for this service exception
    • converts this into a SerializableError
    • serializes this into the response body as JSON
  • The client sees a RemoteException, which contains the SerializableError which was sent over the wire
  • The client can inspect the SerializableError and choose to act
  • If the client is itself a server and does not handle the RemoteException, a SerializableError error will be sent as the response with and errorCode of INTERNAL, errorName of Default:Internal, the same errorInstanceId as the original RemoteException and no parameters.

Serialization of Optional and Nullable objects

@Nullable or Optional<?> fields in complex types are serialized using the standard Jackson mechanism:

  • a present value is serialized as itself (in particular, without being wrapped in a JSON object representing the Optional object)
  • an absent value is serialized as a JSON null. For example, assume a Java type of the form
public final class ComplexType {
    private final Optional<ComplexType> nested;
    private final Optional<String> string;
}

, and an instance

ComplexType value = new ComplexType(
        Optional.of(
                new ComplexType(
                        Optional.<ComplexType>absent(),
                        Optional.<String>absent(),
        Optional.of("baz"));

The JSON-serialized representation of this object is:

{"nested":{"nested":null,"string":null},"string":"baz"}

Optional return values

When a call to a service interface declaring an Optional<T> return value with media type application/json yields:

  • a Optional#empty return value, then the HTTP response has error code 204 and an empty response body.
  • a non-empty return value, then the HTTP response has error code 200 and the body carries the deserialized T object directly, rather than a deserialized Optional<T> object.

JaxRsClients intercept such responses, deserialize the T-typed return value and return it to the caller wrapped as an Optional<T>.

Call tracing

Clients and servers propagate call trace ids across JVM boundaries according to the Zipkin specification. In particular, clients insert X-B3-TraceId: <Trace ID> HTTP headers into all requests which get propagated by Jetty servers into subsequent client invocations.

Endpoints returning plain strings

Endpoints returning plain strings should produce media type text/plain. Return type Optional<String> is only supported for media type application/json.

Quality of service: retry, failover, throttling, backpressure

Flow control in Conjure is a collaborative effort between servers and clients.

Servers advertise an overloaded state using 429/503 responses, which clients interpret by throttling the number of in-flight requests they will send (currently according to an additive increase, multiplicative decrease based algorithm). Requests are retried a fixed number of times, scheduled with an exponential backoff algorithm.

Concurrency permits are only released when the response body is closed, so large streaming responses are correctly tracked.

conjure-java-runtime servers can use the QosException class to advertise the following conditions:

  • throttle: Returns a Throttle exception indicating that the calling client should throttle its requests. The client may retry against an arbitrary node of this service.
  • retryOther: Returns a RetryOther exception indicating that the calling client should retry against the given node of this service.
  • unavailable: An exception indicating that (this node of) this service is currently unavailable and the client may try again at a later time, possibly against a different node of this service.

The QosExceptions have a stable mapping to HTTP status codes and response headers:

  • throttle: 429 Too Many Requests, plus optional Retry-After header
  • retryOther: 308 Permanent Redirect, plus Location header indicating the target host
  • unavailable: 503 Unavailable

conjure-java-runtime clients handle the above error codes and take the appropriate action:

  • throttle: reschedule the request with a delay: either the indicated Retry-After period, or a configured exponential backoff
  • retryOther: retry the request against the indicated service node; all request parameters and headers are maintained
  • unavailable: retry the request on a different host after a configurable exponential delay

Additionally, connection errors (e.g., connection refused or DNS errors) yield a retry against a different node of the service. Retries pick a target host by cycling through the list of URLs configured for a Service (see ClientConfiguration#uris). Note that the "current" URL is maintained across calls; for example, if a first call yields a retryOther/308 redirect, then any subsequent calls will be made against that URL. Similarly, if the first URL yields a DNS error and the retried call succeeds against the URL from the list, then subsequent calls are made against that URL.

The number of retries for 503 and connection errors can be configured via ClientConfiguration#maxNumRetries or ServiceConfiguration#maxNumRetries, defaulting to 4.

Metrics

The HostMetricsRegistry uses HostMetrics to track per-host response metrics. HostMetrics provides the following metrics:

  • get1xx(): A timer of 1xx responses.
  • get2xx(): A timer of 2xx responses.
  • get3xx(): A timer of 3xx responses.
  • get4xx(): A timer of 4xx responses, excluding 429s.
  • get5xx(): A timer of 5xx responses, excluding 503s.
  • getQos(): A timer of 429 and 503 responses.
  • getOther(): A timer of all other responses.
  • getIoExceptions(): A timer of all failed requests.

License

This repository is made available under the Apache 2.0 License.

conjure-java-runtime's People

Contributors

a-k-g avatar ash211 avatar bavardage avatar bulldozer-bot[bot] avatar carterkozak avatar dansanduleac avatar ellisjoe avatar gatesn avatar gmaretic avatar gracew avatar iamdanfox avatar j-baker avatar jkozlowski avatar jonsyu1 avatar leeavital avatar markelliot avatar mglazer avatar nmiyake avatar parker89 avatar pkoenig10 avatar pnepywoda avatar qinfchen avatar raiju avatar robert3005 avatar schlosna avatar sfackler avatar sixinli avatar svc-autorelease avatar svc-excavator-bot avatar uschi2000 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

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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

conjure-java-runtime's Issues

Error handler throws ClassNotFoundExceptions if not in a Jersey environment

Was trying to use Gatekeeper in a non-Jersey environment (a test script), and instead of having errors decoded properly, received a bunch of ClassNotFoundExceptions (as in, if you are not in a jersey server, you cannot see any error messages).

This is probably because the exception decoded is not supposed to be used outside of a Jersey environment - see #24 for details.

I guess a reasonable solution would be to catch the ClassNotFoundException and use a RuntimeException to redo the whole shebang in that case?

Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ClassNotFoundException: org.glassfish.jersey.internal.RuntimeDelegateImpl
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:593)
at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)
at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160)
at java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateParallel(ForEachOps.java:189)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
at java.util.stream.IntPipeline.forEach(IntPipeline.java:404)
at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:560)
at com.palantir.jbaker.PerfTest.main(perftest.java:35)
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:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: org.glassfish.jersey.internal.RuntimeDelegateImpl
at javax.ws.rs.ext.RuntimeDelegate.findDelegate(RuntimeDelegate.java:152)
at javax.ws.rs.ext.RuntimeDelegate.getInstance(RuntimeDelegate.java:120)
at javax.ws.rs.core.Response$ResponseBuilder.newInstance(Response.java:848)
at javax.ws.rs.core.Response.status(Response.java:613)
at javax.ws.rs.WebApplicationException.<init>(WebApplicationException.java:226)
at com.palantir.remoting.http.errors.SerializableErrorErrorDecoder.constructException(SerializableErrorErrorDecoder.java:117)
at com.palantir.remoting.http.errors.SerializableErrorErrorDecoder.decode(SerializableErrorErrorDecoder.java:74)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:126)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:74)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:97)
at com.sun.proxy.$Proxy15.registerResource(Unknown Source)
at com.palantir.jbaker.PerfTest.lambda$main$2(perftest.java:42)
at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205)
at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.ClassNotFoundException: org.glassfish.jersey.internal.RuntimeDelegateImpl
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at javax.ws.rs.ext.FactoryFinder.newInstance(FactoryFinder.java:115)
at javax.ws.rs.ext.FactoryFinder.find(FactoryFinder.java:225)
at javax.ws.rs.ext.RuntimeDelegate.findDelegate(RuntimeDelegate.java:135)
... 21 more

Allow HostnameVerifier to be set for clients

Currently, the CLIENT_SUPPLIER objects in FeignClientFactory are defined as Function<Optional<SSLSocketFactory>, Client>. It would be helpful to have the ability to specify the HostnameVerifier in addition to the factory (this is the other constructor parameter for Client.Default() in Feign, and is supported by OkHttp as well).

Concretely:

  • Create new HttpClientParams class that contains Optional<SSLSocketFactory> sslSocketFactory and Optional<HostnameVerifier> hostnameVerifier
  • Modify suppliers as follows:
    private static final Function<HttpClientParams, Client> OKHTTP_CLIENT_SUPPLIER =
            new Function<HttpClientParams, Client>() {
                @Override
                public Client apply(HttpClientParams params) {
                    com.squareup.okhttp.OkHttpClient client = new com.squareup.okhttp.OkHttpClient();
                    client.setSslSocketFactory(params.sslSocketFactory.orNull());
                    client.setHostnameVerifier(params.hostnameVerifier.orNull());
                    return new OkHttpClient(client);
                }
            };

    private static final Function<HttpClientParams, Client> DEFAULT_CLIENT_SUPPLIER =
            new Function<HttpClientParams, Client>() {
                @Override
                public Client apply(HttpClientParams params) {
                    return new Client.Default(params.sslSocketFactory.orNull(), params.hostnameVerifier.orNull());
                }
            };
  • Propagate changes down-stream (preserve current APIs to default to Optional.absent() for HostnameVerifier, but add new APIs that accept HttpClientParams).

Motivation:

With Docker networking, internal container ports are mapped to "localhost" -- in order to connect to the container axiom.skylab.dev:3000 from the host machine, the host machine establishes a connection to localhost:3000. However, if the container is a server that provides a certificate, we'll encounter a hostname mismatch -- the certificate we received is signed for "axiom.skylab.dev", but the hostname we connected to is localhost, so the hostname verification fails.

Being able to set the verifier would allow working around this in test environments -- it would be possible to provide a verifier that would work around this (allow the proper matches or skip entirely).

I think this is reasonable given that Feign views HostnameVerifier at the same level as SSLSocketFactory for their clients. It is possible to work around this (client can bypass FeignClientFactory and just create the proxy themselves), but wanted to open for discussion.

Migrate project to use CircleCI

Project currently uses Travis CI. Works, but seems like we're standardizing on Circle, and Circle should provide a few more things more easily (for example, with current Travis builds it's not clear how to get more detailed failure information when tests fail).

Handle Error Codes for non-remoting clients

The exception mapper returns 500 in several situations where different error codes are more appropriate. Occurs when using a non-remoting client to interact with the server:

Parameterized types unsupported

JaxRsClient.create is not able to create parameterized client classes.

Exception in thread "main" java.lang.IllegalStateException: Parameterized types unsupported: SaltClient

[Needs Investigation] Returning an Optional<String> when Content-Type=application/json causes unexpected errors

Exception in thread "main" feign.FeignException: Unexpected character ('/' (code 47)): maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)
 at [Source: java.io.BufferedReader@2b662a77; line: 1, column: 2] reading GET http://localhost:8442/.....
    at feign.FeignException.errorReading(FeignException.java:48)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:127)
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:71)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:94)
    at com.sun.proxy.$Proxy19.getPath(Unknown Source)

This was a problem in 0.3.0 and is still a problem in 0.4.0.

Retrofit client factory should support propagating exceptions

Currently, any clients constructed through Retrofit will not include any information about the inner exception in the Java RetrofitException that comes back, despite the JSON representation having an extra field that specifically contains such information. This effectively means that certain inner exception information is swallowed.

For Feign clients, it appears we do extra work in FeignClientFactory to support this, likely through our custom SerializableErrorErrorDecoder.java. The request is to do something similar with our Retrofit clients, perhaps by building a Retrofit's custom ErrorHandler.

Examples of exceptions that get swallowed include any errors from Spark, such as exceptions relating to jagged rows in our TextDataFrameReader.

Allow request interceptors to be set

In our project, we would like to send the version as part of the request headers while talking among various internal services. Presently this blocks us from using the factories provided by http-remoting library. We're having to do something custom like -

public static <T> T create(Class<T> serviceClass,
                               ProductVersion version,
                               SSLSocketFactory socketFactory,
                               URL serviceBaseUrl) {
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.setSocketFactory(socketFactory);
        Client client = new feign.okhttp.OkHttpClient(okHttpClient);
        return Feign.builder()
                    .contract(new JAXRSContract())
                    .encoder(new JacksonEncoder(mapper))
                    .decoder(new JacksonDecoder(mapper))
                    .requestInterceptor(request -> request.header(VersionHeader.VERSION_HEADER, version.toJson()))
                    .client(client)
                    .target(serviceClass, serviceBaseUrl.toString());
    }

Run `certSetup.sh` as part of the build/test task

The script generates all of the encryption material already, so it makes sense to remove the current encryption material from version control and just run the script as part of the build. Will serve as enforcement for the script continuing to work, and will make it such that change sets aren't polluted with change in security materials when the script is changed.

.pem file based trust store

Support .pem files on disk for use with a trust store. Ideally support dynamic loading of new certificates without restart.

When keystore support is added, support defining a private key for use with a .pem file.

Enable injection of other Jackson modules in FeignJaxRsClientBuilder

Jdk8Module and JodaModule are prime candidates for usage in certain application APIs, but the current builder never exposes the created ObjectMapper for further configuration.

As a particular example, some APIs currently take Map<String, Object> configuration maps, ostensibly for custom bean serialization. However, if those beans contain Joda types or Java 8 Optionals, serialization will fail.

SerializableErrorErrorDecoder.java fails on exceptions without a single-string constructor

BLUF: Failure to parse error body for any HTTP statuses other than a few of the most common

In https://github.com/palantir/http-remoting/blob/develop/http-clients/src/main/java/com/palantir/remoting/http/errors/SerializableErrorErrorDecoder.java, "exceptionClass.getConstructor(String.class)" requires that the exception has a constructor that takes a single String. This is reasonable for the HTTP statuses that have a a specific exception made for them (e.g. BadRequestException, ForbiddenException), but for all the other statuses, you have to use either a ClientErrorException or ServerErrorException, which do not have single-string constructors.

Hacky workarounds are possible: I wrote code in Contour the catches HTTP-remoting exception and searches the message for the right HTTP status code. :/

ObjectMappers methods return shared mutable ObjectMapper

ObjectMappers.vanilla() and ObjectMappers.guavaJdk7() both directly return a static variable that is a mutable object, which means that if a caller changes the configuration of the returned object, the change is shared across all instances and for all future callers.

These methods should be changed to hand back a copy of the mappers instead so that callers are free to mutate the returned values without impacting other instances or future callers.

Retrofit2Client should append slashes if needed to URI's?

Looks like Retrofit2 requires URI's to end in slash (see square/retrofit#1049).

Since the Palantir convention in configuration files has been to avoid ending URI's with a slash, perhaps the Retrofit2Client should do this automatically. Otherwise, users of the library have to manually do so, as is done in Foundry's ServiceFactory class when creating a DataProxy client.

If jaxrs server can't parse request, it sends an error that SerializableErrorToExceptionConverter cannot convert to SerializableError

ERROR [2016-08-26 17:59:19,051] com.palantir.remoting1.errors.SerializableErrorToExceptionConverter: Error 400. Reason: Bad Request. Failed to parse error body and instantiate ex
 at [Source: {"code":400,"message":"Unable to process JSON"}; line: 1, column: 12] (through reference chain: com.palantir.remoting1.errors.Json["code"]). Body:
{"code":400,"message":"Unable to process JSON"}
! com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "code" (class com.palantir.remoting1.errors.ImmutableSerializableError$Json), not marked as
!  at [Source: {"code":400,"message":"Unable to process JSON"}; line: 1, column: 12] (through reference chain: com.palantir.remoting1.errors.Json["code"])
! at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:839) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1045) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1352) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1330) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:264) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:161) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:136) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1095) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:296) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:133) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3736) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2726) ~[jackson-databind-2.6.3.jar:2.6.3]
! at com.palantir.remoting1.errors.SerializableErrorToExceptionConverter.getException(SerializableErrorToExceptionConverter.java:60) ~[error-handling-1.0.0-alpha4.jar:na]
! at com.palantir.remoting1.jaxrs.feignimpl.FeignSerializableErrorErrorDecoder.decode(FeignSerializableErrorErrorDecoder.java:45) [jaxrs-clients-1.0.0-alpha4.jar:na]
! at httpremoting.shaded.feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:134) [jaxrs-clients-1.0.0-alpha4.jar:na]
! at httpremoting.shaded.feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) [jaxrs-clients-1.0.0-alpha4.jar:na]
! at httpremoting.shaded.feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) [jaxrs-clients-1.0.0-alpha4.jar:na]
! at com.sun.proxy.$Proxy51.submitModule(Unknown Source) [na:na]
... CALLING CODE ...

TrustStoreConfiguration is not serializable

Tried to serialize a TrustStoreConfiguration object and got the following exception:

com.fasterxml.jackson.databind.JsonMappingException: Direct self-reference leading to cycle (through reference chain: com.palantir.trust.Json["trustStorePath"]->sun.nio.fs.UnixPath["parent"]->sun.nio.fs.UnixPath["root"])

Is it meant to be like this?

naming convention for generated immutable class

Should the generated class be prefixed with Immutable_ vs Immutable? Take TrustStoreConfiguration for example, the generated class is never exposed (using the builder) and prefixing Immutable_ would help avoiding name collision with the classes with Immutable` prefix.

I am curious on the naming convention, so we can add it to baseline and apply to other projects

Make it easier to compose encoders/decoders

Currently, the encoders/decoders provided by http-remoting need to be manually nested, leading to ugly code like the following:

...
new QueryMapEncoder(new JacksonEncoder(ObjectMappers.guavaJdk7())),
new OptionalAwareDecoder(new TextDelegateDecoder(new JacksonDecoder(ObjectMappers.guavaJdk7()))),
...

Possible approaches would be to have a method that takes in a collection of delegating encoders/decoders and have it chain them directly or to use some kind of "handler" pattern.

@FormParam doesn't work

I attempted to use an earlier version of http-remoting (0.14) with the @FormParam annotation on my JaxRs client. The server never received the parameter, so could we verify/add support for @FormParam in http-remoting 1.0?

Thanks!

Retrofit2Client and capturing raw requests/responses

I often use vanilla Retrofit's capabilities to intercept and log Requests and Responses for debugging purposes. However, the Retrofit2ClientBuilder.build() method does not allow me to add an OkHttp Interceptor to my client before constructing it.

It would be very handy if either (a) this constructor could allow me to add an Interceptor to my client (see http://stackoverflow.com/a/33256827/866021) or (b) if it could somehow log raw requests and responses at the DEBUG level for testing.

Think about behavior when exceptions are passed through

Say I have the following chain of calls, all made using http-remoting:

Client A ----> Server B ----> Server C

If Server C produces a 404 (encoded as a NotFoundException) and server B doesn't handle and wrap it explicitly, then this results in a NotFoundException getting propagated to Client A. However, from Client A's perspective, the error really should be some sort of 5xx, because the not-found condition is an implementation detail.

We could handle this a couple of ways in the http-remoting API:

  1. Always throw a WrappedException type instead of one of the exception types that has a special meaning to Jersey.
  2. Allow people to specify exception type mappings when they make their FeignClient.
  3. Document this oddity and hope that people explicitly handle 4XX errors to convert them into 5XX errors.

My preference is (1) since it's explicit, but it would certainly be an API break.

Tagging @robert3005, @markelliot, @ashwinr and @derekcicerone for thoughts on this.

Support multiple URI's for the Retrofit client factory

For certain scenarios, a Retrofit client might be required, rather than the recommended Feign client, such as when communicating with streaming endpoints. It'd be great to have a version of RetrofitClientFactory.createProxy that supports multiple URI's, similar to the FeignClientFactory.createProxy method.

Overwriting the stack trace makes debugging hard

The error handler currently replaces the system stack trace with the stack trace from the server. This can be quite frustrating. For example, I've received IllegalArgumentExceptions after doing a refactor and known that it could have come from one of a few places in my code (and have no immediate way of finding out which), but know for sure that it came from a part of the server that throws IllegalArgumentExceptions.

I haven't thought of a great solution yet (perhaps errors with status <500 could not have their stack trace propagated?), but I wanted to flag this (happy to implement if this is seen as reasonable).

Below is an example of this being the case, accidentally leaving "Bearer" off an auth header. I regard this error trace as being useless.

java.lang.IllegalArgumentException: Unexpected HTTP Authorization header format "eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiIzNjQzODJhYy1jNmY4LTQ2Y2QtYmRiZi05ZWM0Mjc1NzgzMTUiLCJzdWJqZWN0TmFtZSI6ImFkbWluIiwidG9rZW5OYW1lIjoidG9rZW41IiwianRpIjoiYzZmODhhMmItMmQxYi00NzQwLWJiYzItZDYyMTcyYjkyNDhkIn0.qVheamwOwd_Ucl-F7zp2F30uAyAFMVy6sDCPMslMywk3DZHOdlBXuYn5hqehWpg1vnUH7rGWzoxxrpe2AMbPyHYWfP96IGOOZBdAsxN5psz6f-GkLANaShOWqtdn2YDz80PQQBgiP6zmASw6WvenwAaeECSrgtZYrNXuApr51Dhp5S5T3neq02R8b-tUhDH9_NObu8V2UTXM5pORaMckW0LW1fgkM3Z843dUvo4KWDiKbJ6vEj2z8KUKNs5PWowoDNi94XJi2Bz3YNQRrVITkGxfG24bqTxggoFlmZSeHkaAjkKDuv2-HMnX_FD9b0mEvNASqmLK8UAAaKXHSkP4_0rw0rG9mdRUxpSZnAwhYJvE4NDUFmwtoLq9nSMYmLXq0uZ39w0fTLR28qZdx6kjN699GE9nZZptUpJaMkram64KktbZ7ylWVm6dMy_HJWR7gIiroreDH3NtVFDiPKmb5d0crgApmvtzKbOJeGH08xDlSOf7Zl3A9S8HP0oPPUKIv4TdDxjf5f7js7iWBdjBF8lKCQ2W1HoPPeogxH1VVUOmyumCTt6nxxmUNYpIUK-ysExGn1ym_xaGQSp0atcrHznGeeGRFh9l-dGCwowzb9WiXOBDxwCer-LIiFt9zkHZUDo9CIHyA5PHPXx7rW-4gFuOucN52nogmoVhV7nTvV4"
    at com.palantir.AppName.auth.BearerAuthService.authenticateHeader(BearerAuthService.java:59)
    at com.palantir.AppName.server.AppName.registerResource(AppName.java:120)
    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:497)
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:144)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161)
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:205)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102)
    at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:308)
    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:291)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1140)
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:403)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:386)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:334)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    at io.dropwizard.jetty.NonblockingServletHolder.handle(NonblockingServletHolder.java:49)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)
    at org.eclipse.jetty.servlets.UserAgentFilter.doFilter(UserAgentFilter.java:83)
    at org.eclipse.jetty.servlets.GzipFilter.doFilter(GzipFilter.java:300)
    at io.dropwizard.jetty.BiDiGzipFilter.doFilter(BiDiGzipFilter.java:134)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at io.dropwizard.servlets.ThreadNameFilter.doFilter(ThreadNameFilter.java:29)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at io.dropwizard.jersey.filter.AllowedMethodsFilter.handle(AllowedMethodsFilter.java:44)
    at io.dropwizard.jersey.filter.AllowedMethodsFilter.doFilter(AllowedMethodsFilter.java:39)
    at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)

Javadoc error in WebPreconditions

When I try to build the latest version locally using ./gradlew build, it fails with the following exception:

/Volumes/git/palantir/http-remoting/http-servers/src/main/java/com/palantir/remoting/http/server/WebPreconditions.java:44: warning - Tag @link: can't find checkNotEmpty(String) in com.palantir.remoting.http.server.WebPreconditions
1 error
22 warnings
:http-servers:javadoc FAILED

FAILURE: Build failed with an exception.

Interestingly, in Circle it seems that this same issue is a warning rather than an error. However, the reference does seem to be incorrect. Should figure out why it's not failing in Circle as a separate thing, but underlying issue should be fixed in the mean time.

SerializableErrorToExceptionConverter doesn't handle javax.ws.rs.NotAllowedException

    java.lang.RuntimeException: Failed to construct exception as class javax.ws.rs.NotAllowedException, constructing RuntimeException instead: java.lang.NoSuchMethodException: javax.ws.rs.NotAllowedException.<init>(java.lang.String)
    HTTP 405 Method Not Allowed
        at com.palantir.remoting.http.errors.SerializableErrorToExceptionConverter.getException(SerializableErrorToExceptionConverter.java:82) ~[error-handling-0.11.0.jar:na]
        at com.palantir.remoting.http.errors.FeignSerializableErrorErrorDecoder.decode(FeignSerializableErrorErrorDecoder.java:43) ~[http-clients-0.11.0.jar:na]
        at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:126) ~[feign-core-8.16.0.jar:8.16.0]
        at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:74) ~[feign-core-8.16.0.jar:8.16.0]
        at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-8.16.0.jar:8.16.0]
        at com.sun.proxy.$Proxy57.createDataset(Unknown Source) ~[na:na]

I think the problem is that https://github.com/palantir/http-remoting/blob/0072e03b3e95b4b666b1b2b841ba4c08092e806c/error-handling/src/main/java/com/palantir/remoting/http/errors/SerializableErrorToExceptionConverter.java#L137 is looking for a constructor with one (String) parameter, or a constructor with (String, Throwable) parameters.

But NotAllowedException has a final variadic parameter at the end:

public NotAllowedException(String allowed, String... moreAllowed) {

public NotAllowedException(String message, Throwable cause, String... allowedMethods) {

Concept of server sets with different connection strategies

As MP was investigating a failover LDAP client, we found the library we were using had a pretty cool way of doing failover.

Basically, the client takes a ServerSet and the ServerSet is responsible for deciding which URL to pick. The basic FailoverServerSet would be identical to the current FailoverFeignTarget but adding a level of indirection allows you to compose different ServerSets. For example, we can define a RoundRobinServerSet which always RR each request regardless of failover and the FailoverServerSet can take a list of RoundRobinServerSets. This enables support for complex network topologies such as east/west coast data centers.

Basic proposal, add object ServerSet and allow application to specify serviceUrls in terms of ServerSets, example:

serviceUrls:
  type: failover
  serverSets: 
    - type: roundrobin
      serverSets:
      - east1                      # defaults to SingleServerSet type
      - east2
    - type: roundrobin
      serverSets:
      - west1
      - west2

Thoughts?

2-way SSL support

Modify trust-stores to have a configurable key store for use with 2-way SSL and add http-clients tests to verify that this is sufficient for 2-way SSL to function.

Provide defaults for using JAVA_HOME cacerts for proxy / client creation

There are cases where we may just want to use the trusted certs from the default cacerts file from JAVA_HOME when no Optional<SSLSocketFactory is provided.

Not exactly sure where the best spot to put the default would be, or if we should be making it more explicit?

A few options I see are as follows:

Thoughts?

GuavaOptionalAwareContract turns all absent header params into {header name}

See https://github.com/palantir/http-remoting/blob/develop/http-clients/src/test/java/com/palantir/remoting/http/GuavaOptionalAwareContractTest.java#L117

This is never the behaviour you want. Imagine someone not providing an optional Accept header and actually ending up requiring something of type {Accept}.

Alternatively, imagine someone making a request without an If-Match header, and actually demanding an If-Match header of {If-Match}.

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.