Wildcards in camel route - apache-camel

I have rest controller with request mapping as follows:
#PostMapping(value = "fpl/generate/{legIdentifier:.+}"
My camel route is defined as from("direct:/fpl/generate/").
The controller calls web service, web service calls FluentEndpointInvoker class which calls route defined above.
public class FluentEndpointInvoker {
#EndpointInject(uri = BASE_ENDPOINT_URI)
private FluentProducerTemplate producer;
#Value("${server.servlet.context-path}")
private String contextRoot;
public <T, R> T request(Class<T> type, R request, HttpHeaders headers) {
return producer.withProcessor(exchange -> {
exchange.getIn().setBody(request, request.getClass());
headers.forEach((key, value) -> exchange.getIn().setHeader(key, value));
String endpoint = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()
.getRequestURI();
exchange.getIn().setHeader(ROUTE_ENDPOINT_HEADER, "direct:".concat(endpoint.replaceFirst(contextRoot, "")));
}).request(type);
}
}
The endpoint that is generated is something like direct:///fpl/generate/LH.1234.30Jun2016.FRA.BOG.X1. How can I configured wildcards in camel route so that endpoint can get called

Well, if you are not forced to use an endpoint with the full URI, you could simplify the case.
Instead of creating a dynamic endpoint, you could send all messages to the endpoint direct:///fpl/generate and set the full request URI as header on the message.
That way you have a simple route endpoint to use and the URI header to make decisions etc based on the full URI.

Related

Debugging RestEasy RestClient

I am using the framework in quarkus to build a rest client for the mandrill API
#RegisterRestClient
#Path("1.0")
#Produces("application/json")
#Consumes("application/json")
public interface MailService {
#POST
#Path("/messages/send-template.json")
JsonObject ping(JsonObject mandrillInput);
}
This is the relevant portion of my application.properties
com.example.service.MailService/mp-rest/url=https:/mandrillapp.com/api
And my example resource
#Path("/hello")
public class ExampleResource {
#Inject
#RestClient
MailService mailService;
#Produces(MediaType.TEXT_PLAIN)
#GET
public String hello() {
System.out.print("In the API");
JsonObject key = Json.createObjectBuilder().add("key", "ABCD").build();
System.out.println("The json built is "+key);
JsonObject response = mailService.ping(key);
System.out.println("The response is " + response);
return "hello";
}
}
What I saw is that if the API I am calling (Mandrill in this case) returns an error response (If my key is wrong for example), then the variable I am using to store the response doesnt get the response. Instead the REST API I am exposing to my application wrapping around this, gets populated with the response from Mandrill.
Is this expected behaviour? How can I debug the output of a rest client implementation in Quarkus?
The REST API being called is https://mandrillapp.com/api/docs/users.JSON.html#method=ping2
If you want to be able to get the body of the response when an error occurs, I suggest you use javax.ws.rs.core.Response as the response type.
You could also go another route and handle exceptions using ExceptionMapper

Restlet + Google App Engine + CORS

