com.dexmatech.styx:rate-limit

Styx is an async and non-blocking API Gateway.

License

License

Categories

Categories

Dex General Purpose Libraries Utility
GroupId

GroupId

com.dexmatech.styx
ArtifactId

ArtifactId

rate-limit
Last Version

Last Version

1.0.22
Release Date

Release Date

Type

Type

jar
Description

Description

Styx is an async and non-blocking API Gateway.

Download rate-limit

How to add to project

<!-- https://jarcasting.com/artifacts/com.dexmatech.styx/rate-limit/ -->
<dependency>
    <groupId>com.dexmatech.styx</groupId>
    <artifactId>rate-limit</artifactId>
    <version>1.0.22</version>
</dependency>
// https://jarcasting.com/artifacts/com.dexmatech.styx/rate-limit/
implementation 'com.dexmatech.styx:rate-limit:1.0.22'
// https://jarcasting.com/artifacts/com.dexmatech.styx/rate-limit/
implementation ("com.dexmatech.styx:rate-limit:1.0.22")
'com.dexmatech.styx:rate-limit:jar:1.0.22'
<dependency org="com.dexmatech.styx" name="rate-limit" rev="1.0.22">
  <artifact name="rate-limit" type="jar" />
</dependency>
@Grapes(
@Grab(group='com.dexmatech.styx', module='rate-limit', version='1.0.22')
)
libraryDependencies += "com.dexmatech.styx" % "rate-limit" % "1.0.22"
[com.dexmatech.styx/rate-limit "1.0.22"]

Dependencies

compile (6)

Group / Artifact Type Version
com.dexmatech.styx : styx-core jar 1.0.22
org.apache.logging.log4j : log4j-api jar 2.6.2
org.apache.logging.log4j : log4j-core jar 2.6.2
org.slf4j : slf4j-api jar 1.7.21
org.apache.logging.log4j : log4j-slf4j-impl jar 2.6.2
com.lmax : disruptor jar 3.3.5

provided (1)

Group / Artifact Type Version
org.projectlombok : lombok jar 1.16.6

test (10)

Group / Artifact Type Version
com.dexmatech.styx : environment jar 1.0.22
junit : junit jar 4.12
org.hamcrest : hamcrest-all jar 1.3
org.mockito : mockito-all jar 1.10.17
org.powermock : powermock-module-junit4 jar 1.6.5
org.powermock : powermock-api-mockito jar 1.6.5
org.skyscreamer : jsonassert jar 1.2.3
com.fasterxml.jackson.core : jackson-core jar 2.5.1
com.fasterxml.jackson.core : jackson-annotations jar 2.5.1
com.fasterxml.jackson.core : jackson-databind jar 2.5.1

Project Modules

There are no modules declared in this project.

Styx Api gateway

Build Status

Styx is an async and non-blocking API Gateway developed by dexmatech that handle all incoming request and redirect to internal servers.

Table of contents

What is an API Gateway

Api gateway encapsulates the internal system architecture and provides a single entry point to access to our internal API's, normally this term is strongly associated to microservice-based architectures.

In a simple concept, the main responsibilities of an API gateways are filter and redirect between external client requests and internal APIs.

alt tag

Usually it might have other responsibilities:

  • Authentication and security: Authenticate each client request and reject or permit pass to the systems, also manage ssl
  • Rate limiting: Applies rate limit policies to our request clients
  • Service discovery(client or server) : Responsible to know the internal address of each microservice with which it communicates
  • Request routing: Routing requests to the different backend services
  • Response composition: An API Gateway will often handle a request by invoking multiple microservices and aggregating the results.
  • Resiliency: Failures in an specific resources or clients should not break the rest (circuit-breaker)
  • Log metrics: Track statistics in order to give you a view of what is happening
  • Transport transformations: Sometimes your internal apis are managed in different transport protocols
  • Caching
  • Logging

Please do not confuse API gateway with API management solutions, the second one could be a full solution that integrates design, management, documenting and other features around APIs, also it could integrate API gateways.

Quick start

Installation

Maven:

<dependency>
  <groupId>com.dexmatech.styx</groupId>
  <artifactId>styx-core</artifactId>
  <version>VERSION</version>
</dependency>

<dependency>
  <groupId>com.dexmatech.styx</groupId>
  <artifactId>grizzly-adapter</artifactId>
  <version>VERSION</version>
</dependency>

Run api gateway

Main concepts

Styx Api gateway tries to make easy integrate your internal APIs encouraging:

  • Async: All actions are made using java completable futures
  • NIO (non blocking IO): All http calls are implemented using an http nio client
  • Functional programing: We are not functional jihadists, but we use functions, immutability ...

Then, although for a basic use you don't need to do, if you extend the framework, please, don't try to use imperative programming, try to use nio drivers (if it is possible) and compose futures.

Http proxy

alt tag

From a global vision an api gateway is an http proxy as 'nginx', a facade that acts as a front-end to control and protect our private network.

Request-reply pipeline

alt tag

