Giter VIP home page Giter VIP logo

xrpc's Introduction

xrpc

Build Status Download

Xrpc is a framework for creating production quality API services on top of Netty. The framework helps to encapsulate our best practices and provides sane (and safe) defaults.

It currently supports the http/1.1 and the http/2 protocols. It does so interchangeably, i.e your implementation does not need to change and it will automatically respond to a http/1.1 and a http/2 client the same way. The user is free to determine whatever payload they would like, but our recommendation is JSON where you don't control both ends and protobuf (version 3) where you do.

Running the demos

Running the people demo app in a test server

$ ./bin/startPeopleTestServer.sh

Basic http set

$ curl -k -d '{"name": "bob"}' -X POST https://localhost:8080/people

Basic http/2 get

Note: This demo requires curl with http/2

See: https://simonecarletti.com/blog/2016/01/http2-curl-macosx/

$ curl -k  https://localhost:8080/people
[{"name":"bob"}]
$ curl -k  https://localhost:8080/people --http1.1
[{"name":"bob"}]

Running the dino demo app in a test server

Run the dino app server to demo proto buffer handling.

$ ./bin/startDinoTestServer.sh

Proto http set

Note: This demo requires curl with http/2

See: https://simonecarletti.com/blog/2016/01/http2-curl-macosx/

$ java -cp demos/dino/build/libs/xrpc-dino-demo-0.1.1-SNAPSHOT-all.jar \
    com.nordstrom.xrpc.demos.dino.DinoSetEncoder trex blue | \
    curl -k -X GET https://localhost:8080/DinoService/SetDino --data-binary @-

Proto http get

$ java -cp demos/dino/build/libs/xrpc-dino-demo-0.1.1-SNAPSHOT-all.jar \
    com.nordstrom.xrpc.demos.dino.DinoGetRequestEncoder trex | \
    curl -k -X GET https://localhost:8080/DinoService/GetDino --data-binary @-
trexblue

Admin routes

xrpc comes with some built in admin routes. See also AdminHandlers.java.

Admin routes are split into two groups: Informational routes, which may contain internally-sensitive info; and unsafe routes, which can update a running server, and should be exposed only to a subset of users. These can be enabled with the admin_routes.enable_info and admin_routes.enable_unsafe flags.

Informational routes are enabled by default, while unsafe routes are disabled by default.

Informational routes:

  • /metrics -> Returns the metrics reporters in JSON format
  • /health -> Expose a summary of downstream health checks
  • /ping -> Responds with a 200-OK status code and the text 'PONG'
  • /ready -> Exposes a Kubernetes or ELB specific healthcheck for liveliness

Unsafe routes:

  • /restart -> Restart service
  • /killkillkill -> Shutdown service
  • /gc: Request a garbage collection from the JVM

Contributing

Please see the contributing guide for details on contributing to this repository.

xrpc's People

Contributors

andyday avatar farhie avatar jkinkead avatar lducharme avatar mindyor avatar patrickdent avatar pdex avatar rivukis avatar shine17 avatar spenserpothier avatar trevorinman avatar wboyle avatar xjdr avatar

Stargazers

 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

xrpc's Issues

unable to get request body from h1Request getter property

we have the use case of extracting the request body for our post/put methods.
we are trying get that from the getter @Getter private final FullHttpRequest h1Request in XrpcRequest.java . We are thinking to use getH1Request().content().
But the getH1Request is returning null.

We need someway of passing the request body to the service handlers as a byte buffer or a strongly typed object (similar to spring controllers where we get body as an object @RequestBody myobject) or even as string.

Path Variables: final variable includes query string

for example, for the path:
/people/{person}

the following uri
/people/patrick?exampleParam=something

produces the following context:
{person: "patrick?exampleParam=something"}

we expect that it would be:
{person: "patrick"}

context groups not URL decoded

 Handler personHandler =
        context -> {
          Person p = new Person(context.variable("person"));
          people.add(p);

          return Recipes.newResponseOk("");
        };

