3 ApiFest Mapping Server
Now let's see how the ApiFest Mapping Server could be started and what configurations are needed in order to translates the requests between the public API and your backend API.3.1 Start ApiFest Mapping Server
In order to start the ApiFest Mapping Server, you should configure several properties. Here is how the configuration file looks like:
apifest.host=
apifest.port=
apifest.mappings=
apifest.global-errors=
token.validate.host=
token.validate.port=
connect.timeout=
custom.jar=
apifest.nodes=
hazelcast.password=
In the following table you can find the description of each property, whether it's mandatory and an example value.
apifest.port=
apifest.mappings=
apifest.global-errors=
token.validate.host=
token.validate.port=
connect.timeout=
custom.jar=
apifest.nodes=
hazelcast.password=
property | description | required | example |
---|---|---|---|
apifest.host | The host on which the ApiFest Mapping Server will run. | false, default value localhost | apifest.host=127.0.0.1 |
apifest.port | The port on which the ApiFest Mapping Server will run. | false, default value 8181 | apifest.port=8181 |
apifest.mappings | The path to the folder where mappings configuration files are placed. | The path to the folder where mappings configuration files are placed. | apifest.mappings=/home/apifest/mappings |
apifest.global-errors | The path to the XML file that describes custom global errors. | false | apifest.global-errors=/home/apifest/global-errors.xml |
token.validate.host | The host where the ApiFest OAuth 2.0 Server (or a balancer in front of the ApiFest OAuth 2.0 Server nodes) runs and access tokens will be validated. | false**, no access token validation will be performed | token.validate.host=127.0.0.1 |
token.validate.port | The port where the ApiFest OAuth 2.0 Server (or a balancer in front of the ApiFest OAuth 2.0 Server instances) runs and access tokens will be validated. | false**, no access token validation will be performed | token.validate.port=8181 |
connect.timeout | The connection timeout (in ms) used for connections to the backend application. | false, default value=10 | connect.timeout=5 |
custom.jar | The path to the jar with your custom classes that implement request/response transformations (actions/filters). | false | custom.jar=/home/apifest/transformations.jar |
apifest.nodes | A comma-separated list of all ApiFest Mapping Server nodes defined by host only. Used to create a Hazelcast cluster that will store the loaded mapping configuration. | false, if not set only localhost will be used | apifest.nodes=10.32.23.11, 10.32.23.10 |
hazelcast.password | Hazelcast password used to secure the Hazelcast nodes. If not defined the default Hazelcast password will be used. | false, default Hazelcast password will be used - dev-pass | hazelcast.password=securepass |
* If apifest.mappings property is not set, then a warning in the log will appear on the server startup.
** If token.validation.host and token.validation.port are not defined then the access token could not be validated and the response will be HTTP 401 {"error":"access token not valid"}. Warnings in the log will appear that these two properties are not set. Also, if the ApiFest Mapping Server could not connect to the ApiFest OAuth 2.0 Server, for any reason, the same error response will be returned. The corresponding error will be visible in the server log.
** If token.validation.host and token.validation.port are not defined then the access token could not be validated and the response will be HTTP 401 {"error":"access token not valid"}. Warnings in the log will appear that these two properties are not set. Also, if the ApiFest Mapping Server could not connect to the ApiFest OAuth 2.0 Server, for any reason, the same error response will be returned. The corresponding error will be visible in the server log.
The properties file should be passed as an environment variable on server startup:
-Dproperties.file=/home/apifest/apifest.properties
Once, the server is started, the following message will appear in the log:
ApiFest Mapping Server started at [host:port]
By default, the ApiFest Mapping Server starts with a log4j.xml configuration that will log in the console. In order to customize the log configuration, use standard lo4j.xml configuration and pass it as a system property on the ApiFest Mapping Server startup.
Here is an example for log4j.xml:
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="Console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%c] %m%n" />
</layout>
</appender>
<appender name="File" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="apifest-mapping.log" />
<param name="Append" value="true" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%c] %m%n" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="Console" />
<appender-ref ref="File" />
</root>
</log4j:configuration>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="Console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%c] %m%n" />
</layout>
</appender>
<appender name="File" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="apifest-mapping.log" />
<param name="Append" value="true" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p [%c] %m%n" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="Console" />
<appender-ref ref="File" />
</root>
</log4j:configuration>
And here is how you can pass it on the server startup:
java -jar -Dlog4j.configuration=file:///home/apifest/log4j.xml apifest/target/apifest-0.1.2-SNAPSHOT-jar-with-dependencies.jar
3.2 Create a mappings configuration file
A mappings configuration file describes the different API resources/endpoints exposed as public ones, i.e. it defines your public API.Let's see how a mapping configuration file look like:
<mappings version=[version]>
<backend host=[backend_host] port=[backend_port]/>
<endpoints>
<endpoint external=[external_path] internal=[internal_path] method=[HTTP_METHOD] authType=[auth_type] scope=[scope_name] varName=[var_name] varExpression=[var_expression] backendHost=[backendHost] backendPort=[backendPort]>
</endpoint>
</endpoints>
...
</mappings>
<backend host=[backend_host] port=[backend_port]/>
<endpoints>
<endpoint external=[external_path] internal=[internal_path] method=[HTTP_METHOD] authType=[auth_type] scope=[scope_name] varName=[var_name] varExpression=[var_expression] backendHost=[backendHost] backendPort=[backendPort]>
</endpoint>
</endpoints>
...
</mappings>
The following tables describes each tag/attribute, its description, whether it's required or not and gives an example value.
tag (attribute) name | description | required | example |
---|---|---|---|
mappings version | The version of the public API this mappings configuration file describes. | true | <mappings version="v1"> |
backend | Defines where the backend application is running, requests will be translated to that backend. Note,that all requests described in the mappings file will be pointed to that backend unless a specific backend per endpoint is set. | true | <backendhost="127.0.0.1" port="8181"> |
host | The backend host | true | host="balancer.apifest.com" |
port | The backend port | true | port="80" |
endpoint | Defines an API resource that will be exposed. | ||
external | The URI path at which the resource will be available in the public API. | true | external="/v1/countries" |
internal | The URI path at which the resourceis available in the backend application. | true | internal="/common/countries" |
method | The HTTP method used to access the resource. | true | method="GET" |
authType | The type of the access token usedto access that resource - user OR client-app (user corresponds to passwordaccess token type and client-app - client_credentials access token type defined in OAuth 2.0 specification); if no authType is defined, then no access token is required to access the resource | false | authType="client-app" authType="user" |
scope | Defines the allowed scope(s) of access tokens that can access that resource (see more details in chapter 3.10); if several scopes are allowed - use space as a delimiter; if no scope is defined, then any scope is allowed | false | scope="basic" scope="basic extended" |
varName | Defines a space-separated list of variable(s) name(s) in the external/internalpath. | false | varName="countryCode" |
varExpression | Defines a space-separated list of regular expressions (Java format) that defines the variable(s) in the external/internal path. Note, the order of the regular expressions(s) should correspond to the order used in the varNamevalue. | false | varExpression="[A-Z]{3}" |
backendHost | Specific backend host for that endpoint will be used instead of the one defined in <backend...> tag. Note, that it will be used if both endpoint specific backendHost and backendPort are defined. | false, the host and port defined in <backend> will be used | backendHost="127.0.0.1" |
backendPort | Specific backend port for that endpoint will be used instead of the one defined in <backend...> tag. Note, that it will be used if both endpoint specific backendHost and backendPort are defined. | false, the host and port defined in <backend> will be used | backendPort="8181" |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappings version="v0.1">
<backend port="5000" host="127.0.0.1"/>
<endpoints>
<endpoint external="/v0.1/countries" internal="/common/countries" method="GET" scope="basic" authType="client-app"/>
<endpoint external="/v0.1/countries/{countryCode}" internal="/common/countries/{countryCode}" method="GET" scope="basic" authType="client-app" varExpression="[A-Z]{3}" varName="countryCode"/>
</endpoints>
</mappings>
<mappings version="v0.1">
<backend port="5000" host="127.0.0.1"/>
<endpoints>
<endpoint external="/v0.1/countries" internal="/common/countries" method="GET" scope="basic" authType="client-app"/>
<endpoint external="/v0.1/countries/{countryCode}" internal="/common/countries/{countryCode}" method="GET" scope="basic" authType="client-app" varExpression="[A-Z]{3}" varName="countryCode"/>
</endpoints>
</mappings>
3.3 Mappings configuration validation
Once a mappings configuration file is created, it could be validated against the XML schema.You can execute the following command (from the home directory of the apifest code) in order to validate the mappings configuration file you have created.
mvn validate -Pmapping-validation -Dmappings.file=[mapping_file]
where mapping_file is the path to the mappings configuration file.If the mappings configuration file is not valid, an error message will be displayed and the build will fail, otherwise - the build will be successful and the following message will appear in the log:
INFO [com.apifest.MappingConfigValidator] mapping file [mapping_file] is valid
3.4 Create a custom action
For some requests a specific request transformation might be needed before the request hits you backend application. For instance, if you want to expose the following resource in your public API - GET /me that corresponds to GET /users/{userId} in your backend API, you will need a logic that somehow (we will see how that could be done later) extracts {userId}. That logic should be implemented as a custom Action class. An action in the ApiFest context means all such request transformations made before a request hits your backend application.Each custom Action class should extend com.apifest.api.BasicAction class and implement its execute method. Let's see an example.
Here is a mappings configuration that describes the translation from the public call GET /v1.0/me to the internal one - GET /users/{userId}.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappings version="v1.0">
<backend port="5000" host="127.0.0.1"/>
<endpoints>
<endpoint external="/v0.1/me" internal="/users/{userId}" method="GET" scope="basic" authType="user">
<action class="com.apifest.example.ReplaceUserIdAction"/>
</endpoint>
</endpoints>
</mappings>
<mappings version="v1.0">
<backend port="5000" host="127.0.0.1"/>
<endpoints>
<endpoint external="/v0.1/me" internal="/users/{userId}" method="GET" scope="basic" authType="user">
<action class="com.apifest.example.ReplaceUserIdAction"/>
</endpoint>
</endpoints>
</mappings>
As you can see the action class is a fully-qualified class defined to the endpoint. The class will be invoked while the request is translated from the public API to the backend application.
Here is the code of ReplaceUserIdAction class.
public class ReplaceUserIdAction extends BasicAction {
protected static final String USER_ID = "{userId}";
@Override
public HttpRequest execute(HttpRequest req, String internalURI, HttpResponse tokenValidationResponse) {
String newURI = internalURI.replace(USER_ID, BasicAction.getUserId(tokenValidationResponse));
req.setUri(newURI);
return req;
}
}
protected static final String USER_ID = "{userId}";
@Override
public HttpRequest execute(HttpRequest req, String internalURI, HttpResponse tokenValidationResponse) {
String newURI = internalURI.replace(USER_ID, BasicAction.getUserId(tokenValidationResponse));
req.setUri(newURI);
return req;
}
}
The class (along with all other custom actions and filters) should be packaged in a jar and the path to the jar should be set in the custom.jar property in the apifest.properties file.
3.5 Create a custom filter
For some requests a response transformation might be needed. For instance, if your backend API returns the following response:{"userId":"123456","email":"user@apifest.com","balance":"120"} to the request GET /users/123456 but for some reasons you don't want to return the user's balance, then you can use a filter. Of course, you can implement whatever response transformation you need.
Let's see how such a filter class might look like:
public class RemoveBalanceFilter extends BasicFilter {
@Override
public HttpResponse execute(HttpResponse response) {
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(response.getContent().toString(CharsetUtil.UTF_8)).getAsJsonObject();
json.remove("balance");
byte[] newContent = json.toString().getBytes(CharsetUtil.UTF_8);
response.setContent(ChannelBuffers.copiedBuffer(newContent));
HttpHeaders.setContentLength(response, newContent.length);
return response;
}
}
@Override
public HttpResponse execute(HttpResponse response) {
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(response.getContent().toString(CharsetUtil.UTF_8)).getAsJsonObject();
json.remove("balance");
byte[] newContent = json.toString().getBytes(CharsetUtil.UTF_8);
response.setContent(ChannelBuffers.copiedBuffer(newContent));
HttpHeaders.setContentLength(response, newContent.length);
return response;
}
}
Each custom Filter class should extend com.apifest.api.BasicFilter class and implement its execute method.
Here is the corresponding mapping configuration for that example:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappings version="v1.0">
<backend port="5000" host="127.0.0.1"/>
<endpoints>
<endpoint external="/v0.1/me" internal="/users/{userId}" method="GET" scope="basic" authType="user">
<action class="com.apifest.example.ReplaceUserIdAction"/>
<filter class="com.apifest.example.RemoveBalanceFilter"/>
</endpoint>
</endpoints>
</mappings>
<mappings version="v1.0">
<backend port="5000" host="127.0.0.1"/>
<endpoints>
<endpoint external="/v0.1/me" internal="/users/{userId}" method="GET" scope="basic" authType="user">
<action class="com.apifest.example.ReplaceUserIdAction"/>
<filter class="com.apifest.example.RemoveBalanceFilter"/>
</endpoint>
</endpoints>
</mappings>
The class (along with all other custom filters and actions) should be packaged in a jar and the path to the jar should be set in the custom.jar property in the apifest.properties file.
3.6 Customized errors
If, for any reasons, you need to customize the responses from your backend API (let's say for different API versions), you might configure that in the mappings configuration file.Let's see an example. For instance, your backend API returns the following response:
HTTP 404 {"error":"User with id 654321 not found"}
to the request GET /users/654321 but you want to return a unified HTTP 404 response error message, for instance: {"error":"resource not found"} for all HTTP 404 responses. Then you can configure that by using custom errors in the mappings configuration file:
<mappings>
...
<errors>
<error status="404" message='{"error":"resource not found"}'/>
</errors>
</mappings>
...
<errors>
<error status="404" message='{"error":"resource not found"}'/>
</errors>
</mappings>
This customization will be applied to the corresponding API version and when a HTTP 404 response is returned by the internal API. If you need to customize the error responses that are not related to mapped resources, i.e. the user tries to hit a resource that is not available, then you can use the customized global errors - see chapter 3.8.
3.7 Use Apifest Doclet to create mappings configuration files
You can use the ApiFest Doclet to generate the mappings configuration file automatically. However, you will need to put some Javadoc annotations in your code. For HTTP method, the ApiFest Doclet reads JAX-RS annotations - javax.ws.rs.GET, javax.ws.rs.POST, javax.ws.rs.PUT, javax.ws.rs.DELETE, javax.ws.rs.HEAD and javax.ws.rs.OPTIONS.Here is a table that describes the ApiFest Javadoc annotations required to generate a mappings configuration file.
ApiFest Javadoc annotation | description | example |
---|---|---|
@apifest.external | Corresponds to the endpoint external path (skip the API version here, it will be prepended when mappingconfiguration file is generated) | @apifest.external/countries/{countryCode} |
@apifest.internal | Corresponds to the endpoint internal path. | @apifest.internal/common/countries/{countryCode} |
@apifest.scope | Corresponds to OAuth 2.0 scope of the endpoint - a space-separatedlist of scopes. | @apifest.scopebasic extended |
@apifest.auth.type | Corresponds to the endpoint authType - user or client-app. | @apifest.auth.type client-app |
@apifest.re.{var_name} | Defines the regular expression used to map the corresponding var_name. In case of several variables, add @apifest.re.{var_name} for each of them. |
@apifest.re.countryCode[A-Z]{3} |
@apifest.action | Defines the action class (fully qualified name) that will be executed for an endpoint. | com.apifest.example.ReplaceCustomerIdAction |
@apifest.filter | Defines the filter class (fully qualified name) that will be extecuted for an endpoint. | com.apifest.example.RemoveBalanceFilter |
@apifest.backend.host | Corresponds to the endpoint specific backendHost. | @apifest.backend.host127.0.0.1 |
@apifest.backend.port | Corresponds to the endpoint specific backendPort. | @apifest.backend.port8181 |
Here is an example of an annotated method:
/**
* @apifest.external /countries/{countryCode}
* @apifest.internal /countries/{countryCode}
* @apifest.re.countryCode [A-Z]{3}$
* @apifest.auth.type user
* @apifest.scope test_scope
*/
@GET
@Path("/{countryCode}")
public Response getByCode(@PathParam("countryCode") String countryCode);
* @apifest.external /countries/{countryCode}
* @apifest.internal /countries/{countryCode}
* @apifest.re.countryCode [A-Z]{3}$
* @apifest.auth.type user
* @apifest.scope test_scope
*/
@GET
@Path("/{countryCode}")
public Response getByCode(@PathParam("countryCode") String countryCode);
In order to create a mappings configuration file using the ApiFest Doclet, you can add a maven profile in your pom.xml file. Here is an example:
<profile>
<id>gen-mapping</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<dependencies>
<dependency>
<groupId>com.apifest</groupId>
<artifactId>apifest-doclet</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>javadoc</goal>
</goals>
<configuration>
<doclet>com.apifest.doclet.Doclet</doclet>
<docletArtifact>
<groupId>com.apifest</groupId>
<artifactId>apifest-doclet</artifactId>
<version>0.1.0</version>
</docletArtifact>
<additionalJOptions>
<additionalJOption>-J-Dmapping.version=v1.0</additionalJOption>
<additionalJOption>-J-Dmapping.filename=mapping_v1_0.xml</additionalJOption>
<additionalJOption>-J-Dbackend.host=127.0.0.1</additionalJOption>
<additionalJOption>-J-Dbackend.port=8181</additionalJOption>
<additionalJOption>-J-Dapplication.path=/common</additionalJOption>
</additionalJOptions>
<useStandardDocletOptions>false</useStandardDocletOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<id>gen-mapping</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<dependencies>
<dependency>
<groupId>com.apifest</groupId>
<artifactId>apifest-doclet</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>javadoc</goal>
</goals>
<configuration>
<doclet>com.apifest.doclet.Doclet</doclet>
<docletArtifact>
<groupId>com.apifest</groupId>
<artifactId>apifest-doclet</artifactId>
<version>0.1.0</version>
</docletArtifact>
<additionalJOptions>
<additionalJOption>-J-Dmapping.version=v1.0</additionalJOption>
<additionalJOption>-J-Dmapping.filename=mapping_v1_0.xml</additionalJOption>
<additionalJOption>-J-Dbackend.host=127.0.0.1</additionalJOption>
<additionalJOption>-J-Dbackend.port=8181</additionalJOption>
<additionalJOption>-J-Dapplication.path=/common</additionalJOption>
</additionalJOptions>
<useStandardDocletOptions>false</useStandardDocletOptions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
Then you can use the following command in order to create the mappings configuration file:
mvn validate -Pgen-mapping
The following XML configuration will be produced:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappings version="v1.0">
<backend port="8181" host="127.0.0.1"/>
<endpoints>
<endpoint external="/v1.0/countries/{countryCode}" internal="/common/countries/{countryCode}" method="GET" scope="test_scope" authType="user" varName="countryCode" varExpression="[A-Z]{3}$"/>
</endpoints>
</mappings>
<mappings version="v1.0">
<backend port="8181" host="127.0.0.1"/>
<endpoints>
<endpoint external="/v1.0/countries/{countryCode}" internal="/common/countries/{countryCode}" method="GET" scope="test_scope" authType="user" varName="countryCode" varExpression="[A-Z]{3}$"/>
</endpoints>
</mappings>
It will be stored in [project_home]/target/site/apidocs/mapping_v1_0.xml file.
The ApiFest Doclet expects the following environment variables:
name | description | required | example |
---|---|---|---|
mapping.version | The version of the API that will be exposed with the current mappings configuration file. | true | v1.0 |
mapping.filename | The name of the mappings configuration file that will be created. | false, the default value is output_mapping_[mapping.version].xml | mappings_v1.0.xml |
backend.host | The host on which the internal API could be accessed. | true | 127.0.0.1 |
backend.port | The port on which the internal API could be accessed. | true | 8181 |
application.path | The application path that will be prepended to each endpoint path. | false | /commons |
defaultActionClass | The default action will be added to each endpoint if no specific endpoint action is set. | false | com.apifest.example.ReplaceUserIdAction |
defaultFilterClass | The default filter will be added to each endpoint if no specific endpoint filter is set. | false | com.apifest.example.RemoveBalanceFilter |
If you do not use maven to build your project, you can use the ApiFest Doclet as a standard doclet to generate mappings configuration file. Here is how to run the ApiFest Doclet on the command line:
javadoc -docletpath %JAVA_HOME%/lib/tools.jar";[PATH_TO_apifest-api.jar];[PATH_TO_apifest-doclet.jar -classpath [all_jars_required] -doclet com.apifest.doclet.Doclet -J-Dmapping.version=[API_version] -J-Dmapping.filename=[output_mappings_file] -J-Dbackend.host=[backend_host] -J-Dbackend.port=[backend_port] -J-Dapplication.path=[application_path] -J-DdefaultActionClass=[default_action_class] -J-DdefaultFilterClass=[default_filter_class] [your_package]
3.8 Customized global errors
ApiFest enables you to customize the error response messages, when the errors are not related to a mappings configuration. These are the cases when a resource is not mapped at all, something goes wrong with the token validation or something goes wrong when the request is mapped and Internal Server Error is thrown. Then the following default responses will be returned:
Status Code: 404 Not Found
Content-Length: 21
Content-Type: application/json
Response Body: {"error":"Not found"}
andContent-Length: 21
Content-Type: application/json
Response Body: {"error":"Not found"}
Status Code: 401 Unauthorized
Content-Length: 33
Content-Type: application/json
Response Body: {"error":"access token required"}
andContent-Length: 33
Content-Type: application/json
Response Body: {"error":"access token required"}
Status Code: 500 Internal Server Error
You can change the error response message using the custom global errors. Note, that the responses are in JSON format and the customized global errors are expected to be in JSON format, too.
The description of these error responses are not related to an API version that's why they are described in a separate XML configuration file. Here is an example of a global errors configuration file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<global-errors>
<error status="401" message='{"error":"authorization required"}'/>
<error status="404" message='{"error":"resource not found"}'/>
<error status="500" message='{"error":"ops...something went wrong"}'/>
</global-errors>
<global-errors>
<error status="401" message='{"error":"authorization required"}'/>
<error status="404" message='{"error":"resource not found"}'/>
<error status="500" message='{"error":"ops...something went wrong"}'/>
</global-errors>
The file path should be set as apifest.global-errors property in the apifest.properties file.
Then if you try to access a resource that is not available in the public API, you will receive the following response:
Status Code: 404 Not Found
Content-Length: 30
Content-Type: application/json
Response Body: {"error":"resource not found"}
Content-Length: 30
Content-Type: application/json
Response Body: {"error":"resource not found"}
Any changes in the file will be reloaded on ApiFest online configuration reload (see chapter 3.11).
3.9 Customized global errors validation
Once a mappings configuration file is created, it could be validated against the XML schema.You can execute the following command (from the home directory of the apifest code) in order to validate the mappings configuration file you have created:
mvn validate -Pglobal-errors-validation -Dglobal-errors.file=[global-errors_file]
where global-errors_file is the path to the customized global errors configuration file.If the global errors configuration file is not valid, an error message will be displayed and the build will fail, otherwise - the build will be successful and the following message will appear in the log:
INFO [com.apifest.GlobalErrorsConfigValidator] global errors file [global-errors_file] is valid
3.10 Scope definition
The scope in the mappings configuration file defines the OAuth 2.0 scope of the access tokens that are permitted to access a given endpoint. Let's say the following endpoint is defined in the mappings configuration file:
<endpoints>
<endpoint external="/v0.1/me" internal="/users/{userId}" method="GET" scope="basic" authType="user">
<action class="com.apifest.example.ReplaceUserIdAction"/>
<filter class="com.apifest.example.RemoveBalanceFilter"/>
</endpoint>
</endpoints>
<endpoint external="/v0.1/me" internal="/users/{userId}" method="GET" scope="basic" authType="user">
<action class="com.apifest.example.ReplaceUserIdAction"/>
<filter class="com.apifest.example.RemoveBalanceFilter"/>
</endpoint>
</endpoints>
The scope defined is basic, that means that this endpoint could be accessed with access tokens issued with scope basic. If one tries to access the endpoint with an access token that has another scope - extended, for instance, then the following response will be returned:
Status Code: 401 Unauthorized
Content-Length: 40
Content-Type: application/json
Response Body: {"error":"access token scope not valid"}
Content-Length: 40
Content-Type: application/json
Response Body: {"error":"access token scope not valid"}
The scope may be defined as a space-separated list of scopes that are allowed for the endpoint, for instance scope="basic extended". Then the permitted access tokens will have either basic or extended scope or both. As the ApiFest Mapping Server may reload its mappings configuration online, that enables online changes of endpoint scopes - just edit the scope in the mappings configuration file and then call GET /apifest-reload.
See more about the scope in chapter 3.10.
3.11 Online configuration reload
The mappings configuration and the customized global errors configuration could be updated online while the ApiFest Server is running. That helps you to expose a new endpoint online or to change the error response message online.In order to reload these configurations, one needs to call GET /apifest-reload service. Note, that it's enough to call the service on one of the APiFest Mapping Server instances (if you are running several instances as a cluster) as these configurations are shared among all ApiFest Mapping Server instances.
In order to check the currently loaded mappings, you can use GET /apifest-mappings.
Here is an example response:
{
"v1.0": {
"mappings": {
"com.apifest.MappingPattern@56eff2b4": {
"backendPort": 5000,
"backendHost": "127.0.0.1",
"authType": "user",
"scope": "test_scope",
"method": "GET",
"internalEndpoint": "/users/{userId}",
"externalEndpoint": "/v1.0/me",
"action": {
"actionClassName": "com.apifest.example.ReplaceUserIdAction"
}
},
"com.apifest.MappingPattern@e1a60a8b": {
"backendPort": 5000,
"backendHost": "127.0.0.1",
"varExpression": "[A-Z]{3}",
"varName": "countryCode",
"authType": "client-app",
"scope": "test_scope",
"method": "GET",
"internalEndpoint": "/countries/{countryCode}",
"externalEndpoint": "/v1.0/countries/{countryCode}"
}
},
"actions": {
"com.apifest.example.ReplaceUserIdAction": "com.apifest.example.ReplaceUserIdAction"
},
"filters": {
},
"errors": {
"404": "{\"error\":\"resource not found\"}"
}
}
}
"v1.0": {
"mappings": {
"com.apifest.MappingPattern@56eff2b4": {
"backendPort": 5000,
"backendHost": "127.0.0.1",
"authType": "user",
"scope": "test_scope",
"method": "GET",
"internalEndpoint": "/users/{userId}",
"externalEndpoint": "/v1.0/me",
"action": {
"actionClassName": "com.apifest.example.ReplaceUserIdAction"
}
},
"com.apifest.MappingPattern@e1a60a8b": {
"backendPort": 5000,
"backendHost": "127.0.0.1",
"varExpression": "[A-Z]{3}",
"varName": "countryCode",
"authType": "client-app",
"scope": "test_scope",
"method": "GET",
"internalEndpoint": "/countries/{countryCode}",
"externalEndpoint": "/v1.0/countries/{countryCode}"
}
},
"actions": {
"com.apifest.example.ReplaceUserIdAction": "com.apifest.example.ReplaceUserIdAction"
},
"filters": {
},
"errors": {
"404": "{\"error\":\"resource not found\"}"
}
}
}
In order to check the currently loaded customized global errors, you can use GET /apifest-global-errors.
Here is an example response:
{
"401": "{\"error\":\"unauthorized\"}",
"404": "{\"error\":\"resource not mapped\"}"
}
"401": "{\"error\":\"unauthorized\"}",
"404": "{\"error\":\"resource not mapped\"}"
}