The main idea of the project is simple, given a bunch of incoming requests from the outside world, they will be handled by a pipeline.

A pipeline has one entry for an http request and one outcome with the resulting http response

One single pipeline will manage all incoming request in three different phases:

  • First, apply request stages
  • Second, apply routing stage
  • Third, apply response stages

If there is any error or stage fails, pipeline will abort the rest of the pipeline and immediately will reject the request.

There is no shared context or pipeline context, if you are thinking in some way about servlet filters, it is all about requests and responses.

Request stages

alt tag

In a simple concept, and thinking in a functional way, a 'request stage' is a simple function:

request -> request

Pipeline will apply all stages asynchronously, and also a request stage could success or fail, then reality is that a 'request stage' is a function that:

Request -> [Future[StageResult[Request]]]

For example, an authentication stage could be:

  • Given a request with an authentication token client as header
  • The function check if it is a valid token
    • If is not valid then abort stage, request will be rejected
    • If is valid
      • Retrieve privileges associated to this token
      • Add privilege headers to the current request
  • Final return the result:
    • Success with a request
    • Fail( aborting immediately the pipeline flow) with a response

Routing stage

After request has passed all request stages then is the turn of routing stage.

The the simple firm is:

Request -> [Future[StageResult[Response]]]

This is the proxy stage, it will take the request and it will create a new one, redirecting to internal servers in order to make the real request for the client.

Styx provide a default and basic implementation of routing stage, is totally async and nio, and uses a header to route:

X-routing-url

But if you need to compose different internal calls or something more complex you will need to implement a new one.

Response stages

In that point your request have been handled, and you have your response ready to be sent to the client.

If you need to add cookies, headers or anything you want to do in the response, this is the way:

Response -> [Future[StageResult[Response]]]

Http server

In order to handle real http request and responses, we will need a real http server in front over our pipelines. Styx uses Grizzly as default http server.

alt tag

It is so simple, running pipelines in an http servers is a mapping between:

http server request implementation -> styx request

And:

styx response -> http server response implementation

Then, if you want to run styx over another server, add this mappers and make us a pull request.

Usage

Styx uses internal dsl's and don't use magics as other frameworks, it means that if you are a java developer you need to code.

Create single pipeline

All incoming request will be handled by this:

// create http request-reply pipeline
HttpRequestReplyPipeline pipeline = HttpRequestReplyPipeline
				.pipeline()
				.applyingStaticHostOnRouteGeneration("internal.service.dmz")
				.applyingDefaultRoutingStage()
				.build();

// create single api pipeline
ApiPipeline apiPipeline = ApiPipeline.singlePipeline().using(pipeline).build();

Create multi pipeline

Sometimes you will need more than one pipeline to handle you request, for example you could have:

  • Requests from host 'X' will have authentication 1 by token
  • Request with path '/some_path' will have rate limit but no authentication
  • The rest of your requests will be authenticated and rate-limited
	// We create our pipelines
		RoutablePipeline pipelineWithAuthentication =
				matchingRequestsByHost("X").using(
						HttpRequestReplyPipeline
								.pipeline()
								.applyingPreRoutingStage("authentication", myAuthentication())
								.applyingStaticHostOnRouteGeneration("internal.service.dmz")
								.applyingDefaultRoutingStage()
								.build()
				).build();

		RoutablePipeline pipelineWithRateLimit =
				matchingRequestsByPathRegexPattern("/some_path.*").using(
						HttpRequestReplyPipeline
								.pipeline()
								.applyingPreRoutingStage("rate-limit", myRateLimit())
								.applyingStaticHostOnRouteGeneration("internal.service.dmz")
								.applyingDefaultRoutingStage()
								.build()
				).build();

		RoutablePipeline defaultPipeline =
				defaultPipeline().using(
						HttpRequestReplyPipeline
								.pipeline()
								.applyingPreRoutingStage("authentication", myAuthentication())
								.applyingPreRoutingStage("rate-limit", myRateLimit())
								.applyingStaticHostOnRouteGeneration("internal.service.dmz")
								.applyingDefaultRoutingStage()
								.build()
				).build();

		// create multi pipeline
		ApiPipeline apiPipeline = ApiPipeline
				.multiPipeline()
				.addPipeline(pipelineWithAuthentication)
				.addPipeline(pipelineWithRateLimit)
				.addPipeline(defaultPipeline).build();

Default routing stage

Default routing stage is an NIO stage that acts as a proxy an makes the real requests to internal servers.

As default it only expects that the incoming request has a custom header to make the real route like:

    X-routing-url: http://internal.server.dmz:8080/api  

Then, you can do:

  1. Use a default static implementation, it will redirect all request to same host:
 HttpRequestReplyPipeline
   				.pipeline()
   				.applyingStaticHostOnRouteGeneration("internal.service.dmz") 
   		...

Then if a request enter with a requested uri as

http://www.mydomain.com:8080/api

This stage will add a custom header as

http://internal.service.dmz:8080/api
  1. Create your own creation of routing with your own logic, remember to add X-routing-url custom header

  2. A module was added with Server side discovery

