-
Notifications
You must be signed in to change notification settings - Fork 140
[Z Design notes] setShouldContinueHttpRequest(Predicate HttpRequest predicate) Filter chains
We added support for doing things that you would normally do in a ServletFilter
or its ilk.
We had the hook there already and the Predicate
already allowed you to chain Predicates
but
this did not address that we started to use Predicate
s to wire in health checks and stat check
endpoints. We added a mechanism to create chains of predicates. The first one that returns false, the chain stops processing.
See https://github.com/advantageous/qbit/pull/357/files for the pull request. https://github.com/advantageous/qbit/issues/355 for issue
The HTTP server allows you to pass a predicate.
setShouldContinueHttpRequest(Predicate<HttpRequest> predicate)
Predicate<HttpRequest> predicate.
The predicate allows for things like security interception. Look for an auth header. Reject request if auth header is not in place.
Predicates are nest-able.
It is often the case, that you will want to run more than one predicate.
To support this, we added addShouldContinueHttpRequestPredicate(final Predicate<HttpRequest> predicate)
to the HttpServerBuilder.
The HttpServerBuilder will keep a list of predicates, and register them with the HttpServer when it builds the http server.
You can add your own predicates or replace the default predicate mechanism.
private RequestContinuePredicate requestContinuePredicate = null;
public RequestContinuePredicate getRequestContinuePredicate() {
if (requestContinuePredicate == null) {
requestContinuePredicate = new RequestContinuePredicate();
}
return requestContinuePredicate;
}
public HttpServerBuilder setRequestContinuePredicate(final RequestContinuePredicate requestContinuePredicate) {
this.requestContinuePredicate = requestContinuePredicate;
return this;
}
public HttpServerBuilder addShouldContinueHttpRequestPredicate(final Predicate<HttpRequest> predicate) {
getRequestContinuePredicate().add(predicate);
return this;
}
public class RequestContinuePredicate implements Predicate<HttpRequest>{
private final CopyOnWriteArrayList <Predicate<HttpRequest>> predicates = new CopyOnWriteArrayList<>();
public RequestContinuePredicate add(Predicate<HttpRequest> predicate) {
predicates.add(predicate);
return this;
}
@Override
public boolean test(final HttpRequest httpRequest) {
boolean shouldContinue;
for (Predicate<HttpRequest> shouldContinuePredicate : predicates) {
shouldContinue = shouldContinuePredicate.test(httpRequest);
if (!shouldContinue) {
return false;
}
}
return true;
}
}
We added a bunch of unit tests to make sure this actually works. :)
We created an example to show how this works.
package com.mammatustech;
import io.advantageous.qbit.admin.ManagedServiceBuilder;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.http.request.HttpRequest;
import io.advantageous.qbit.http.server.HttpServerBuilder;
import java.util.function.Predicate;
/**
* Default port for admin is 7777.
* Default port for main endpoint is 8080.
*
* <pre>
* <code>
*
* Access the service:
*
* $ curl http://localhost:8080/root/hello/hello
*
* This above will respond "shove off".
*
* $ curl --header "X-SECURITY-TOKEN: shibboleth" http://localhost:8080/root/hello/hello
*
* This will get your hello message.
*
* To see swagger file for this service:
*
* $ curl http://localhost:7777/__admin/meta/
*
* To see health for this service:
*
* $ curl http://localhost:8080/__health
* Returns "ok" if all registered health systems are healthy.
*
* OR if same port endpoint health is disabled then:
*
* $ curl http://localhost:7777/__admin/ok
* Returns "true" if all registered health systems are healthy.
*
*
* A node is a service, service bundle, queue, or server endpoint that is being monitored.
*
* List all service nodes or endpoints
*
* $ curl http://localhost:7777/__admin/all-nodes/
*
*
* List healthy nodes by name:
*
* $ curl http://localhost:7777/__admin/healthy-nodes/
*
* List complete node information:
*
* $ curl http://localhost:7777/__admin/load-nodes/
*
*
* Show service stats and metrics
*
* $ curl http://localhost:8080/__stats/instance
* </code>
* </pre>
*/
@RequestMapping("/hello")
public class HelloWorldService {
@RequestMapping("/hello")
public String hello() {
return "hello " + System.currentTimeMillis();
}
public static void main(final String... args) {
final ManagedServiceBuilder managedServiceBuilder =
ManagedServiceBuilder.managedServiceBuilder().setRootURI("/root");
final HttpServerBuilder httpServerBuilder = managedServiceBuilder.getHttpServerBuilder();
/** We can register our security token checker here. */
httpServerBuilder.addShouldContinueHttpRequestPredicate(HelloWorldService::checkAuth);
/* Start the service. */
managedServiceBuilder.addEndpointService(new HelloWorldService())
.getEndpointServerBuilder()
.build().startServer();
/* Start the admin builder which exposes health end-points and meta data. */
managedServiceBuilder.getAdminBuilder().build().startServer();
System.out.println("Servers started");
}
/**
* Checks to see if the header <code>X-SECURITY-TOKEN</code> is set to "shibboleth".
* @param httpRequest http request
* @return true if we should continue, i.e., auth passed, false otherwise.
*/
private static boolean checkAuth(final HttpRequest httpRequest) {
/* Only check uri's that start with /root/hello. */
if (httpRequest.getUri().startsWith("/root/hello")) {
final String x_security_token = httpRequest.headers().getFirst("X-SECURITY-TOKEN");
/* If the security token is set to "shibboleth" then continue processing the request. */
if ("shibboleth".equals(x_security_token)) {
return true;
} else {
/* Security token was not what we expected so send a 401 auth failed. */
httpRequest.getReceiver().response(401, "application/json", "\"shove off\"");
return false;
}
}
return true;
}
}
To exercise this and show that it is working, let's use curl.
$ curl --header "X-SECURITY-TOKEN: shibboleth" http://localhost:8080/root/hello/hello
"hello 1440012093122"
$ curl http://localhost:8080/root/hello/hello
"shove off"
$ curl http://localhost:8080/__health
"ok"
You may wonder why/how health comes up in this conversation.
It is clear really. EndpointServerBuilder
and ManagedServiceBuilder
configure the health system
as a should you continue Predicate
as well.
public class EndpointServerBuilder {
public EndpointServerBuilder setupHealthAndStats(final HttpServerBuilder httpServerBuilder) {
if (isEnableStatEndpoint() || isEnableHealthEndpoint()) {
final boolean healthEnabled = isEnableHealthEndpoint();
final boolean statsEnabled = isEnableStatEndpoint();
final HealthServiceAsync healthServiceAsync = healthEnabled ? getHealthService() : null;
final StatCollection statCollection = statsEnabled ? getStatsCollection() : null;
httpServerBuilder.addShouldContinueHttpRequestPredicate(
new EndPointHealthPredicate(healthEnabled, statsEnabled,
healthServiceAsync, statCollection));
}
return this;
}
package io.advantageous.qbit.server;
import io.advantageous.boon.json.JsonFactory;
import io.advantageous.qbit.http.request.HttpRequest;
import io.advantageous.qbit.service.health.HealthServiceAsync;
import io.advantageous.qbit.service.stats.StatCollection;
import java.util.function.Predicate;
public class EndPointHealthPredicate implements Predicate<HttpRequest> {
private final boolean healthEnabled;
private final boolean statsEnabled;
private final HealthServiceAsync healthServiceAsync;
private final StatCollection statCollection;
public EndPointHealthPredicate(boolean healthEnabled, boolean statsEnabled,
HealthServiceAsync healthServiceAsync, StatCollection statCollection
) {
this.healthEnabled = healthEnabled;
this.statsEnabled = statsEnabled;
this.healthServiceAsync = healthServiceAsync;
this.statCollection = statCollection;
}
@Override
public boolean test(final HttpRequest httpRequest) {
boolean shouldContinue = true;
if (healthEnabled && httpRequest.getUri().startsWith("/__health")) {
healthServiceAsync.ok(ok -> {
if (ok) {
httpRequest.getReceiver().respondOK("\"ok\"");
} else {
httpRequest.getReceiver().error("\"fail\"");
}
});
shouldContinue = false;
} else if (statsEnabled && httpRequest.getUri().startsWith("/__stats")) {
if (httpRequest.getUri().equals("/__stats/instance")) {
if (statCollection != null) {
statCollection.collect(stats -> {
String json = JsonFactory.toJson(stats);
httpRequest.getReceiver().respondOK(json);
});
} else {
httpRequest.getReceiver().error("\"failed to load stats collector\"");
}
} else if (httpRequest.getUri().equals("/__stats/global")) {
/* We don't support global stats, yet. */
httpRequest.getReceiver().respondOK("{\"version\":1}");
} else {
httpRequest.getReceiver().notFound();
}
shouldContinue = false;
}
return shouldContinue;
}
}
QBit Website What is Microservices Architecture?
QBit Java Micorservices lib tutorials
The Java microservice lib. QBit is a reactive programming lib for building microservices - JSON, HTTP, WebSocket, and REST. QBit uses reactive programming to build elastic REST, and WebSockets based cloud friendly, web services. SOA evolved for mobile and cloud. ServiceDiscovery, Health, reactive StatService, events, Java idiomatic reactive programming for Microservices.
Reactive Programming, Java Microservices, Rick Hightower
Java Microservices Architecture
[Microservice Service Discovery with Consul] (http://www.mammatustech.com/Microservice-Service-Discovery-with-Consul)
Microservices Service Discovery Tutorial with Consul
[Reactive Microservices] (http://www.mammatustech.com/reactive-microservices)
[High Speed Microservices] (http://www.mammatustech.com/high-speed-microservices)
Reactive Microservices Tutorial, using the Reactor
QBit is mentioned in the Restlet blog
All code is written using JetBrains Idea - the best IDE ever!
Kafka training, Kafka consulting, Cassandra training, Cassandra consulting, Spark training, Spark consulting
Tutorials
- QBit tutorials
- Microservices Intro
- Microservice KPI Monitoring
- Microservice Batteries Included
- RESTful APIs
- QBit and Reakt Promises
- Resourceful REST
- Microservices Reactor
- Working with JSON maps and lists
__
Docs
Getting Started
- First REST Microservice
- REST Microservice Part 2
- ServiceQueue
- ServiceBundle
- ServiceEndpointServer
- REST with URI Params
- Simple Single Page App
Basics
- What is QBit?
- Detailed Overview of QBit
- High level overview
- Low-level HTTP and WebSocket
- Low level WebSocket
- HttpClient
- HTTP Request filter
- HTTP Proxy
- Queues and flushing
- Local Proxies
- ServiceQueue remote and local
- ManagedServiceBuilder, consul, StatsD, Swagger support
- Working with Service Pools
- Callback Builders
- Error Handling
- Health System
- Stats System
- Reactor callback coordination
- Early Service Examples
Concepts
REST
Callbacks and Reactor
Event Bus
Advanced
Integration
- Using QBit in Vert.x
- Reactor-Integrating with Cassandra
- Using QBit with Spring Boot
- SolrJ and service pools
- Swagger support
- MDC Support
- Reactive Streams
- Mesos, Docker, Heroku
- DNS SRV
QBit case studies
QBit 2 Roadmap
-- Related Projects
- QBit Reactive Microservices
- Reakt Reactive Java
- Reakt Guava Bridge
- QBit Extensions
- Reactive Microservices
Kafka training, Kafka consulting, Cassandra training, Cassandra consulting, Spark training, Spark consulting