I am using Restlet on Google App Engine for developing my sample application.
The front end is Angular 2 App.
The Rest API is working fine with browser.
However, I am getting the following issue when I am trying to hit the URL from Angular app.
XMLHttpRequest cannot load https://1-dot-jda-saas-training-02.appspot.com/rest/projectsBillingInfo/123. The 'Access-Control-Allow-Origin' header contains multiple values 'http://evil.com/, *', but only one is allowed. Origin 'http://localhost:3000' is therefore not allowed access.
So, I thought I will go ahead and add the CORS headers in the response. I used CorsFilter for that as follows but the issue is still there. When I see the header of the Response, I do not see any CORS headers. What am I missing here?
#Override
public Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
CorsFilter corsFilter = new CorsFilter(getContext(), router);
corsFilter.setAllowedOrigins(new HashSet<String>(Arrays.asList("*")));
corsFilter.setAllowedCredentials(true);
// Defines only one route
router.attachDefault(AddressServerResource.class);
router.attach("/contact/123",ContactServerResource.class);
router.attach("/projectsBillingInfo/123",ProjectBillingResource.class);
return corsFilter;
}
EDIT
I could get this working. May be I was doing some mistake.
But, I am not able to make this work with the GaeAuthenticator. When I am putting the GaeAuthenticator along with Corsfilter, it skips the authentication part of it. So, either the authentication works or the corsfilter works but not both. Is there any easy way to set/modify HTTP headers in restlet.
Here is the code I am using ..
#Override
public Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attachDefault(AddressServerResource.class);
router.attach("/contact/123",ContactServerResource.class);
router.attach("/projectsBillingInfo/123",ProjectBillingResource.class);
GaeAuthenticator guard = new GaeAuthenticator(getContext());
guard.setNext(router);
CorsFilter corsFilter = new CorsFilter(getContext(), router);
corsFilter.setAllowedOrigins(new HashSet<String>(Arrays.asList("*")));
corsFilter.setAllowedCredentials(true);
return corsFilter;
First, I think you can use the service instead of the filter:
public MyApplication() {
CorsService corsService = new CorsService();
corsService.setAllowedCredentials(true);
corsService.setSkippingResourceForCorsOptions(true);
getServices().add(corsService);
}
Do you mind to set the "skippingServerResourceForOptions"?
#Override
public Restlet createInboundRoot() {
// Create a router Restlet that routes each call to a
// new instance of HelloWorldResource.
Router router = new Router(getContext());
// Defines only one route
router.attachDefault(AddressServerResource.class);
router.attach("/contact/123",ContactServerResource.class);
router.attach("/projectsBillingInfo/123",ProjectBillingResource.class);
return router;
}
Best regards, Thierry Boileau

Empty response from camel in cxf POJO mode