Also, you can override some parameters of default routing implementation:

 RoutingStage routingstage = DefaultRoutingStage
 				.usingDefaults()
 				// if you want to change header used to route
 				.usingHeaderToRoute("X-my-custom-route")
 				// if you want to change how route is extracted, maybe you don't want to use a header
 				.usingStrategyToRoute(ROUTE_EXTRACTOR)
 				// if you want to make transformations on response after proxy success
 				// for example copy some headers from request to response
 				.applyAfterRoutingSuccess((httpRequest, httpResponse) -> httpResponse.addHeader("X",httpRequest.getHeaders().get("X")))
 				// if you want to provide an custom config
 				.usingDefaultClientAndConfig(new DefaultAsyncHttpClientConfig.Builder().build())
 				.build();
 				
// create http request-reply pipeline
HttpRequestReplyPipeline pipeline = HttpRequestReplyPipeline
				.pipeline()
				.applyingStaticHostOnRouteGeneration("internal.service.dmz")
				.applyingRoutingStage(routingstage)
				.build();

// create single api pipeline
ApiPipeline apiPipeline = ApiPipeline.singlePipeline().using(pipeline).build(); 				
 				

Run pipeline over http server

In a simplest way :

 ApiGateway.runningOverGrizzly().withPipeline(apiPipeline).build().startAndKeepRunning();

Or if you want to config some behaviour :

 ApiGateway
        .runningOverGrizzly()
        .withDefaultServerRunningOnPort(8081)
        .withExecutorService(Executors.newFixedThreadPool(4))
        .withPipeline(apiPipeline)
        .build()
        .startAndKeepRunning();

Implementing stage

How we said before, a request stage is a function:

    Request -> [Future[StageResult[Request]]]

Then, let's code a simple request stage that add some custom headers

RequestPipelineStage stage = r -> CompletableFuture
                .completedFuture(
                        StageResult.stageSuccessWith(r.addHeader("X-some-header", "some-value"))
                        );

Implementing complex stage

Previous sample is so easy, but it is not realistic, normally you will want to access to a remote repository, add dependencies or do more complicated things, then let' s do an more complex sample.

We will code an authentication stage, to reach that, we will codify a simple DSL that constructs our final function.

import com.dexmatech.styx.core.http.HttpResponse;
import com.dexmatech.styx.core.pipeline.stages.AbortedStage;
import com.dexmatech.styx.core.pipeline.stages.StageResult;

import java.util.Objects;
import java.util.Optional;

public class AuthenticationStage {

	public static final String DEFAULT_TOKEN_HEADER = "X-token";

	public interface AuthenticationService {
		boolean authenticate(String token);
	}

	public static Builder usingDefaults() {
		return new Builder();
	}

	public static class Builder {

		private AuthenticationService authenticationService;
		private Optional<String> tokenHeader = Optional.empty();

		public Builder authenticatingWith(AuthenticationService authenticationService) {
			this.authenticationService = authenticationService;
			return this;
		}

		public Builder usingHeaderToken(String tokenHeader) {
			this.tokenHeader = Optional.ofNullable(tokenHeader);
			return this;
		}

		public RoutingStage build() {
			Objects.requireNonNull(authenticationService, "Please provide an authentication service");
			String headerName = tokenHeader.orElse(DEFAULT_TOKEN_HEADER);
			return httpRequest -> {

				Optional<String> token = Optional.ofNullable(httpRequest.getHeaders().get(headerName));
				return token.map(t -> {

					if (authenticationService.authenticate(t)) {
						return StageResult.completeStageSuccessfullyWith(httpRequest);
					} else {
						return StageResult.completeStageFailingWith(
								HttpResponse.forbidden(), AbortedStage.because("Invalid token")
						);
					}
				}).orElseGet(() ->
						StageResult.completeStageFailingWith(
								HttpResponse.internalServerError(), AbortedStage.because("Token not found")
						)
				);

			};

		}

	}

}

With a resulting usage:

AuthenticationService authenticationService = new MyAuthenticationImpl(config, ...); 

RequestPipelineStage stage = AuthenticationStage
				.usingDefaults()
				.authenticatingWith(authenticationService)
				.usingHeaderToken("X-my-security-header")
				.build();

Note: this is only a sample in order to view how to code a simple DSL to encapsulate an stage creation. Note 2: this example is totally blocking, please use async code

Handling with errors

TODO

Resiliency

Doing : Circuit breaker module

Monitoring

TODO

Changing http server

TODO

Modules

Here are listed and linked all modules or extensions, they could be request stages, routing or anything usefull

Request stages:

Authors

Contributing

Of course, we encourage the community to contribute with us and add specific modules and integrations with 3rd party systems!

Take as an example any of our modules of the previous section.

Bug reporting and Pull Requests are welcome.

See https://github.com/dexma/styx-api-gateway/contributors

com.dexmatech.styx

DEXMA

Versions

Version
1.0.22
1.0.21
1.0.20
1.0.19
1.0.17
1.0.12
1.0.11
1.0.10
1.0.9