Path variables in Apache Camel rest route - rest

I would like to use path variables in my REST route URL in the following way:
rest().post("/companies/{companyId}/branches/branchId={branchId}");
The companyId can be properly retrieved as header, however, "branchId={branchId}" is treated as literal string. Therefore request to:
/companies/100/branches/branchId=200 - will return 404 not found.
but
/companies/100/branches/branchId={branchId} - will enter the route.
I would like to use branchId as header, in the same way as companyId, without having to change the structure of the URL.

Every path or query parameter can be received through the headers in Apache Camel. You can retrieve the headers with using the simple expression;
${header.your-header}
So you can retrieve the parameters as below;
parameter
simple expression
companyId
${header.companyId}
branchId
${header.branchId}
Here is a working Route example that I've made for you;
package com.bzdgn.camelso.route;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.model.rest.RestBindingMode;
public class RestEndpointRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
rest()
.post("/companies/{companyId}/branches/{branchId}")
.consumes("text/plain")
.produces("text/plain")
.to("direct:handle-post");
from("direct:handle-post")
.id("post-route")
.log("Received Body: ${body}")
.log("Company Id: ${header.companyId}")
.log("Branch Id: ${header.branchId}")
.setBody(simple("CompanyId = ${header.companyId} \nBranchId = ${header.branchId}"))
.convertBodyTo(String.class);
restConfiguration()
.component("netty-http")
.host("localhost")
.port("8080")
.bindingMode(RestBindingMode.auto);
}
}
The logs will be as below if you send a HTTP Post Request to the designated URL;
2021-05-28 21:13:30 INFO HttpServerBootstrapFactory:53 - BootstrapFactory on port 8080 is using bootstrap configuration: [NettyServerBootstrapConfiguration{protocol='http', host='localhost', port=8080, broadcast=false, sendBufferSize=65536, receiveBufferSize=65536, receiveBufferSizePredictor=0, workerCount=0, bossCount=1, keepAlive=true, tcpNoDelay=true, reuseAddress=true, connectTimeout=10000, backlog=0, serverInitializerFactory=org.apache.camel.component.netty.http.HttpServerInitializerFactory#ae3540e, nettyServerBootstrapFactory=null, options=null, ssl=false, sslHandler=null, sslContextParameters='null', needClientAuth=false, enabledProtocols='TLSv1,TLSv1.1,TLSv1.2, keyStoreFile=null, trustStoreFile=null, keyStoreResource='null', trustStoreResource='null', keyStoreFormat='JKS', securityProvider='SunX509', passphrase='null', bossGroup=null, workerGroup=null, networkInterface='null', reconnect='true', reconnectInterval='10000'}]
2021-05-28 21:13:30 INFO NettyComponent:164 - Creating shared NettyConsumerExecutorGroup with 17 threads
2021-05-28 21:13:31 INFO SingleTCPNettyServerBootstrapFactory:182 - ServerBootstrap binding to localhost:8080
2021-05-28 21:13:33 INFO NettyConsumer:77 - Netty consumer bound to: localhost:8080
2021-05-28 21:13:33 INFO AbstractCamelContext:2983 - Routes startup summary (total:2 started:2)
2021-05-28 21:13:33 INFO AbstractCamelContext:2988 - Started post-route (direct://handle-post)
2021-05-28 21:13:33 INFO AbstractCamelContext:2988 - Started route1 (rest://post:/companies/%7BcompanyId%7D/branches/%7BbranchId%7D)
2021-05-28 21:13:33 INFO AbstractCamelContext:3000 - Apache Camel 3.10.0 (camel-1) started in 3s337ms (build:93ms init:274ms start:2s970ms)
2021-05-28 21:13:35 INFO post-route:166 - Received Body: Example text body here
2021-05-28 21:13:35 INFO post-route:166 - Company Id: my-custom-company
2021-05-28 21:13:35 INFO post-route:166 - Branch Id: my-custom-branch-id
And you can test it via postman like this. The request, and the headers listed in the below images, and do not forget to add "Content Type: text/plain", because that's defined in the route that it will consume and produce "text/plain".

Related

Request-based Sticky Session configuration with Spring Cloud LoadBalancer

I have the following configuration for request-based sticky session using Spring Cloud LoadBalancer
spring:
cloud:
discovery.client.simple.instances:
say-hello:
- instanceId: say-hello1
uri: http://localhost:8080
- instanceId: say-hello2
uri: http://localhost:8081
loadbalancer:
configurations: request-based-sticky-session
sticky-session:
add-service-instance-cookie: true
server.port:9090
the following call:
$ http :9090/hi 'Cookie:sc-lb-instance-id=say-hello1'
should go only to the say-hello1 instance based on the Request-based Sticky Session for LoadBalancer but instead is using round robin load balancing.
What do I miss here?
Here is the source code to try it: https://github.com/altfatterz/client-side-loadbalancing
There are 2 things to consider here:
In the sample, the cookie has to be passed on to the actual load-balanced request, for example like so:
#GetMapping("/hi")
public String hi(#RequestParam(value = "name", defaultValue = "Mary") String name) {
logger.info("Accessing /hi endpoint");
HttpHeaders headers = new HttpHeaders();
headers.set("Cookie", "sc-lb-instance-id=say-hello1");
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<String> greeting = restTemplate.exchange("http://say-hello/greeting", HttpMethod.GET, entity, String.class, new HashMap<>());
return greeting + " " + name;
}
This feature is only supported for WebClient-backed load-balancing. It was not properly documented. I have documented it here.
I have also created a GitHub issue for adding the non-reactive implementation, however, the decision to implement it will be dependant on larger community interest.

Error Parsing HTTP Response Using Apama HTTP Client plugin

I am trying to do an HTTP call to a REST API using Apama HTTP Client plugin. I am able to send a request to the REST Resource, but while parsing the response, I am getting the below error.
WARN [20176] - Failed to parse the event "`{contentType:application/json,sag.type:apamax.httpserversample.HTTPResponse,http:{headers:{contentLength:50,content-type:application/json,content-length:50},statusCode:200,method:POST,path:/rest/POC_422837/WS/provider/apamaTestConn,cookies:{},statusReason:OK}}{body:{status:Hello Apama. How are you doing?}}"
from httpClient due to the error: Unable to parse event apamax.httpserversample.HTTPResponse:
Unable to parse string from the map '{status:Hello Apama. How are you doing?}':
Invalid datatype, could not cast to string`
The YAML Config file looks as below,
connectivityPlugins:
HTTPClientGenericTransport:
libraryName: connectivity-http-client
class: HTTPClient
startChains:
httpClient:
- apama.eventMap
- mapperCodec:
apamax.httpserversample.HTTPRequest:
towardsTransport:
mapFrom:
- metadata.http.path: payload.path
- metadata.requestId: payload.id
- metadata.http.method: payload.method
- payload: payload.data
defaultValue:
- metadata.contentType: application/json
- metadata.sag.type: HelloWorld
apamax.httpserversample.HTTPResponse:
towardsHost:
mapFrom:
- payload.body: payload
- payload.id: metadata.requestId
apamax.httpserversample.HTTPError:
towardsHost:
mapFrom:
- payload.id: metadata.requestId
- payload.code: metadata.http.statusCode
- payload.message: metadata.http.statusReason
- classifierCodec:
rules:
- apamax.httpserversample.HTTPResponse:
- metadata.http.statusCode: 200
- apamax.httpserversample.HTTPError:
- metadata.http.statusCode:
- jsonCodec:
filterOnContentType: true
- stringCodec
- HTTPClientGenericTransport:
host: ${CORRELATOR_HOST}
port: ${CORRELATOR_PORT}
Please help.
I believe the problem is that you map in the config
apamax.httpserversample.HTTPResponse:
towardsHost:
mapFrom:
- payload.body: payload
- payload.id: metadata.requestId
the payload of the response to HTTPResponse.body.
However, as you can see from the warning, the payload is actually a map, so you need to do
- payload.body: payload.status

Deny access to one particular subpath for spring cloud gateway route

We're using Spring Cloud Gateway in front of our backend services. We have a route similar to the following:
routes:
- id: foobar-service
uri: lb://foobar-service
predicates:
- Path=/foobar/**
filters:
- StripPrefix=1
We want to deny access to one particular subpath (e.g. /foobar/baz/**) but leave the rest open. Is it possible to do this using the YAML syntax? Perhaps we need to implement the routes using the Fluent API instead?
routes:
- id: foobar-baz-service
uri: no://op
predicates:
- Path=/foobar/baz/**
filters:
- SetStatus=403
- id: foobar-service
uri: lb://foobar-service
predicates:
- Path=/foobar/**
filters:
- StripPrefix=1
Sharing my experience with the API implementation with a single route.
#Bean
public RouteLocator routes( final RouteLocatorBuilder locatorBuilder ) {
RouteLocatorBuilder.Builder builder = locatorBuilder.routes();
builder.route(p -> p //
.path(getAllowedPaths()) //
.and() //
.not(n -> n.path(getRestrictedPaths()) //
.filters(f -> f //
//filters
.uri(uri)));
return builder.build();
}

can we connect to public api endpoint instead of local host using dredd tool?

I tried to use a public end point(eg:api.openweathermap.org/data/2.5/weather?lat=35&lon=139) instead of the local host while configuring dredd and ran the command to run the tool.But I am not able to connect to the end point through dredd. It is throwing Error:getaddrINFO EAI_AGAIN .
But when I tried to connect to the endpoint using post man .I am able to connect successfully
There is no difference in calling a local or remote endpoint.
Some remote endpoints have some sort of authorization requirements.
This an example of Dredd calling external endpoint:
dredd.yml configuration file fragment
...
blueprint: doc/api.md
# endpoint: 'http://api-srv:5000'
endpoint: https://private-da275-notes69.apiary-mock.com
As you see, the only change is the endpoint on Dredd configuration file (created using Dredd init).
But, as I mention, sometimes you'll need to provide authorization through the header or query string parameter.
Dreed has hooks that allow you to change things before each transaction, for instance:
You'd like to add the apikey parameter in each URL before executing the request. This code can handle that.
hook.js
// Writing Dredd Hooks In Node.js
// Ref: http://dredd.org/en/latest/hooks-nodejs.html
var hooks = require('hooks');
hooks.beforeEach(function(transaction) {
hooks.log('before each');
// add query parameter to each transaction here
let paramToAdd = 'api-key=23456';
if (transaction.fullPath.indexOf('?') > -1)
transaction.fullPath += '&' + paramToAdd;
else
transaction.fullPath += '?' + paramToAdd;
hooks.log('before each fullpath: ' + transaction.fullPath);
});
Code at Github gist
Save this hook file anywhere in your project an than run Dredd passing the hook file.
dredd --hookfiles=./hoock.js
That's it, after execution the log will show the actual URL used in the request.
info: Configuration './dredd.yml' found, ignoring other arguments.
2018-06-25T16:57:13.243Z - info: Beginning Dredd testing...
2018-06-25T16:57:13.249Z - info: Found Hookfiles: 0=/api/scripts/dredd-hoock.js
2018-06-25T16:57:13.263Z - hook: before each
2018-06-25T16:57:13.264Z - hook: before each fullpath: /notes?api-key=23456
"/notes?api-key=23456"
2018-06-25T16:57:16.095Z - pass: GET (200) /notes duration: 2829ms
2018-06-25T16:57:16.096Z - hook: before each
2018-06-25T16:57:16.096Z - hook: before each fullpath: /notes?api-key=23456
"/notes?api-key=23456"
2018-06-25T16:57:16.788Z - pass: POST (201) /notes duration: 691ms
2018-06-25T16:57:16.788Z - hook: before each
2018-06-25T16:57:16.789Z - hook: before each fullpath: /notes/abcd1234?api-key=23456
"/notes/abcd1234?api-key=23456"
2018-06-25T16:57:17.113Z - pass: GET (200) /notes/abcd1234 duration: 323ms
2018-06-25T16:57:17.114Z - hook: before each
2018-06-25T16:57:17.114Z - hook: before each fullpath: /notes/abcd1234?api-key=23456
"/notes/abcd1234?api-key=23456"
2018-06-25T16:57:17.353Z - pass: DELETE (204) /notes/abcd1234 duration: 238ms
2018-06-25T16:57:17.354Z - hook: before each
2018-06-25T16:57:17.354Z - hook: before each fullpath: /notes/abcd1234?api-key=23456
"/notes/abcd1234?api-key=23456"
2018-06-25T16:57:17.614Z - pass: PUT (200) /notes/abcd1234 duration: 259ms
2018-06-25T16:57:17.615Z - complete: 5 passing, 0 failing, 0 errors, 0 skipped, 5 total
2018-06-25T16:57:17.616Z - complete: Tests took 4372ms

Apiary Error when invoking API

I get the following error on ApiaryWe are sorry, but the API call failed.
My host is defined as
FORMAT: 1A
HOST: https://test.mynetwork.com/
GET CALL IS DEFINED AS
data urn [models/v2/files/{urn}/data{?guid}]
GET [GET]
Parameters
urn (required, string, ttt)...design urn.
guid (optional, string,067e6162-3b6f-4ae2-a171-2470b63dfe02)...filter by guid.
Response 200 (application/vnd.api+json)
Body
data
{
"version": "1.0",
}
When i invoke this , i get error . Any inputs
I have edited your API Blueprint as follows:
FORMAT: 1A
HOST: https://test.mynetwork.com
# Test API
This is a test API
## data urn [/models/v2/files/{urn}/data{?guid}]
+ Parameters
+ urn: `ttt` (required, string) - design urn
+ guid: `067e6162-3b6f-4ae2-a171-2470b63dfe02` (optional, string) - filter by guid
### test [GET]
+ Response 200 (application/vnd.api+json)
{
"version": "1.0"
}
You can find tutorials here: https://help.apiary.io/
Also not sure what you mean "invoke" the API - are you calling the mock server or are you hitting your own server through Apiary.
Happy to help further via the support option in Apiary itself or email us on support [at] apiary.io.