Using the HTTP Filter endpoint to intercept an API request to an ad-hoc API exposed in Martini. The article will guide you through setting up the HTTP Filter endpoint including the required configurations and services.
How does it work?
The HTTP filter endpoint in Martini allows developers to invoke services upon the receipt of a request. Using this feature, we can implement an interceptor service that is triggered before the actual request is sent to the ad-hoc REST API endpoint that is also exposed in Martini.
This guide is geared towards advanced users so this article assumes that you have prior knowledge about Martini, as well as a good understanding of technologies used for this demo.
Implementation
The resulting folder structure in your package should look something like this. In this walkthrough, you can ignore the services_sql, and config folder.
Start by creating the code directories you see in the screenshot above excluding config, and services_sql.
Creating the Interceptor Service
The interceptor service will handle the checking if the request was sent securely or not. In this demo, HMAC will be used as the security scheme for the authenticated request. In the groovy folder, create the Groovy class for the HmacUtil, and copy the code below:
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.InvalidKeyException
import java.security.Signature
class HmacUtil {
public static String hmac_sha1( String key, String data ) {
try {
Mac mac = Mac.getInstance( 'HmacSHA1' )
SecretKeySpec secretKeySpec = new SecretKeySpec( key.getBytes(), 'SHA1' )
mac.init( secretKeySpec )
byte[] digest = mac.doFinal( data.getBytes() )
String encodedData = digest.encodeBase64().toString()
return encodedData
} catch ( InvalidKeyException e ) {
throw new RuntimeException( 'Invalid key exception while converting to HMac SHA1' )
}
}
public static boolean hmac_verify( String key, String sig, String data ) {
String hmacSig = hmac_sha1( key, data )
println "=========Generated sig: ${hmacSig}\tReceived Sig: ${sig}========="
return sig.equals( hmacSig )
}
}
The code above will be our HMAC signature generator and checker for verifying the HMAC signature that will be sent along the request that we will intercept using the HTTP Filter endpoint
Then we will need some helper classes that will handle the requests and responses the interceptor service will intercept. In the same folder, create two new Groovy classes using the code snippets below
CachedBodyHttpServletInputStream
import javax.servlet.ReadListener
import javax.servlet.ServletInputStream
public class CachedBodyServletInputStream extends ServletInputStream {
private InputStream cachedBodyInputStream
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
@Override
public boolean isFinished() {
return cachedBodyInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener( ReadListener listener ) {
throw new RuntimeException( 'Not implemented' )
}
}
CachedBodyHttpServletRequest
import demo016.groovy.CachedBodyServletInputStream
import javax.servlet.ServletInputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletRequestWrapper
import org.springframework.util.StreamUtils
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedBody
public CachedBodyHttpServletRequest( HttpServletRequest request ) throws IOException {
super( request )
InputStream requestInputStream = request.getInputStream()
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream)
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream( this.cachedBody )
}
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( this.cachedBody )
return new BufferedReader( new InputStreamReader( byteArrayInputStream ) )
}
}
The two new Groovy classes that were introduced will be used to extract the request body from the request so we can sign it later.
Once you have that sorted, create a new Martini Service with the name of your choice under the services folder with the following parameters:
Input
$__request - object with class name javax.servlet.http.HttpServletRequest
$__response - object with class name javax.servlet.http.HttpServletResponse
$__chain - object with class name javax.servlet.FilterChain
Output
$__discontinueChain - boolean
$__newResponse - object with class name javax.servlet.http.HttpServletResponse
$__newRequest - object with class name javax.servlet.http.HttpServletRequest
The resulting output should look like this
After specifying what the inputs and outputs the interceptor service will have, it’s time to work on the rest of the service logic. Here’s what it is going to roughly look like at the end of this section
The first thing you need to do is to add a try-catch block. In the try block, add a new Map step, and add set expressions to the following properties that was previously declared
$__newResponse
$__newRequest
payloadString
This property needs to be declared in the Map step, once declared, provide the set expression below
The resulting Input/Output tree in the map step should look similar below:
The next step is to add the Groovy method/function that was defined in the HmacUtil class that was created in the previous steps. Refer to the image below
In the service mapper view, directly map the payloadString to data, and use set expression to extract the request headers that will be intercepted by this service you are working on right now. Finally, declare a new property for the output of this function/method call and map the output from hmac_verify function to it. After doing so, your Mapper UI should look similar below:
The logger step is optional. Going forward, add a new Fork Step using the property declared for identifying if the HMAC signature provided in the request is valid or not. After adding the Fork Step, add two Blocks that will hold the logic when isValid is true or false. Refer to the image below.
The false Block
The first block added will be labeled as false. Inside the false Block step, add a new Script Step, and a Map step. In the Script Step, use code snippet below:
$__newResponse.setStatus( 401 )
$__newResponse.setContentType( 'application/json' )
$__newResponse.getWriter().write( '{\n\t"message": "invalid HMAC signature"\n}' )
$__newResponse.getWriter().flush()
The resulting set expression should be similar to this:
The true block
Repeat the same steps you have taken in the false Block step. With the changes on the Script Step only:
The Catch block
In the catch block, just add an error logger that will display what went wrong during the execution if the service suddenly throws an exception.
Configuring the HTTP Filter Endpoint
Now that we have sorted the Martini Service that will be triggered upon intercepting a request made to the API that will be added in the filter, It’s time to create the HTTP Filter endpoint. Create the HTTP Filter endpoint under the Endpoints node of your package.
The resulting configuration will look like this
In the Service section, select the interceptor service that you created in the previous step.
Under the HTTP Filter Configuration, select the Filter Order to Pre Authentication. This will be the point at which the configured service will be executed when a request is matched. In this case, we want to trigger the interceptor service before the actual request is passed to the target service that will be triggered by the request.
In the later section, you will create the service and will be exposing it as an ad-hoc request with request method POST, so select the POST item in the Request Methods section.
Creating a Service and Exposing it via Ad-Hoc REST
In the services folder, create a new service. This service will be used to expose an Ad-Hoc REST API with request method POST with a path that matches the path that was defined in the HTTP Filter configuration. To make things simple, the ad-hoc REST API will only return what value was provided in the x-event header that will be included in the request.Â
Create the service with the following parameters:
Input
payload - a data model with several properties (refer to screenshot at the end of this list)
$request - object with class name javax.servlet.http.HttpServletRequest
Output
response - a data model with a string property called message
The resulting service inputs and output should be similar below
Once the inputs and output has been defined, finish the rest of the service body. Refer to the image below:
After defining the service logic. In the properties table located at the right side of the UI, look for the REST tab. This tab will appear if you click on an empty space within the service body.
Configure your service with the same configuration, and the ad-hoc REST API is now done.
HTTP Filter in Action
Now that you have all the stuff you need, it’s time to see it in action. Open the HTTP Client and start sending a request to the ad-hoc REST API you just created. Use the following values for the following headers:
x-api-key - GwlLUAQyDjZ3LpIeVv9LSefdq3arT6ws
x-hmac-signature - Kwe1173k9WtPUoI/x+qOki4dvOY=
x-event - either DEVICE_BREAKDOWN or JOB_CREATED
Samples
A sample request body
The header configuration
Sample request+response
A request with invalid HMAC signature