I am playing around with Camel for the first time. My trial project is to write an application that receives an HTTP GET request (using Jetty) and passes on the request via Thrift to another server. The answer received is then passed back to the client. (i.e. I'm writing a data switch or a middleware application if you will between an http-get request and a Thrift-enanbled server.)
I have the non-camel version perfectly and I am now trying to put the camel-equivalent together. For now I am only trying to get the jetty request written to a file.
This is what I have so far:
public class CamelMedicalService {
public static void main(String[] args) throws Exception {
CamelContext context = new DefaultCamelContext();
context.addRoutes(new MedicalServiceRouteBuilder());
context.start();
// Wait a minute and then stop all.
Thread.sleep(TimeUnit.MINUTES.toMillis(1));
context.stop();
}
}
and the RouteBuilder:
public class MedicalServiceRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("jetty:http://localhost:8080").to("file://test");
}
}
I am currently getting
java.lang.ClassNotFoundException: org.eclipse.jetty.util.component.Destroyable... I am not sure how to resolve this. How should I set this up so that I can receive an http request and pass it to the file?
Like in the comments, please check if jetty-util.jar is in the classpath, if not, you can copy it to your WEB-INF/lib directory.
Related
Is there any way to route all request to specific URI to another projects rest controller?
consider the code below:
#Component
public class CamelSportsRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
restConfiguration()
.component("servlet")
.bindingMode(RestBindingMode.auto);
rest().path("/hello").get().route()
.toD("localhost:9080/hello");
}
}
I want to route all request of /hello to another project rest controller endpoint: localhost:9080/hello but without XML it cant be possible.
See matchOnUriPrefix of jetty and undertown components and bridgeEndpoint option of HTTP component.
This is what you need:
from("undertow:http://localhost:8080/hello?matchOnUriPrefix=true")
.to("http4://localhost:8081/hello?bridgeEndpoint=true");
Also see this another answer to more details https://stackoverflow.com/a/67893371/11052487
I have a Spring Boot rest service based application configured on multiple ports that needs to distinguish each request between the port it came through. The idea of having several ports for the application is due to different public and private sub-networks (with different security access levels) that could access different parts of the services exposed by the application.
Conceptually the idea was to add additional connectors to the embedded tomcat and then catch all incoming requests altering them by adding a custom header to each one specifying the "channel" it came through.
The problem I'm facing is that I have no idea how I could catch these incoming requests on a connector level (before it gets to any filter or servlet).
So, for the multi-port solution I have:
#Configuration
public class EmbeddedTomcatConfiguration {
#Value("${server.additional-ports}")
private String additionalPorts;
#Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
Connector[] additionalConnectors = additionalConnector();
if(additionalConnectors != null && additionalConnectors.length > 0) {
tomcat.addAdditionalTomcatConnectors(additionalConnectors);
}
return tomcat;
}
private Connector[] additionalConnector() {
if(StringUtils.isNotBlank(additionalPorts)) {
return Arrays.stream(additionalPorts.split(","))
.map(String::trim)
.map(p -> {
Connector connector = new Connector(Http11NioProtocol.class.getCanonicalName());
connector.setScheme("http");
connector.setPort(Integer.valueOf(p));
return connector;
})
.toArray(Connector[]::new);
}
return null;
}
}
In theory, I could register a custom LifecycleListener to each connector, but, as far as I know, it won't help. I've also heard something about valves, though I'm not sure how to implement them per connector.
Or maybe I'm going a completely wrong way.
I'd really appreciate any help in the matter.
It seems as though you have your heart set on trying a Valve, but, after some more research, I would recommend using a ServletFilter to do this work instead of a Valve.
I believe you can do this work in a Valve, but a Valve must be deployed into the tomcat/lib directory instead of being packaged inside of your application. I would urge you to consider trying to keep your application together in deployable artifact instead of having to remember to deploy one extra jar file to your tomcat instance when creating a new deployment.
From this answer, Difference between getLocalPort() and getServerPort() in servlets you should be able to access the tomcat port by calling getLocalPort() on the HttpServletRequest.
Then based on your idea in the question add a specific context name into the request header.
public class PortContextFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
int localPort = request.getLocalPort();
// if else or case statements
ServletRequest newRequest = PortContextHttpServletRequestWrapper(request, YourPortContext)
filterChain.doFilter(newRequest, response);
}
public void destroy() {
// Nothing to do
}
public void init(FilterConfig filterConfig) throws ServletException {
// Nothing to do.
}
}
After that is done, in theory, you should be able to use the #RequestMapping annotation to route based on the name inside the header.
#PreAuthorize("hasPermission(#your_object, 'YOUR_OBJECT_WRITE')")
#RequestMapping("/yourobject/{identifier}", headers="context=<context_name>", method = RequestMethod.POST)
public String postYourObject(#PathVariable(value = "identifier") YourObject yourObject) {
// Do something here...
...
}
One can also use RequestCondition to route requests based on port, which I think is closer to stock spring. see Spring Boot | How to dynamically add new tomcat connector?
is that possible to do this in camel:
2 rest services standing on jetty, first by http (for example on port 1234) and second https (for example on port 4321), how can I configure it? Is that possible?
Effect which i need to receive (example urls):
http://localhost:1234/firstHttpMethod
http://localhost:1234/secondHttpMethod
https://localhost:4321/firstHttpsMethod
https://localhost:4321/secondHttpsMethod
For this moment when I'm trying to add 2 routes, only second is working. How to solve that problem (I have a think to do 2 rest services - first on jetty, second on something else, but its not good conception).
code looks like this:
camelContext.addRoutes(firstJettyBuilder());
camelContext.addRoutes(secondJettyBuilder());
protected RouteBuilder firstJettyBuilder()
{
return new RouteBuilder()
{
#Override
public void configure()
throws Exception
{
restConfiguration()
.component("jetty")
.host("localhost")
.port(42300)
.scheme("https")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES")
.dataFormatProperty("json.in.enableFeatures", "FAIL_ON_NULL_FOR_PRIMITIVES");
configureSSL();
}
private void configureSSL()
{
final JettyHttpComponent jettyComponent = camelContext.getComponent("jetty", JettyHttpComponent.class);
final Map<String, Object> sslSocketConnectorProperties = new HashMap<>();
sslSocketConnectorProperties.put("keyStorePath", KEYSTORE);
sslSocketConnectorProperties.put("trustStorePath", KEYSTORE);
sslSocketConnectorProperties.put("keyStorePassword", KEYSTORE_PASSWORD);
sslSocketConnectorProperties.put("trustStorePassword", KEYSTORE_PASSWORD);
jettyComponent.setSslSocketConnectorProperties(sslSocketConnectorProperties);
}
};
}
protected RouteBuilder createPosJettyBuilder()
{
return new RouteBuilder()
{
#Override
public void configure()
throws Exception
{
restConfiguration()
.component("jetty")
.host("localhost")
.port(42302)
.scheme("http")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("json.in.disableFeatures", "FAIL_ON_UNKNOWN_PROPERTIES")
.dataFormatProperty("json.in.enableFeatures", "FAIL_ON_NULL_FOR_PRIMITIVES");
}
};
}
Short answer: I don't thnik this is possible in the same Camel context, because of reasons that I can call bugs. It might be possible with different contexts.
Here are some observations after debugging this.
1st try: as in the question.
Camel uses the same Jetty endpoint for both configurations. The second RouteBuilder overwrites the endpoint configuration of the first one. Hence, the expected first server is not running at all.
2nd try: multiple Jetty endpoints.
One may try something like (after creating Jetty endpoint(s) and adding them to the context):
this.restConfiguration("jetty")....
this.rest("/path").... // good
...
this.restConfiguration("jetty-tls")....
this.rest("/path").... // produces exception!
It looks like the rest definitions are added to the Camel context. On creating routes for the second RouteBuilder, the definition from the first one is already there. Camel wants to create 2 routes with the same path and throws exception:
Failed to start route ... because of Multiple consumers for the same endpoint is not allowed: jetty:...
Unfortunately, it is not an option to skip the rest definition in one of the builders.
Bonus try: multiple Jetty endpoints and different paths.
One expects at least this to work:
this.restConfiguration("jetty")....
this.rest("/path1").... // good
...
this.restConfiguration("jetty-tls")....
this.rest("/path2").... // good
No exceptions here, but Camel starts 3 routes!
I'm using Camel to integrate 2 systems. I have defined different routes and one of the routes consumes from a specific rabbitmq queue and send it to a REST service. Nothing fancy here, the route looks like this:
public class WebSurfingRabbitToRestRoute extends RouteBuilder{
#Override
public void configure() throws Exception {
from("rabbitmq://rabbit_host:port/Rabbit_Exchange").
setHeader("CamelHttpMethod", constant("POST")).
setHeader("Content-Type", constant("application/json")).
bean(TransformResponse.class, "transform").
to("http4://rest_service_host:port/MyRestService).
}
}
As you can see, i process every message before sending it to the rest service since i need to adjust some things. The problem comes when i find out that sometimes (i dont know how or when), the system that publish into rabbit, send 2 messages concatenated at once.
What i expect to get is a simple json like this:
[{field1:value1, field2:value2}]
What i sometimes get is:
[{field1:value1, field2:value2},{field1:value3, field2:value4}]
So when i face this scenario, the rest service im routing the message to, fails (obviously).
In order to solve this, i would like to know if there is a way to invoke a route from inside a processor. From the previous snippet of code you can see that Im calling the transform method, so the idea will be to do something like the following pseudo-code, because after the route is already fired, i cant split the events and send them both within the same route "instance", so i thought about invoking a different route that i can call from here which will send the message2 to the very same rest service.
public class TransformRabbitmqResponse {
public String transform(String body) throws Exception {
// In here i do stuff with the message
// Check if i got 2 messages concatenated
// if body.contains("},{") {
// split_messages
// InvokeDifferentRoute(message2)
//}
}
}
Do you guys think this is possible?
One option (though I am not sure this is the best option) would be to split this up into two different routes using a direct endpoint.
public class WebSurfingRabbitToRestRoute extends RouteBuilder{
#Override
public void configure() throws Exception {
from("rabbitmq://rabbit_host:port/Rabbit_Exchange")
.setHeader("CamelHttpMethod", constant("POST"))
.setHeader("Content-Type", constant("application/json"))
.bean(TransformResponse.class, "transform");
from("direct:transformedResponses")
.to("http4://rest_service_host:port/MyRestService");
}
}
And then in your transform bean, you can use camel Producer Template to publish the transformed payload(s) to your new direct endpoint (assuming you are using json?).
producerTemplate.sendBody("direct:transformedResponses", jsonString);
I'm trying to make a Java servlet that can make Apache Thrift calls, but I'm having trouble starting the servlet.
I have a thrift client, a Java class for making calls to the thrift server
public class ThriftClient {
static TTransport transport;
static TProtocol protocol;
static MyService.Client client;
static long xtk_pointer;
public static void openSocket() throws TException {
transport = new TSocket("localhost", 9090);
transport.open();
protocol = new TBinaryProtocol(transport);
client = new MyService.Client(protocol);
}
and I have a java servlet which opens a socket through the thrift client
public class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
out.println("Hello World");
}
void startThrift(String [] args) {
try {
ThriftClient.openSocket();
However, when I try to run this servlet (using eclipse and a tomcat server), I get an error like
SEVERE: A child container failed during start
and a ClassNotFoundException for org.apache.thrift.TException
EDIT: All I had to do was include the thrift jars into the Tomcat Server's classpath. See my answer below
I have used the thrift client already without any ClassNotFoundExceptions, and the servlet works on its own as well. However, once I add ThriftClient.openSocket(); into the servlet, it breaks, so I have a feeling Thrift and Tomcat are clashing somehow. Any ideas?
Edit: The weird part is that I never call the method startThrift() but I still get the error.
While you included the thrift jars into your project, you also have to add them to the Tomcat library as well
First make sure your external jars are in your project Java Build Path...
right click your project, click Properties
Under Deployment Assembly, click Add...
Double click Java Build Path Entries... and select the jars/libraries you want to include