A versatile and lightweight API Gateway for REST and legacy SOAP Web Services, built in Java.
- Deploy APIs directly from OpenAPI specifications.
- Validate requests and responses against OpenAPI and JSON Schema.
- Support for JSON Web Tokens, OAuth2, API Keys, NTLM, and Basic Authentication.
- Built-in OAuth2 Authorization Server.
- Rate limiting and traffic control
- Protection for GraphQL, JSON, and XML APIs against malicious inputs.
- Seamless support for SOAP message routing.
- Configure, validate, and rewrite WSDL-based services, including message validation.
- Intuitive Admin Web Console for monitoring and management.
- Advanced load balancing to ensure high availability.
- Flexible message transformation for seamless data processing.
- Embeddable reverse proxy HTTP framework to build custom API gateways.
- Getting Started
- Basics Routing, rewriting
- OpenAPI Support
- Routing
- Scripting
- Message Transformation
- Conditionals with if
- Security
- Traffic Control Rate limiting, Load balancing
- Legacy Web Services SOAP and WSDL
- Operation
- Ensure Java 17 or newer is installed.
- Download and Extract
- Get the latest binary release.
- Unzip the downloaded file to a directory of your choice.
- Start the Gateway
- Open a terminal in the extracted directory.
- Run the appropriate command for your operating system:
- Linux/Mac:
./service-proxy.sh
- Windows:
service-proxy.bat
- Linux/Mac:
- Access the Gateway
- Open your browser and navigate to http://localhost:2000.
- The gateway will forward traffic to https://api.predic8.de by default.
- Modify Configuration
- To customize the behavior, edit the file located at
conf/proxies.xml
.
Run the Membrane API Gateway in a Docker container:
docker run -p 2000:2000 predic8/membrane
- Open http://localhost:2000 in your browser, or use
curl
:curl http://localhost:2000
- The response will match the output of directly calling https://api.predic8.de.
To use a custom proxies.xml configuration file, bind it to the Membrane container.
docker run -v proxies.xml:/opt/membrane/conf/proxies.xml -p 2000:2000 predic8/membrane
docker run -v "$(pwd)/proxies.xml:/opt/membrane/conf/proxies.xml" -p 2000:2000 predic8/membrane
For detailed Docker setup instructions, see the Membrane Deployment Guide.
- Try the code snippets below.
- Run the provided examples to see Membrane in action.
- Follow the REST API Tutorial to learn about deploying and securing RESTful services.
- Check out the SOAP API Tutorial for legacy web service integration.
- For detailed guidance, visit the official documentation.
To define new APIs or modify the existing configuration, edit the proxies.xml
file located in the conf
folder. This file serves as the central configuration point for managing API behavior and routing rules.
Explore and copy the sample snippets below into the proxies.xml
file and modify them to suit your needs. Then save or restart the gateway to apply the changes. Usually a save will trigger a reload automatically.
For even more samples have a look at the examples
folder.
To forward requests from the API Gateway to a backend, use a simple api
configuration. The example below routes requests received on port 2000
with a path starting with /shop
to the backend at https://api.predic8.de
:
<api port="2000">
<path>/shop</path>
<target url="https://api.predic8.de"/>
</api>
After adding the configuration to the proxies.xml
file, open the following URL in your browser to test the API: http://localhost:2000/shop/v2/
Membrane allows you to configure APIs directly from OpenAPI documents in the proxies.xml
file. Backend addresses and other details are automatically derived from the OpenAPI description.
The snippet below shows how to deploy an API using an OpenAPI file (fruitshop-api.yml
) with request validation enabled:
<api port="2000">
<openapi location="fruitshop-api.yml" validateRequests="yes"/>
</api>
Once configured, a list of deployed APIs is available at:
http://localhost:2000/api-docs
Click on an API title in the list to open the Swagger UI for interactive exploration and testing:
For additional details and a working example, check out the OpenAPI Example.
Membrane offers versatile routing options. Its fallthrough mechanism ensures that only the first matching API rule is applied, skipping the rest. This enables precise and efficient routing based on criteria such as paths, HTTP methods, or hostnames.
The configuration below demonstrates several routing rules, with comments explaining their behavior:
<!-- Block POST requests -->
<api port="2000" method="POST">
<response>
<static>POST is blocked!</static>
</response>
<return statusCode="405"/>
</api>
<!-- Requests matching "/shop/v2/products/.*" -->
<api port="2000">
<path isRegExp="true">/shop/v2/products/.*</path>
<target url="https://api.predic8.de" />
</api>
<!-- All other requests to "/shop" -->
<api port="2000">
<path>/shop</path>
<target url="https://api.predic8.de" />
</api>
<!-- Requests with a HOST header of "www.predic8.de" -->
<api port="2000" host="www.predic8.de">
<response>
<static>Calling Web Server</static>
</response>
<return/>
</api>
<!-- Requests to "api.predic8.de" -->
<api port="2000" host="api.predic8.de">
<response>
<static>Calling API</static>
</response>
<return/>
</api>
port
: The port Membrane listens on for incoming connections.method
: Matches the HTTP method (e.g.,GET
,POST
,DELETE
). Use*
to match any method.host
: Specifies hostnames for routing. Supports basic globbing with*
.path
: Matches request paths. Regular expressions can be enabled withisRegExp="true"
.
For more routing options, see the Membrane API documentation.
Sometimes, you may need an endpoint that doesn’t forward requests to a backend. Membrane makes it easy to create such endpoints.
The following configuration creates a health check endpoint that responds to requests at http://localhost:2000/health:
<api port="2000">
<path>/health</path>
<response>
<static>I'am fine.</static>
</response>
<return statusCode="200"/>
</api>
You can block specific paths (e.g., /nothing
) while allowing other calls to pass through.
Routing Note: APIs are matched from top to bottom. When multiple APIs share the same port, place the APIs with stricter routing conditions higher in the configuration.
<api port="2000"> <!-- Calls to /nothing are blocked with 404 -->
<path>/nothing</path>
<response>
<static>Nothing to see!</static>
</response>
<return statusCode="404"/>
</api>
<api port="2000">
<response>
<static>Other call to port 2000</static>
</response>
<return statusCode="404"/>
</api>
The URLs of request can be rewritten dynamically before forwarding them to the backend. This is useful for restructuring API paths or managing legacy endpoints.
The following configuration rewrites requests starting with /fruitshop
to /shop/v2
, preserving the remainder of the path:
<api port="2000">
<path>/fruitshop</path>
<rewriter>
<map from="^/fruitshop(.*)" to="/shop/v2/$1"/>
</rewriter>
<target url="https://api.predic8.de"/>
</api>
A request to:
http://localhost:2000/fruitshop/products/4
will be rewritten to and forwarded to the backend at:
https://api.predic8.de/shop/v2/products/4
Membrane has powerful scripting features that allow to modify the desired of an API using Groovy or Javascript.
- Custom Responses: Tailor responses dynamically based on client requests or internal logic.
- Mocking APIs: Simulate API behavior during testing or development phases.
- Dynamic Headers: Add headers conditionally based on business rules.
- Debugging: Inspect incoming requests during development.
The following API executes a Groovy script during the request and the response.
<api port="2000">
<groovy>
println "I'am executed in the ${flow} flow"
println "HTTP Headers:\n${header}"
</groovy>
<target url="https://api.predic8.de"/>
</api>
After invoking http://localhost:2000 you can see the following output in the console where you have started Membrane:
I'am executed in the REQUEST flow
HTTP Headers:
Host: localhost:2000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0
...
I'am executed in the RESPONSE flow
HTTP Headers:
Content-Length: 390
Content-Type: application/json
You can realize a load balancer by setting the destination randomly.
<api port="2000">
<request>
<groovy>
sites = ["https://api.predic8.de","https://membrane-api.io","https://predic8.de"]
Collections.shuffle sites
exchange.setDestinations(sites)
</groovy>
</request>
<target/> <!-- No details needed target uses destinations from exchange -->
</api>
The groovy
plugin in Membrane allows you to dynamically generate custom responses. The result of the last line of the Groovy script is passed to the plugin. If the result is a Response
object, it will be returned to the caller.
The following example creates a custom JSON response with a status code of 200
, a specific content type, and a custom header:
<api port="2000">
<groovy>
Response.ok()
.contentType("application/json")
.header("X-Foo", "bar")
.body("""
{
"success": true
}
""")
.build()
</groovy>
</api>
- The
Response.ok()
method initializes a new HTTP response with a status of200 OK
. - The
contentType()
method sets theContent-Type
header, ensuring the response is identified as JSON. - The
header()
method adds custom headers to the response. - The
body()
method specifies the response payload. - The
build()
method finalizes the response object, which is then returned by thegroovy
plugin.
When accessing this API, the response will look like this:
HTTP/1.1 200 OK
Content-Type: application/json
X-Foo: bar
{
"success": true
}
For more information about using Groovy with Membrane, refer to:
In addition to Groovy, Membrane supports JavaScript for implementing custom behavior. This allows you to inspect, modify, or log details about requests and responses.
The following example logs all HTTP headers from incoming requests and responses to the console:
<api port="2000">
<javascript>
console.log("------------ Headers: -------------");
var fields = header.getAllHeaderFields();
for (var i = 0; i < fields.length; i++) {
console.log(fields[i]);
}
CONTINUE;
</javascript>
<target url="https://api.predic8.de"/>
</api>
The CONTINUE
keyword ensures that the request continues processing and is forwarded to the target URL.
When a JavaScript script returns a Response
object as the last line of code, the request flow is interrupted, and the response is sent back to the client. This allows for creating custom responses dynamically.
The following example generates a JSON response and sends it directly to the client:
<api port="2000">
<javascript>
var body = JSON.stringify({
foo: 7,
bar: 42
});
Response.ok(body).contentType("application/json").build();
</javascript>
</api>
For more details about using JavaScript with Membrane, check the JavaScript Plugin documentation.
You can modify HTTP headers in requests or responses using Membrane's setHeader
and headerFilter
feature. This is particularly useful for enabling CORS or adding custom headers.
The following configuration adds CORS
headers to the responses received from the backend:
<api port="2000">
<response>
<setHeader name="Access-Control-Allow-Origin" value="*" />
<setHeader name="Access-Control-Allow-Methods" value="GET" />
</response>
<target url="https://api.predic8.de" />
</api>
Membrane allows dynamic extraction of values from the JSON body of a request or response and uses them to set HTTP headers.
The following example extracts the id
and name
fields from a JSON body and sets them as custom headers in the response:
<api port="2000">
<response>
<!-- Extract the "id" field from the JSON body and set it as the X-Product-Id header -->
<setHeader name="X-Product-Id" value="${jsonPath('$.id')}"/>
<!-- Extract the "name" field from the JSON body and set it as the X-Product-Name header -->
<setHeader name="X-Product-Name" value="${jsonPath('$.name')}"/>
</response>
<target url="https://api.predic8.de" />
</api>
You can easily remove specific HTTP headers from requests or responses (or both) using the headerFilter
element. This is useful for cleaning up headers or meeting security requirements.
The following configuration demonstrates how to manage headers:
<api port="2000">
<response>
<headerFilter>
<include>X-XSS-Protection</include> <!-- Keep the X-XSS-Protection header -->
<exclude>X-.*</exclude> <!-- Remove all headers starting with "X-" except those explicitly included -->
</headerFilter>
</response>
<target url="https://www.predic8.de"/>
</api>
<include>
: Specifies headers to retain.<exclude>
: Defines headers to remove. Wildcards can be used for patterns.
The first matching rule will be acted upon by the filter.
<api port="2000" method="GET">
<request>
<template contentType="application/json" pretty="yes">
{ "answer": ${params.answer} }
</template>
</request>
<return/>
</api>
Call this API with http://localhost:2000?answer=42
. Replace <return.../>
with your <target url="backend-server"/>
.
Call the following APIs with this request:
curl -d '{"city":"Berlin"}' -H "Content-Type: application/json" "http://localhost:2000"
This template will transform the JSON input into plain text:
<api port="2000" method="POST">
<request>
<template contentType="text/plain">
City: ${json.city}
</template>
</request>
<return statusCode="200"/>
</api>
...into JSON:
<template contentType="application/json" pretty="true">
{
"destination": "${json.city}"
}
</template>
...and into XML:
<template contentType="application/xml">
<![CDATA[
<places>
<place>${json.city}</place>
</places>
]]>
</template>
Using the xpathExtractor
you can extract values from XML request or response bodies and store it in properties. The properties are then available as variables in the template
plugin.
<api port="2000">
<request>
<xpathExtractor>
<property name="fn" xpath="person/@firstname"/>
</xpathExtractor>
<template>Buenas Noches, ${fn}sito!</template>
</request>
<return statusCode="200" contentType="text/plain"/>
</api>
See: message-transformation examples
Use the Javascript or Groovy plugin for more powerful yet simple transformations.
<api port="2000">
<request>
<javascript>
({ id:7, place: json.city })
</javascript>
</request>
<return contentType="application/json"/>
</api>
Call the API with this curl command:
curl -d '{"city":"Berlin"}' -H "Content-Type: application/json" "http://localhost:2000"
This script transforms the input and adds some calculations.
<api port="2000">
<request>
<javascript>
function convertDate(d) {
return d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0"+d.getDate()).slice(-2);
}
({
id: json.id,
date: convertDate(new Date(json.date)),
client: json.customer,
total: json.items.map(i => i.quantity * i.price).reduce((a,b) => a+b),
positions: json.items.map(i => ({
pieces: i.quantity,
price: i.price,
article: i.description
}))
})
</javascript>
</request>
<return/>
</api>
See examples/javascript for a detailed explanation. The same transformation can also be realized with Groovy
You can beautify a JSON or XML using the <beautifier/>
plugin.
<api port="2000">
<template contentType="application/xml"><![CDATA[
<foo><bar>baz</bar></foo>
]]></template>
<beautifier/>
<return statusCode="200"/>
</api>
Returns:
<foo>
<bar>baz</bar>
</foo>
Replace 5XX
error messages from a backend:
<api port="2000">
<response>
<if test="statusCode matches '5\d\d'" language="SpEL">
<static>
Error!
</static>
</if>
</response>
<return/>
</api>
Dynamically manipulate and monitor messages with Groovy:
<api port="2000">
<response>
<groovy>
header.add("X-Groovy", "Hello from Groovy!")
println("Status: ${message.statusCode}")
CONTINUE
</groovy>
</response>
<target url="https://api.predic8.de"/>
</api>
Create a response with Javascript:
<api port="2000">
<response>
<javascript>
var body = JSON.stringify({
foo: 7,
bar: 42
});
Response.ok(body).contentType("application/json").build();
</javascript>
</response>
<return/> <!-- Do not forward, return immediately -->
</api>
Also try the Groovy and Javascript example.
Membrane offers lots of security features to protect backend servers.
You can define APIs keys directly in your configuration, and Membrane will validate incoming requests against them.
The following configuration secures the Fruitshop API
by validating a key provided as a query parameter:
<api port="2000">
<apiKey>
<!-- Define valid API keys -->
<keys>
<secret value="abc123" />
<secret value="secret" />
<secret value="Paris2025" />
</keys>
<!-- Extract the API key from the query parameter -->
<queryParamExtractor paramName="api-key" />
</apiKey>
<target url="https://api.predic8.de" />
</api>
To test the configuration, pass a valid API key in the query string:
curl "http://localhost:2000/shop/v2/products/4?api-key=abc123"
If the key is invalid or missing, Membrane denies access and returns an error response (HTTP 401 Unauthorized).
For more complex setups, such as API keys in the HTTP header, role-based access control (RBAC) or file-based key storage, see the API Key Plugin Examples.
The API below only allows requests with valid tokens from Microsoft's Azure AD. You can also use the JWT validator for other identity providers.
<api port="8080">
<jwtAuth expectedAud="api://2axxxx16-xxxx-xxxx-xxxx-faxxxxxxxxf0">
<jwks jwksUris="https://login.microsoftonline.com/common/discovery/keys"/>
</jwtAuth>
<target url="https://your-backend"/>
</api>
Use OAuth2/OpenID to secure endpoints against Google, Azure AD, GitHub, Keycloak or Membrane authentication servers.
<api port="2001">
<oauth2Resource>
<membrane src="https://accounts.google.com"
clientId="INSERT_CLIENT_ID"
clientSecret="INSERT_CLIENT_SECRET"
scope="email profile"
subject="sub"/>
</oauth2Resource>
<groovy>
// Get email from OAuth2 and forward it to the backend
def oauth2 = exc.properties.oauth2
header.setValue('X-EMAIL',oauth2.userinfo.email)
CONTINUE
</groovy>
<target url="https://backend"/>
</api>
Try the tutorial OAuth2 with external OpenID Providers
Operate your own identity provider:
<api port="2000">
<oauth2authserver location="logindialog" issuer="http://localhost:2000" consentFile="consentFile.json">
<staticUserDataProvider>
<user username="john" password="password" email="[email protected]"/>
</staticUserDataProvider>
<staticClientList>
<client clientId="abc" clientSecret="def" callbackUrl="http://localhost:2001/oauth2callback"/>
</staticClientList>
<bearerToken/>
<claims value="aud email iss sub username">
<scope id="username" claims="username"/>
<scope id="profile" claims="username email password"/>
</claims>
</oauth2authserver>
</api>
See the OAuth2 Authorization Server example.
<api port="2000">
<basicAuthentication>
<user name="bob" password="secret"/>
<user name="alice" password="secret"/>
</basicAuthentication>
<target host="localhost" port="8080"/>
</api>
Route to SSL/TLS secured endpoints:
<api port="8080">
<target url="https://api.predic8.de"/> <!-- Note the s in https! -->
</api>
Secure endpoints with SSL/TLS:
<api port="443">
<ssl>
<keystore location="membrane.p12" password="secret" keyPassword="secret" />
<truststore location="membrane.p12" password="secret" />
</ssl>
<target host="localhost" port="8080" />
</api>
Membrane offers protection mechanisms to secure your APIs from common risks associated with XML and JSON payloads.
The xmlProtection
plugin inspects incoming XML requests and mitigates risks such as:
- External entity references (XXE attacks).
- Excessively large element names.
- High numbers of attributes or deeply nested structures.
Example:
<api port="2000">
<xmlProtection />
<target url="https://api.predic8.de"/>
</api>
The jsonProtection
plugin safeguards APIs from JSON-based vulnerabilities by setting limits on:
- Depth: Prevents overly nested JSON structures.
- Key Length: Restricts excessively long keys.
- Object Size: Maximum number of fields in aJSON object.
- String Length: Controls maximum length of string values.
- ...
Example:
<api port="2000">
<jsonProtection maxDepth="5" maxKeyLength="100" maxStringLength="100000"/>
<target url="https://api.predic8.de"/>
</api>
See JSON Protection.
Limit the number of incoming requests:
<api port="2000">
<rateLimiter requestLimit="3" requestLimitDuration="PT30S"/>
<target host="localhost" port="8080"/>
</api>
Distribute workload to multiple backend nodes. See the example
<api port="8080">
<balancer name="balancer">
<clusters>
<cluster name="Default">
<node host="my.backend-1" port="4000"/>
<node host="my.backend-2" port="4000"/>
<node host="my.backend-3" port="4000"/>
</cluster>
</clusters>
</balancer>
</api>
Route and intercept WebSocket traffic:
<api port="2000">
<webSocket url="http://my.websocket.server:1234">
<wsLog/>
</webSocket>
<target port="8080" host="localhost"/>
</api>
See documentation
Integrate legacy services.
SOAP proxies configure themselves by analysing WSDL:
<soapProxy wsdl="http://thomas-bayer.com/axis2/services/BLZService?wsdl"/>
The validator checks SOAP messages against a WSDL document including referenced XSD schemas.
<soapProxy wsdl="http://thomas-bayer.com/axis2/services/BLZService?wsdl">
<validator/>
</soapProxy>
Log data about requests and responses to a file or database as CSV or JSON file.
<api port="2000">
<log/> <!-- Logs to the console -->
<statisticsCSV file="./log.csv"/> <!-- Logs fine-grained CSV -->
<target url="https://api.predic8.de"/>
</api>
Membrane supports seamless monitoring with Prometheus and Grafana, enabling visibility into API performance and system metrics.
Add an API with the prometheus
plugin to your proxies.xml
file. This will expose metrics at the specified endpoint:
<api port="2000">
<path>/metrics</path>
<prometheus />
</api>
Then you can query the metrics by navigating to:
http://localhost:2000/metrics.
This endpoint provides Prometheus-compatible metrics, which you can scrape using a Prometheus server.
For a complete configuration example with Prometheus and Grafana, refer to:
Prometheus Example.
Add an API with the prometheus
plugin at the top of the proxies.xml
file.
<api port="2000">
<path>/metrics</path>
<prometheus />
</api>
Then query the metrics endpoint by opening http://localhost:2000/metrics. Now you can setup a prometheus to scrape that endpoint. For a complete example with prometheus and Grafana have a look at examples/prometheus.
Membrane supports integration with OpenTelemetry traces using the openTelemetry
plugin and the W3C
propagation standard. This enables detailed tracing of requests across Membrane and backend services.
This diagram illustrates Membrane in a tracing setup with a backend service and a database connection.
The configuration below shows Membrane forwarding requests to a backend, while exporting OpenTelemetry data to a collector:
<api port="2000">
<openTelemetry sampleRate="1.0">
<otlpExporter host="localhost" port="4317"/>
</openTelemetry>
<target host="localhost" port="3000"/>
</api>
For a working example and detailed setup, see the OpenTelemetry Example.