# The API Gateway Handbook - Code Snippets
In the API Gateway book:
https://www.membrane-api.io/api-gateway-ebook.html
contains many configuration examples, API requests, and URLs. In the PDF version, these snippets can be difficult to copy accurately.
To make things easier, this document collects the most relevant examples so you can copy and paste them directly into Membrane configurations or use them in your terminal.
# 24 Membrane API Gateway
## 24.1 Standalone Java Installation
curl http://localhost:2000/fact
curl http://localhost:2000/shop/v2
## 24.2 Docker Installation
docker run -it -p 2000:2000 predic8/membrane
### On macOS/Linux:
docker run -it -p 2000:2000 -v "$(pwd)/conf/apis.yaml:/opt/membrane/conf/apis.yaml" predic8/membrane
### On Windows (PowerShell or CMD):
docker run -it -p 2000:2000 -v ${PWD}\conf\apis.yaml:/opt/membrane/conf/apis.yaml predic8/membrane
# 25 API Configuration
# yaml-language-server: $schema=https://www.membrane-api.io/v7.1.1.json
api:
port: 2000
target:
url: https://api.predic8.de
# 26 Routing Traffic
api:
port: 2000
method: GET
path:
uri: /shop/v2
target:
url: https://api.predic8.de
curl http://localhost:2000/shop/v2/
## 26.1 Order of API Matching
api:
name: API 1
port: 2000
method: GET
flow:
- log:
message: "Method: ${method}"
target:
url: https://api.predic8.de
---
api:
name: API 2
port: 2000
method: POST
flow:
- log:
message: "Method: ${method}"
target:
url: https://api.predic8.de
### Case 1: Sending a GET request
curl http://localhost:2000/shop/v2/products
### Case 2: Sending a POST request
POST /shop/v2/products
Host: localhost:2000
Content-Type: application/json
{
"name": "Biscuits", "price": 1.99
}
curl -X POST -H 'Content-Type: application/json' -d '{"name": "Biscuits", "price": 1.99}' http://localhost:2000/shop/v2/products
### Case 3: Sending a request with an unsupported method
curl -X PUT http://localhost:2000
## Content-Based Routing Pattern
api:
port: 2000
test: json['name'] == 'Lolly'
flow:
- static:
src: "Sweets this way!"
- return:
status: 200
POST /shop/v2/products
Host: localhost:2000
Content-Type: application/json
{
"name": "Lolly"
}
## Temporarily Blocking Access
api:
port: 2000
path:
uri: /products
flow:
- request:
- static:
contentType: application/json
src: |
{
"message": "Temporarily unavailable"
}
- return:
status: 503
## 26.4 URI Templates
api:
port: 2000
path:
uri: /customers/{cid}/orders/{oid}
flow:
- log:
message: Order ${pathParam.oid} for ${pathParam.cid}
- return:
status: 200
---
api:
port: 2000
path:
uri: /customers/{cid}
flow:
- log:
message: 'Customer: ${pathParam.cid}'
- return:
status: 200
curl http://localhost:2000/customers/88
curl http://localhost:2000/customers/88/orders/3
## 27.1 Exchange Properties
api:
port: 2000
flow:
- setProperty:
name: color
value: RED
- setHeader:
name: X-Size
value: XL
- log:
message: |
Header ${header['X-Size']} Prop ${property.color}
target:
url: http://localhost:2001
---
api:
port: 2001
flow:
- request:
- log:
message: |
Header ${header['X-Size']} Prop ${property.color}
- return:
status: 200
# 28 OpenAPI
## 28.1 Using OpenAPI for Gateway Configuration
api:
port: 2000
openapi:
- location: openapi/dlp-v1.0.0.oas.yml
http://localhost:2000/api-docs
### Loading Multiple APIs
api:
port: 2000
openapi:
- location: openapi/dlp-v1.0.0.oas.yml
- location: openapi/fruitshop-v2-2-0.oas.yml
## 28.2 Rewriting of OpenAPI Addresses
openapi:
- location: openapi/fruitshop-v2-2-0.oas.yml
rewrite:
host: api.predic8.de
protocol: https
port: 443
basePath: /store
## 28.3 OpenAPI Message Validation
api:
port: 2000
openapi:
- location: openapi/fruitshop-v2-2-0.oas.yml
validateRequests: true
## 28.4 APIOps with OpenAPI
FROM predic8/membrane
USER root
RUN apt-get update && \
apt-get install -y wget && \
wget "https://github.com/predic8/rfq-api/releases/latest/download/rfq-api-v1.oas.yml" \
-O /opt/membrane/conf/openapi/rfq.oas.yml
USER membrane
COPY apis.yaml /opt/membrane/conf
EXPOSE 2000
ENTRYPOINT ["/opt/membrane/membrane.sh"]
docker build -t membrane:1 .
docker run -it -p 2000:2000 membrane:1
http://localhost:2000/api-docs
# 29 Transformation and Message Manipulation
## 29.1 Manipulating HTTP Headers
api:
port: 2000
flow:
- response:
- setHeader:
name: X-Foo
value: '42'
- return:
status: 200
curl -i http://localhost:2000
## 29.2 Passing HTTP Headers to Backends
### Gateway API: Adding a Token
api:
port: 2000
flow:
- request:
- setHeader:
name: X-Token
value: abc123
target:
url: http://localhost:3000
### Backend: Logging the Token
api:
port: 3000
flow:
- request:
- log:
message: 'Got: ${header["X-Token"]}'
- return:
status: 200
### Testing the Configuration
curl http://localhost:2000
## 29.3 Computing Header and Property Values
api:
port: 2000
flow:
- request:
- setHeader:
name: X-Prod-Env
value: ${header['host'] matches 'prod.*'}
language: spel
- setHeader:
name: X-Date
value: ${java.time.LocalDate.now()}
language: groovy
- setHeader:
name: X-Address
value: ${$.address.zip} ${$.address.city}
language: jsonpath
- log:
message: "Headers:\n ${header}"
- return:
status: 200
POST http://localhost:2000
Content-Type: application/json
Host: prod.example.com
{
"address": {
"city": "Boston",
"zip": "02108"
}
}
## 29.4 Removing HTTP Headers
api:
port: 2000
flow:
- response:
- headerFilter:
- include: Content.*
- include: last-modified
- exclude: .*
target:
url: https://www.wikipedia.org
curl -sS -D - https://www.wikipedia.org -o /dev/null
curl -sS -D - http://localhost:2000 -o /dev/null
## 29.6 Make It Nice
api:
port: 2000
flow:
- request:
- beautifier: {}
- return:
status: 200
POST http://localhost:2000
Content-Type: application/json
{ "a":{ "b":{"c":7}}}
## 29.7 Transformation Between JSON and XML
### Generic JSON to XML Transformation
api:
port: 2000
flow:
- response:
- json2Xml:
root: fruit
target:
url: https://api.predic8.de
curl http://localhost:2000/shop/v2/products/7
### XML2JSON Transformation
api:
port: 2000
flow:
- request:
- xml2Json: {}
target:
url: https://api.predic8.de
POST http://localhost:2000/shop/v2/products
Content-Type: text/xml
Citron
0.39
## 29.8 Rendering Dynamic Content
api:
port: 2000
flow:
- request:
- setBody:
language: xpath
contentType: application/json
value: |
{
"name": ${/add/name},
"price": ${/add/price}
}
target:
url: https://api.predic8.de
### Transforming a GET into a POST
api:
port: 2000
flow:
- request:
- template:
contentType: application/json
src: |
{
"name": ${param.name[0]},
"price": ${param.price[0].toFloat()}
}
target:
method: POST
url: https://api.predic8.de/shop/v2/products
### Transforming a POST into a GET
api:
port: 2000
target:
method: GET
url: https://api.predic8.de/shop/v2/products/${$.id}
language: jsonpath
Host: localhost:2000
Content-Type: application/json
{
"id": 13
}
### Processing Lists and Map Structures
api:
port: 2000
flow:
- request:
- template:
contentType: application/json
pretty: true
src: |
{
<% header.eachWithIndex { e, i -> %>
<% if (i > 0) { %>,<% } %>
<%= e.key %>: <%= e.value %>
<% } %>
}
- return:
status: 200
# 30 Control Flow
## 30.1 Conditions
api:
port: 2000
flow:
- request:
- if:
test: header['X-Api-Key'] != 'sesame'
flow:
- return:
status: 401
- return:
status: 200
curl -H "X-Api-Key: sesame" localhost:2000 -v
### Choose and Case
api:
port: 2000
flow:
- response:
- choose:
- case:
test: statusCode == 401
flow:
- static:
contentType: application/problem+json
src: |
{
"type": "/problem/auth",
"title": "Authentication Error"
}
- case:
test: statusCode >= 400 and statusCode < 500
flow:
- static:
contentType: application/problem+json
src: |
{
"type": "/problem/client",
"title": "Client Error"
}
- otherwise:
- log:
message: No error
target:
url: http://localhost:3000
# 31 API Orchestration
## 31.1 Aggregating Backend APIs
https://openlibrary.org/books/OL29474405M.json
https://openlibrary.org/authors/OL272947A.json
### Composing Two Backend Calls
api:
port: 2000
path:
uri: /books/{id}
flow:
- request:
- call:
url: https://openlibrary.org/books/${pathParam.id}.json
- setProperty:
name: title
value: ${$.title}
language: jsonpath
- call:
url: https://openlibrary.org${$.authors[0].key}.json
language: jsonpath
- template:
contentType: application/json
src: |
{
"title": ${property.title},
"author": ${jsonPath('$.name')}
}
- return:
status: 200
curl http://localhost:2000/books/OL29474405M
curl http://localhost:2000/books/OL26333978M
## 31.2 Authentication for Backend Access
### 1. Target API (Backend Simulation at port 3001)
api:
port: 3001
name: Target API
flow:
- request:
- log:
message: "Session: ${cookie.SESSION}"
- if:
test: cookie.SESSION != 'akj34'
flow:
- return:
status: 401
- static:
src: Success!
- return:
status: 200
### 2. Authentication Service (port 3000)
api:
port: 3000
path:
uri: /login
flow:
- response:
- setCookies:
- name: SESSION
value: akj34
- log:
message: Login successful. Issue cookie.
- return:
status: 200
### 3. Orchestration API (port 2000)
api:
port: 2000
flow:
- request:
- log:
message: Authenticating via login API
- call:
url: http://localhost:3000/login
- log:
message: "Got Cookie: ${header['Set-Cookie']}"
- setHeader:
name: Cookie
value: ${header['Set-Cookie'].split(';')[0]}
- headerFilter:
- exclude: Set-Cookie
- log:
message: "Forwarding Cookie: ${header['Cookie']}"
target:
url: http://localhost:3001
## 31.3 Processing RESTful List Resources
### Implementation
api:
port: 2000
flow:
- request:
- call:
url: https://api.predic8.de/shop/v2/products?limit=7
- for:
in: $.products
language: jsonpath
flow:
- log:
message: 'Getting price for: ${it["name"]}'
- call:
url: https://api.predic8.de${it['self_link']}
- groovy:
src: |
property.res = property.res ?: []
property.res.add(
[ "name": it['name'],
"price": jsonPath('$.price')
])
- template:
contentType: application/json
pretty: true
src: |
{
"products": <%= property.res %>
}
- return:
status: 200
# 32 Secure Data in Transit with TLS
## 32.1 Reaching Backends over TLS
### TLS to a Backend With Public Certificate
api:
port: 2000
target:
url: https://api.predic8.de
## 32.2 Termination of TLS Connections
openssl x509 -in membrane.pem -text
### Configuring TLS Termination
api:
port: 443
ssl:
key:
private:
location: membrane-key.pem
certificates:
- location: membrane.pem
flow:
- request:
- log: {}
target:
url: https://api.predic8.de
### Testing the TLS Setup
curl -v -k https://localhost:443
### Forwarding TLS Connections Without Decryption
sslProxy:
port: 443
host: secret.example.com
target:
host: secret.example.com
port: 443
## 32.3 Debugging TLS Connectivity
api:
port: 443
ssl:
showSSLExceptions: true
# 33 Access Control Lists
global:
- accessControl:
- deny: 192.168.3.8
- deny: 192.168.10.0/24
- allow: 192.168.0.0/16
- allow: '.+\.predic8\.de$'
api:
port: 9000
flow:
- accessControl:
- allow: 127.0.0.0/8
- allow: localhost
- adminConsole: {}
# 34 Content Protection
## 34.1 JSON Protection
api:
port: 2000
flow:
- jsonProtection: {}
- return:
status: 200
POST http://localhost:2000
Content-Type: application/json
{
"price": 10,
"price": -1
}
## 34.2 XML Protection
global:
- xmlProtection: {}
POST /
Host: localhost:2000
Content-Type: text/xml
]>
Hello
## 34.3 GraphQL Protection
global:
- graphQLProtection:
maxRecursion: 1
maxMutations: 10
maxDepth: 3
disallow:
- mutation: updateCategory
- introspection: {}
# 35 Basic Authentication
### Setting up Basic Authentication
api:
port: 2000
flow:
- basicAuthentication:
users:
- username: alice
password: "qwertz"
- username: carol
password: "$6$k3jia$NlCL.JTXitlwZ4i672In7c8M..."
- static:
src: You're in!
- return:
status: 200
curl -u alice:qwertz http://localhost:2000
curl -u carol:abc123 http://localhost:2000
openssl passwd -6 -salt k3jia "yourPassword"
### How to Generate a .htpasswd File
htpasswd -c .htpasswd alice
htpasswd .htpasswd bob
# 36 API Keys
global:
- apiKey:
stores:
- simple:
- value: aed8bcc4-7c83-44d5-8789-21e4024ac873
- value: 61e5a2d8-21aa-4d0b-bdae-76e60ee52803
extractors:
- header: X-Api-Key
GET http://localhost:2000
X-Api-Key: 61e5a2d8-21aa-4d0b-bdae-76e60ee52803
### 1. Using HTTP header
GET / HTTP/1.1
Host: localhost:2000
X-API-KEY: aed8bcc4-7c83-44d5-8789-21e4024ac873
### 2. Using query string
GET /products?api-key=aed8bcc4-7c83-44d5-8789-21e4024ac873
Host: localhost:2000
### 3. Using JSON payload
POST / HTTP/1.1
Host: localhost:2000
Content-Type: application/json
{
"api-key": "61e5a2d8-21aa-4d0b-bdae-76e60ee52803"
}
## 36.1 Storing API Keys
### Storing API Keys in a File
3141
key_321_abc: admin
7a26cae9-ed29-40b3-bc99-5b1914bb8498: read, write
components:
api-key-store:
apiKeyFileStore:
location: keys.txt
### Storing API Keys in a Database
components:
dataSource:
bean:
class: org.apache.commons.dbcp2.BasicDataSource
properties:
- name: driverClassName
value: org.postgresql.Driver
- name: url
value: jdbc:postgresql://localhost:5432/postgres
- name: username
value: postgres
- name: password
value: secret
---
global:
- apiKey:
stores:
- databaseApiKeyStore:
datasource: '#/components/dataSource'
keyTable: key
scopeTable: scope
### MongoDB API Key Store
api:
port: 2000
flow:
- apiKey:
stores:
- mongoDBApiKeyStore:
connection: mongodb://localhost:27017/
database: apiKeyDB
collection: apikey
target:
url: https://api.predic8.de
mongosh --eval "use('apiKeyDB'); db.apikey.insertMany([
{ id: '345%FSe3', scopes: ['read', 'write'] },
{ id: '3c7f6c34', scopes: ['read'] },
{ id: '343265FA', scopes: ['read', 'admin'] },
{ id: 'flower2025', scopes: ['read', 'write'] }]);"
## 36.2 Role-based Access Control (RBAC)
api:
port: 2000
flow:
- apiKey:
stores:
- apiKeyFileStore:
location: api-keys.txt
extractors:
- header: X-Api-Key
- request:
- setHeader:
name: X-Scopes
value: ${scopes()}
target:
url: http://localhost:3000
api:
port: 3000
path:
uri: /admin
flow:
- request:
- if:
test: not header['X-Scopes'].contains('admin')
flow:
- static:
src: Only admins!
- return:
status: 403
- static:
src: You're in!
- return:
status: 200
# 37 JSON Web Tokens
## 37.1 Issuing JWTs
### Step 1: Generate a Private Key
On Windows:
membrane.cmd generate-jwk -o demo.jwk
On Linux:
membrane.sh generate-jwk -o demo.jwk
### Step 2: Configure Membrane to Issue Tokens
api:
port: 2000
name: Token Server
path:
uri: /token
flow:
- request:
- template:
contentType: application/json
src: |
{
"sub": "user@predic8.de",
"aud": "order"
}
- jwtSign:
jwk:
location: demo.jwk
- return: {}
curl http://localhost:2000/token
## 37.2 Protecting the Token Generation Process
### Requiring an API Key Before Issuing a Token
api:
port: 2000
name: Token Server
path:
uri: /token
flow:
- apiKey:
stores:
- apiKeyFileStore:
location: demo-keys.txt
extractors:
- header: X-Api-Key
- request:
- template:
contentType: application/json
src: |
{
"sub": "api-user",
"aud": "order",
"scope": ${scopes()}
}
- jwtSign:
jwk:
location: demo.jwk
- return:
status: 200
key_321_abc: admin
3141: finance
123456789: finance, accounting
7a26cae9-ed29-40b3-bc99-5b1914bb8498: read, write
## 37.3 Verifying JWTs
### Step 1: Set Up a Demo API
api:
port: 3000
name: Demo API
flow:
- template:
src: |
{
"content": "Protected content!"
}
- return:
status: 200
### Step 2: Protect the API with Membrane
api:
port: 4000
name: Secured Access to Demo API
flow:
- jwtAuth:
expectedAud: order
jwks:
- location: demo.jwk
target:
url: http://localhost:3000
curl -H "Authorization: Bearer <" localhost:4000
# 38 OAuth2 and OpenID Connect
## 38.1 Token Verification
https://login.microsoftonline.com/common/discovery/keys
# 39 Legacy Integration of SOAP Web Services
## 39.1 Sample Web Services Mock
api:
port: 2000
path:
uri: /city-service
flow:
- sampleSoapService: {}
http://localhost:2000/city-service?wsdl
POST /city-service
Host: localhost:2000
Content-Type: text/xml
Bonn
## 39.2 Mocking a Web Service
api:
port: 2000
flow:
- response:
- static:
pretty: true
contentType: text/xml
src: |
England
8980000
- return:
status: 200
### Using the SoapBody Template
api:
port: 2000
flow:
- response:
- soapBody:
version: '1.2'
pretty: true
src: |
England
8980000
- return:
status: 200
HTTP/1.1 200 OK
Content-Type: application/soap+xml
England
8980000
## 39.3 Exposing SOAP Web Services as REST APIs
### REST GET to SOAP
api:
port: 2000
method: GET
path:
uri: /cities/{city}
flow:
- request:
- soapBody:
src: |
${pathParam.city}
- setHeader:
name: SOAPAction
value: https://predic8.de/cities/get
- response:
- setBody:
language: xpath
contentType: application/json
value: |
{
"country": ${//country},
"population": ${//population}
}
target:
method: POST
url: https://www.predic8.de/city-service
### Transforming JSON Lists into XML
{
"fruits": [
{ "name": "Apricot","price": 3.87 },
{ "name": "Date", "price": 2.35 }
]
}
- soapBody:
pretty: true
src: |
<% jsonPath('$.fruits').each { it -> %>
${it.name}
${it.price}
<% } %>
### Transforming XML Lists to JSON
Apricot
3.87
Date
2.35
components:
ns:
xmlConfig:
namespaces:
- prefix: f
uri: https://predic8.de/fruits
- template:
pretty: true
contentType: application/json
src: |
<%
def fruits = xpath('//f:fruit')
def total = fruits.getLength()
%>
{ "fruits": [
<% for (int i = 0; i < total; i++) {
def fruit = fruits.item(i)
%>
{
"name": <%=xpath('string(./f:name)', fruit)%>,
"price": <%=xpath('number(./f:price)',fruit)%>
}<%= (i < total - 1) ? "," : "" %>
<% } %>
]
}
### Using Groovy for Message Transformation
- groovy:
src: |
import groovy.xml.XmlSlurper
import groovy.json.JsonOutput
def xml = body.toString()
def root = new XmlSlurper(false, true).parseText(xml)
root.declareNamespace(
s11: "http://schemas.xmlsoap.org/soap/envelope/",
f: "https://predic8.de/fruits"
)
def fruits = root.'s11:Body'
.'f:getFruitsResponse'
.'f:fruit'.collect { n ->
[
name : n.'f:name'.text(),
price: new BigDecimal(n.'f:price'.text())
]
}
def payload = JsonOutput.toJson([fruits: fruits])
message.setBodyContent(payload.getBytes())
api:
port: 2000
path:
uri: /products
flow:
- request:
- groovy:
location: json-to-soap-request.groovy
- response:
- if:
test: //*[local-name()='Fault']
language: xpath
flow:
- groovy:
location: soap-fault-to-json.groovy
else:
- groovy:
location: soap-response-to-json.groovy
target:
url: http://localhost:2000/product-service
### Routing SOAP Requests
api:
port: 2000
method: POST
path:
uri: /products
components:
ns:
xmlConfig:
namespaces:
- prefix: ps
uri: https://predic8.de/products
---
api:
port: 2000
test: //ps:getProducts
language: xpath
target:
url: http://localhost:3000
---
api:
port: 2000
test: //ps:createProduct
language: xpath
target:
url: http://localhost:3001
### SOAPAction Routing
api:
port: 2000
test: header.SOAPAction == 'https://www.predic8.de/get-city'
target:
url: http://localhost:3000
---
api:
port: 2000
test: header.SOAPAction == 'https://www.predic8.de/create-city'
target:
url: http://localhost:3001
## 39.5 Best Practices
### Prompt: For a SOAP to REST Transformation
Create a Membrane API Gateway configuration that exposes the REST endpoint POST /shop/v2/products as SOAP Web Service. Map SOAP version 1.1 request bodies to JSON and map JSON responses back to SOAP, including status codes and Content-Type headers. Create a lean solution. Do not include security, logging, health checks, or other unneeded features.
Deliverables:
- A Membrane YAML configuration (v7) implementing the mapping. Request and response templates in Groovy for the mapping.
- A WSDL with the SOAP operation, including XSD, messages, portType, binding and service.
- Example curl commands and SOAP requests for testing.
- Document the mapping in comments in the YAML and WSDL.
- Error handling: Map REST status code to SOAP Faults.
- Guide explaining how to set up the solution
Assumptions:
- REST request contains name and price.
- If any of these assumptions are incorrect, infer the most reasonable mapping.
# 40 Proxying SOAP
### A Proxy for SOAP Services
soapProxy:
port: 2000
wsdl: https://www.predic8.de/city-service?wsdl
http://localhost:2000/city-service?wsdl
### Rewriting of WSDL URLs
http://localhost:2000/city-service?wsdl
soapProxy:
path:
uri: /my-service
port: 2000
wsdl: https://www.predic8.de/city-service?wsdl
flow:
- wsdlRewriter:
host: my.host.example.com
protocol: https
port: 443
path: /my-service
### WSDL Validation
soapProxy:
port: 2000
wsdl: https://www.predic8.de/city-service?wsdl
flow:
- validator: {}
### How a soapProxy works
soapProxy:
port: 2000
wsdl: https://www.predic8.de/city-service?wsdl
flow:
- log:
message: "Path: ${path}"
- rateLimiter:
requestLimit: 1000
requestLimitDuration: PT1H
# 41 Operation
## 41.1 Admin Console
### Activating the Console
api:
port: 9000
flow:
- adminConsole: {}
### Securing the Console
api:
port: 9000
flow:
- basicAuthentication:
users:
- username: admin
password: ba9b-fcd63
- adminConsole:
readOnly: true
## 41.2 Monitoring with Prometheus and Grafana
### Prometheus
api:
port: 2000
path:
uri: /metrics
flow:
- prometheus: {}
curl localhost:2000/metrics
## 41.4 API Tracing with OpenTelemetry
### Setting up OpenTelemetry
global:
- openTelemetry:
sampleRate: 1.0
otlpExporter:
host: localhost
transport: grpc
## 41.5 Message Logging
### Logging What Matters
- log:
message: |
Got: ${body}
Header: ${header}
Trace: ${header['Trace-Id']}
- log:
message: "Fruit: ${$.name}"
language: jsonpath
- log:
message: "Fruit: ${//name}"
language: xpath
## 41.6 Message Stores
### In-Memory Store
components:
store:
limitedMemoryExchangeStore:
maxSize: 222222222
### File Message Store
components:
my-store:
fileExchangeStore:
dir: exchanges
maxDays: 7
### MongoDB Message Store
components:
my-store:
mongoDBExchangeStore:
connection: mongodb://localhost:27017/
database: membrane
collection: exchanges