Quantcast
Channel: Sleepless Dev
Viewing all articles
Browse latest Browse all 217

Request filtering based on headers in QBit - Filtering requests with HttpRequest shouldContinue predicate

$
0
0
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 chainPredicates but this did not address that we started to use Predicates 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.
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.

HttpServerBuilder

privateRequestContinuePredicate requestContinuePredicate =null;

publicRequestContinuePredicate getRequestContinuePredicate() {
if (requestContinuePredicate ==null) {
requestContinuePredicate =newRequestContinuePredicate();
}
return requestContinuePredicate;
}

publicHttpServerBuilder setRequestContinuePredicate(finalRequestContinuePredicate requestContinuePredicate) {
this.requestContinuePredicate = requestContinuePredicate;
returnthis;
}

publicHttpServerBuilder addShouldContinueHttpRequestPredicate(finalPredicate<HttpRequest> predicate) {
getRequestContinuePredicate().add(predicate);
returnthis;
}



publicclassRequestContinuePredicateimplementsPredicate<HttpRequest>{

privatefinalCopyOnWriteArrayList<Predicate<HttpRequest>> predicates =newCopyOnWriteArrayList<>();

publicRequestContinuePredicateadd(Predicate<HttpRequest>predicate) {
predicates.add(predicate);
returnthis;
}

@Override
publicbooleantest(finalHttpRequesthttpRequest) {
boolean shouldContinue;

for (Predicate<HttpRequest> shouldContinuePredicate : predicates) {
shouldContinue = shouldContinuePredicate.test(httpRequest);
if (!shouldContinue) {
returnfalse;
}
}
returntrue;
}
}
We added a bunch of unit tests to make sure this actually works. :)
We created an example to show how this works.
packagecom.mammatustech;


importio.advantageous.qbit.admin.ManagedServiceBuilder;
importio.advantageous.qbit.annotation.RequestMapping;
importio.advantageous.qbit.http.request.HttpRequest;
importio.advantageous.qbit.http.server.HttpServerBuilder;

importjava.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")
publicclassHelloWorldService {


@RequestMapping("/hello")
publicStringhello() {
return"hello "+System.currentTimeMillis();
}

publicstaticvoidmain(finalString... args) {
finalManagedServiceBuilder managedServiceBuilder =
ManagedServiceBuilder.managedServiceBuilder().setRootURI("/root");

finalHttpServerBuilder httpServerBuilder = managedServiceBuilder.getHttpServerBuilder();

/** We can register our security token checker here. */
httpServerBuilder.addShouldContinueHttpRequestPredicate(HelloWorldService::checkAuth);

/* Start the service. */
managedServiceBuilder.addEndpointService(newHelloWorldService())
.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.
*/
privatestaticbooleancheckAuth(finalHttpRequesthttpRequest) {

/* Only check uri's that start with /root/hello. */
if (httpRequest.getUri().startsWith("/root/hello")) {

finalString 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)) {
returntrue;
} else {
/* Security token was not what we expected so send a 401 auth failed. */
httpRequest.getReceiver().response(401, "application/json", "\"shove off\"");
returnfalse;
}
}
returntrue;
}
}
To exercise this and show that it is working, let's use curl.

Pass the header token

$ curl --header "X-SECURITY-TOKEN: shibboleth"http://localhost:8080/root/hello/hello
"hello 1440012093122"

No header token

Health request

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.

EndpointServerBuilder

publicclassEndpointServerBuilder {
publicEndpointServerBuildersetupHealthAndStats(finalHttpServerBuilderhttpServerBuilder) {

if (isEnableStatEndpoint() || isEnableHealthEndpoint()) {
finalboolean healthEnabled = isEnableHealthEndpoint();
finalboolean statsEnabled = isEnableStatEndpoint();


finalHealthServiceAsync healthServiceAsync = healthEnabled ? getHealthService() :null;

finalStatCollection statCollection = statsEnabled ? getStatsCollection() :null;

httpServerBuilder.addShouldContinueHttpRequestPredicate(
newEndPointHealthPredicate(healthEnabled, statsEnabled,
healthServiceAsync, statCollection));
}


returnthis;
}

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;

publicclassEndPointHealthPredicateimplementsPredicate<HttpRequest> {

privatefinalboolean healthEnabled;
privatefinalboolean statsEnabled;
privatefinalHealthServiceAsync healthServiceAsync;
privatefinalStatCollection statCollection;


publicEndPointHealthPredicate(booleanhealthEnabled, booleanstatsEnabled,
HealthServiceAsynchealthServiceAsync, StatCollectionstatCollection
) {
this.healthEnabled = healthEnabled;
this.statsEnabled = statsEnabled;
this.healthServiceAsync = healthServiceAsync;
this.statCollection = statCollection;
}

@Override
publicbooleantest(finalHttpRequesthttpRequest) {

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;
} elseif (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\"");
}
} elseif (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;

}
}


Viewing all articles
Browse latest Browse all 217

Latest Images

Trending Articles



Latest Images