from Example.java

if your url is something like /people/Samwise%20Gamgee, context.variable("person") will equal Samwise%20Gamgee. It may be more friendly if xrpc does the Url decoding.

Exception thrown when missing meter.

A Null pointer is thrown when return a status of 202 is returned from a handler.
I'm guessing that any response code not registered in Router.configResponseCodeMeters() will
cause this issue.

9969 [xrpc-worker-1] WARN  i.n.c.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
java.lang.NullPointerException: null
	at com.nordstrom.xrpc.server.UrlRouter.channelRead(UrlRouter.java:78)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at com.nordstrom.xrpc.logging.ExceptionLogger.channelRead(ExceptionLogger.java:29)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1380)
	at io.netty.handler.ssl.SslHandler.decodeNonJdkCompatible(SslHandler.java:1171)
	at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1196)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at com.nordstrom.xrpc.server.ConnectionLimiter.channelRead(ConnectionLimiter.java:55)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1359)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:935)
	at io.netty.channel.kqueue.AbstractKQueueStreamChannel$KQueueStreamUnsafe.readReady(AbstractKQueueStreamChannel.java:543)
	at io.netty.channel.kqueue.AbstractKQueueChannel$AbstractKQueueUnsafe.readReady(AbstractKQueueChannel.java:402)
	at io.netty.channel.kqueue.KQueueEventLoop.processReady(KQueueEventLoop.java:195)
	at io.netty.channel.kqueue.KQueueEventLoop.run(KQueueEventLoop.java:269)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886)
	at java.base/java.lang.Thread.run(Thread.java:844)

Premature Http2DataFrame

It's possible with netty to send an Http2DataFrame before h2 has finished it's handshake. We need to ensure that outbound Http2DataFrames are queued until the handshake is complete.

Should have a way of getting query parameters easily

we have the use case of extracting the query string parameters in the request
we are trying get that from the getter @Getter private final FullHttpRequest h1Request in XrpcRequest.java
But this is returning null.

We need someway of passing the query params as a hash map to the service handlers.

Fix NPE for health checks at startup

There is currently an NPE that is thrown at startup when the health checks are being scheduled against a worker thread pool that doesn't exist yet. This issue is being filed to fix that bug as well as simplify the invocation of the admin handlers.

Exception in thread "main" java.lang.NullPointerException
	at com.nordstrom.xrpc.server.Router.scheduleHealthChecks(Router.java:130)
	at com.nordstrom.xrpc.server.Router.scheduleHealthChecks(Router.java:126)
	at com.nordstrom.xrpc.demo.Example.main(Example.java:156)

Release process/branches

The issue here is that the current approach requires that, for a release to happen, the person running a release needs to have push permissions to master (needs to be a project admin). This isn't great. Having a shared release branch would fix this problem: Any person with normal write permissions could trigger a release.
See discussion in #82

HTTP/2 implementation needs verification.

The HTTP/2 implementation has some possible / probable bugs:

  1. Data frames data is not stored or saved unless endOfStream is set. It looks like this will drop all but the last frame received.
  2. All bytes are marked as processed even if they are ignored due to endOfStream being set. This looks incorrect.
  3. Data handling assumes a request is set, which will probably throw an exception for HTTP/2 requests that don't match a handler.
  4. Error handling doesn't return, which results in two responses being written (not found and server error).

Clean up typesafe config usage

Currently there is no demo of config overrides, and the config loading heavily relies on the classpath.

We should use a scoped class resource, and provide an override example.

Migrate demo to Jackson

Jackson is already an xrpc dependency; the demo should use this instead of Moshi.

It's also easier-to-use and faster.

Configure a release plugin to publish to Maven Central

Gradle has release plugins available to handle the (increment version + commit, tag repository, build release jar, commit snapshot version, push to github) workflow.

We should add one here (and publish an 0.1 version).

Add Exponential Backoff / Circuit Breaker specific to Error Codes