Below is my routing for a sample web service in camel and cxf.
from("cxf:http://localhost:9000/sampleService?serviceClass=com.sample.CommonIntf")
.id("wsProxy")
.bean(MyBean.class)
Simply I am passing input pojo object to bean. And inside bean I am setting the ws response. Here is the bean class.
#Handler
public SOut handle(SInput sin){
SOut s = new SOut();
s.setName(sin.getName());
s.setSurName("aa");
return s;
}
However althoug I can see input object is converted and delivered the handler method soap response is empty.
Here is my web service signature.
public interface CommonIntf{
SOut sampleMethod(SInput input);
}
My question is although my handler returns response, why response soap is empty ?
I think, you just not set exchange output body (request-reply pattern).
Try to modify your route like this:
from("cxf:http://localhost:9000/sampleService?serviceClass=com.sample.CommonIntf")
.id("wsProxy")
.to("bean:MyBean?method=handle");
MyBean class must be registered in bundle context.
<bean id="MyBean" class="com.sample.MyBean"/>
Try the following , define your CXF endpoint
[as per http://camel.apache.org/schema/cxf/ ] in a endpoint definition bean, in this refer the service class,
and refer the same id (for example wsCxfId)in the Camel route.
So the route will be as follows:
from("cxf:bean:wsCxfId")
.id("wsProxy")
.to("bean:MyBean?method=handle");
Hope this helps.

Camel-jetty component : use of available options

I am using the recipientList tag in my route as shown below. The bean findCallbackUrl will return a String which is my destination address. i.e i will be making a POST request to this endpoint . For example i may have a server listening at http://localhost:8080/acceptcallbacks .
<recipientList>
<method ref="findCallbackUrl"/>
</recipientList>
So when the String(destination) returned from the findCallbackUrl bean is jetty:http://localhost:8080/acceptcallback , the POST works fine.
However when some of the option mentioned in the Camel-jetty component are used so issues are seen. when the destination returned is jetty:http://localhost:8080/acceptcallbacks?enableJmx=false or jetty:http://localhost:8080/acceptcallbacks?disableStreamCache=false the POST works fine. However if the returned string is jetty:http://localhost:8080/acceptcallbacks?chunked=false , the call becomes a GET request .
Not sure what is happening here. If the Camel-jetty options are not to be used in the way they have been used above , for options enableJmx or disableStreamCache or some other options , the resulting destination URL should have been http://localhost:8080/acceptcallbacks?enableJmx=false which is a GET request.
Can chunked=false be used for producer as well as consumer endpoints or is it only for consumer endpoint?
Selection of http method in jetty depends on In Body of camel exchange, or on CamelHttpMethod header.
Look at source code of class org.apache.camel.component.http.helper.HttpHelper, method createMethod:
public static HttpMethods createMethod(Exchange exchange, HttpEndpoint endpoint, boolean hasPayload) throws URISyntaxException {
// compute what method to use either GET or POST
HttpMethods answer;
HttpMethods m = exchange.getIn().getHeader(Exchange.HTTP_METHOD, HttpMethods.class);
if (m != null) {
// always use what end-user provides in a header
answer = m;
} else if (hasPayload) {
// use POST if we have payload
answer = HttpMethods.POST;
} else {
// fallback to GET
answer = HttpMethods.GET;
}
return answer;
}
Now look at the call of this method in class org.apache.camel.component.jetty.JettyHttpProducer:
HttpMethods methodToUse = HttpHelper.createMethod(exchange, getEndpoint(), exchange.getIn().getBody() != null);
IMHO options don't matter. Check exchange.getIn().getBody() before request to HTTP, or you can reset header CamelHttpMethod.

Jax-Rs service is not getting invoked

I am developing JAX-RS Rest service using Apache CXF. After deploying it to Tomcat 7 server, if I type the URL http://localhost:8080/Rest/rest?_wadl it shows me the WADL. but if I enter the URL http://localhost:8080/Rest/rest/retrieve it gives me 404 error.
In above URLs: Rest is the name of my project
/rest is the url-pattern for my CXFServlet which is specified in web.xml
/ is the address of jaxrs:server which is specified in beans.xml
retrieve is the path of service which is specified in my interface with #Path annotation.
(My apologies: I can't provide the XML documents referred to above.)
I think this is a CXF bug which get the incorrect base URL for restful web services.
The class "org.apache.cxf.transport.servlet.ServletController" invokes the method "getBaseURL" of the class "org.apache.cxf.transport.servlet.BaseUrlHelper".
It gets the base URL from request URL, and it ignores the parameters part.
This is correct for SOAP web servcies, because SOAP web services URL is just like: http://host:port/basepath?para=a. Unfortunately, for restful web services, the URL is just like http://host:port/basepath/method/parameter. The correct base URL should be http://host:port/basepath, but actually, the BaseUrlHelper gives you http://host:port/basepath/method/parameter. It just gives the URL before "?". It's why the result is correct when you access http://localhost:8080/Rest/rest?_wadl, in this case, it gives the correct base URL http://localhost:8080/Rest.
If you access http://localhost:8080/Rest/rest?_wadl at first then you access http://localhost:8080/Rest/rest/retrieve, it would be correct. Because, CXF set the base URL as the address of EndpointInfo only at the first time. It means, you MUST access the correct base URL at the first time! :(
The solution is: override the method "getBaseURL(HttpServletRequest request)" of "org.apache.cxf.transport.servlet.ServletController", let it return correct base URL.
For example, step1: extends the ServletController.
public class RestfulServletController extends ServletController {
private final String basePath;
public RestfulServletController(DestinationRegistry destinationRegistry, ServletConfig config,
HttpServlet serviceListGenerator, String basePath) {
super(destinationRegistry, config, serviceListGenerator);
this.basePath = basePath;
}
#Override
protected String getBaseURL(HttpServletRequest request) {
// Fixed the bug of BaseUrlHelper.getBaseURL(request) for restful service.
String reqPrefix = request.getRequestURL().toString();
int idx = reqPrefix.indexOf(basePath);
return reqPrefix.substring(0, idx + basePath.length());
}
}
step2: extends CXFNonSpringServlet and use the RestfulServletController in the subclass
public class RestfulCXFServlet extends CXFNonSpringServlet {
... ...
private ServletController createServletController(ServletConfig servletConfig) {
HttpServlet serviceListGeneratorServlet = new ServiceListGeneratorServlet(destinationRegistry, bus);
ServletController newController = new RestfulServletController(destinationRegistry, servletConfig,
serviceListGeneratorServlet, basePath);
return newController;
}
}
step3: instead of CXFNonSpringServlet , you use the derived class RestfulServletController.
Don't forget, you should config the "basePath" as /Rest/rest.
Hope this can help you.

Resources