5 Advanced
5.1 Add custom events handlers
The ApiFest Mapping Server and the ApiFest OAuth 2.0 Server enable you to hook up custom events handlers. Custom event handlers are invoked when the server receives a request, a response is returned and an exception occurs.Request and response handlers should be annotated with @OnRequest/@OnResponse annotations respectively and implement LifecycleHandler interface, i.e. they should implement the following method:
public void handle(final HttpRequest request, final HttpResponse response)
These handlers may emit some event, log some information or whatever is needed.
Exception handlers should be annotated with @OnException and implement ExceptionEventHandler interface, i.e. they should implement the following method:
public void handleException(Exception ex, HttpRequest request)
The custom event handlers should be packaged in the same jar where the custom actions and filters are placed for the ApiFest Mapping server and in the same jar where custom user authentication is packaged for the ApiFest OAuth 2.0 Server. They are loaded on the server startup and are invoked when a request is received, a response is returned or an exception occurs. It's possible to have several handlers for a lifecycle event (request, response or exception) - they all be invoked but the order of invocation is not guaranteed.
Let's see an example where the request body content, the request URI and the request method about an incoming request to the ApiFest Mapping Server should be logged in JSON format.
Here is a simple code that implements it as a request handler:
@OnRequest
public class RequestHandler implements LifecycleHandler {
Logger log = LoggerFactory.getLogger(RequestHandler.class);
@Override
public void handle(HttpRequest request, HttpResponse response) {
logJsonData(request);
}
private void logJsonData(HttpRequest request) {
String content = new String(request.getContent().array(), Charset.forName("UTF-8"));
Map<String, String> params = new HashMap<String, String>();
params.put("request", content);
params.put("method", request.getMethod().getName());
params.put("uri", request.getUri());
JSONObject json = new JSONObject(params);
log.info(json.toString());
}
}
public class RequestHandler implements LifecycleHandler {
Logger log = LoggerFactory.getLogger(RequestHandler.class);
@Override
public void handle(HttpRequest request, HttpResponse response) {
logJsonData(request);
}
private void logJsonData(HttpRequest request) {
String content = new String(request.getContent().array(), Charset.forName("UTF-8"));
Map<String, String> params = new HashMap<String, String>();
params.put("request", content);
params.put("method", request.getMethod().getName());
params.put("uri", request.getUri());
JSONObject json = new JSONObject(params);
log.info(json.toString());
}
}
The class should be packaged in the same jar where custom actions and filters are packaged, set as custom.jar in the ApiFest Mapping Server properties file. Then on each request to the ApiFest Mapping Server the request information will be logged in JSON format. For instance, if GET /v1.0/countries resource is accessed, the following information will be logged:
{"request":"","method":"GET","uri":"/1.0/countries"}
Similar logic could be implemented for each response.
Here is an example response handler:
@OnResponse
public class ResponseHandler implements LifecycleHandler {
Logger log = LoggerFactory.getLogger(ResponseHandler.class);
@Override
public void handle(HttpRequest request, HttpResponse response) {
logJsonData(request, response);
}
private void logJsonData(HttpRequest request, HttpResponse response) {
String content = new String(response.getContent().array(), Charset.forName("UTF-8"));
Map<String, String> params = new HashMap<String, String>();
params.put("response", content);
params.put("method", request.getMethod().getName());
params.put("uri", request.getUri());
JSONObject json = new JSONObject(params);
log.info(json.toString());
}
}
public class ResponseHandler implements LifecycleHandler {
Logger log = LoggerFactory.getLogger(ResponseHandler.class);
@Override
public void handle(HttpRequest request, HttpResponse response) {
logJsonData(request, response);
}
private void logJsonData(HttpRequest request, HttpResponse response) {
String content = new String(response.getContent().array(), Charset.forName("UTF-8"));
Map<String, String> params = new HashMap<String, String>();
params.put("response", content);
params.put("method", request.getMethod().getName());
params.put("uri", request.getUri());
JSONObject json = new JSONObject(params);
log.info(json.toString());
}
}
An exception handler that logs information about the resource and the exception message could be implemented as the one below:
@OnException
public class ExceptionHandler implements ExceptionEventHandler {
Logger log = LoggerFactory.getLogger(ExceptionHandler.class);
@Override
public void handleException(Exception ex, HttpRequest request) {
logJsonData(ex, request);
}
private void logJsonData(Exception ex, HttpRequest request) {
Map<String, String> params = new HashMap<String, String>();
params.put("exception", ex.getMessage());
params.put("method", request.getMethod().getName());
params.put("uri", request.getUri());
JSONObject json = new JSONObject(params);
log.info(json.toString());
}
}
public class ExceptionHandler implements ExceptionEventHandler {
Logger log = LoggerFactory.getLogger(ExceptionHandler.class);
@Override
public void handleException(Exception ex, HttpRequest request) {
logJsonData(ex, request);
}
private void logJsonData(Exception ex, HttpRequest request) {
Map<String, String> params = new HashMap<String, String>();
params.put("exception", ex.getMessage());
params.put("method", request.getMethod().getName());
params.put("uri", request.getUri());
JSONObject json = new JSONObject(params);
log.info(json.toString());
}
}
5.2 Use UpstreamException in the ApiFest Mapping Server
If, for some reasons, the incoming request is not necessary to hit the backend application, then the UpstreamException could be used. Let's say you want to restrict the user access to some endpoints based on the user's IP. In that case, if the IP is not allowed you can throw an UpstreamException and the request will not hit the internal API. The constructor of the UpstreamException class requires a HttpResponse instance - the response that will be returned.Let's see an example.
public class IPRestrictionAction extends BasicAction {
@Override
public HttpRequest execute(HttpRequest request, String internalURI, HttpResponse tokenValidationResponse) throws MappingException, UpstreamException {
String userIP = getUserIP(request);
if (!isIPAllowed(userIP)) {
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
throw new UpstreamException(response);
}
// else continue to the internal API
return request;
}
private boolean isIPAllowed(String userIP) {
...
}
private String getUserIP(HttpRequest request) {
...
}
}
IPRestrictionAction class (as each ApiFest Action) extends BasicAction and implements the execute method. It obtains the user's IP from the request and if the IP is not allowed for the endpoint/internalURI then an exception is thrown and the response is:
@Override
public HttpRequest execute(HttpRequest request, String internalURI, HttpResponse tokenValidationResponse) throws MappingException, UpstreamException {
String userIP = getUserIP(request);
if (!isIPAllowed(userIP)) {
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
throw new UpstreamException(response);
}
// else continue to the internal API
return request;
}
private boolean isIPAllowed(String userIP) {
...
}
private String getUserIP(HttpRequest request) {
...
}
}
Status Code: 400 Bad Request