Current Exp Backoff is limited to timeouts. A new one should be added to take into consideration errors in responses. This should have different configuration options than the timeout based backoffs. We should potentially look into adding a (optional) Dead Letter Queue for failed requests.

Http/2 Client

Create an Http/2 client.

  • This could be implemented as a single client that auto-negotiates protocols.
  • This could be implemented as 2 separate clients, with the appropriate interfaces
  • This could be a simple wrapper around okhttp3

Need the ability to enable CORS support

We need an ability to specify the CORS headers or any additional response headers.
Currently we are doing that in Recipes.java. We need to expose that as parameter in the method so that we can pass custom response header to the client.

For CORS enabling , we just added response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");

public static FullHttpResponse newResponse(
HttpResponseStatus status, ByteBuf buffer, ContentType contentType) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buffer);

response.headers().set(CONTENT_TYPE, contentType.value);
response.headers().setInt(CONTENT_LENGTH, buffer.readableBytes());
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");

return response;

}

Add correct Admin handlers per production standards

the following need to be implemented

'/info' -> should expose version number, git commit number, etc
'/metrics' -> should return the metrics reporters in JSON format
'/health' -> should expose a summary of downstream health checks
'/ping' -> should respond with a 200-OK status code and the text 'PONG'
'/ready' -> should expose a Kubernetes or ELB specific healthcheck for liveliness
'/restart' -> restart service (should be restricted to approved devs / tooling)
'/killkillkill' -> shutdown service (should be restricted to approved devs / tooling)```

this issue supersedes #52  

Metrics namespacing inconsistencies

Expected behavior

Consistent naming between metrics

Actual behavior

Inconsistent naming between metrics

Steps to reproduce

/metrics endpoint

Minimal yet complete reproducer code (or URL to code)

#102 (comment)

xrpc version

0.2.1

Sample output of metrics endpoint

{
  "version" : "3.1.3",
  "gauges" : { },
  "counters" : {
    "com.nordstrom.xrpc.server.Router.Active Connections" : {
      "count" : 1
    }
  },
  "histograms" : { },
  "meters" : {
    "Hard Rate Limits" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "com.nordstrom.xrpc.server.Router.requests.Rate" : { 
      "count" : 2,
      "m15_rate" : 4.405910322420641E-4,
      "m1_rate" : 1.5708635764593015E-8,
      "m5_rate" : 2.0783399443213604E-4,
      "mean_rate" : 0.0023767358720068054,
      "units" : "events/second"
    },
    "requests" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.accepted" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.badRequest" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.created" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.forbidden" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.noContent" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.notFound" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.ok" : {
      "count" : 1,
      "m15_rate" : 4.405910322420641E-4,
      "m1_rate" : 1.5708635764593015E-8,
      "m5_rate" : 2.0783399443213604E-4,
      "mean_rate" : 0.0011882757510781091,
      "units" : "events/second"
    },
    "responseCodes.serverError" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.tooManyRequests" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    },
    "responseCodes.unauthorized" : {
      "count" : 0,
      "m15_rate" : 0.0,
      "m1_rate" : 0.0,
      "m5_rate" : 0.0,
      "mean_rate" : 0.0,
      "units" : "events/second"
    }
  },
  "timers" : {
    "Request Latency" : {
      "count" : 1,
      "max" : 164.399295,
      "mean" : 164.399295,
      "min" : 164.399295,
      "p50" : 164.399295,
      "p75" : 164.399295,
      "p95" : 164.399295,
      "p98" : 164.399295,
      "p99" : 164.399295,
      "p999" : 164.399295,
      "values" : [ 164.399295 ],
      "stddev" : 0.0,
      "m15_rate" : 4.405910322420641E-4,
      "m1_rate" : 1.5708635764593015E-8,
      "m5_rate" : 2.0783399443213604E-4,
      "mean_rate" : 0.0011883696639821677,
      "duration_units" : "milliseconds",
      "rate_units" : "calls/second"
    }
  }
}

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.