I want to handle an HTTPs request that a 3rd party is doing towards my system. The requests contains a JSON object
I have tried using the Jetty component of Camel but when the 3rd party performs the requests Camel throws the following exception:
org.apache.camel.RuntimeCamelException: Cannot read request parameters due Invalid parameter, expected to be a pair but was {"id":"321","rec":"533","status":"ok"}
The Jetty endpoint is defined in blueprint as:
<camel:endpoint id="getRequestEndpoint" uri="jetty:{{webServiceProtocol}}://{{jettyIp}}:{{jettyPort}}/getRequest?sslContextParametersRef=sslContextParameters"/>
Am I missing something or are in a totally wrong path?
Make sure that your client is sending the appropriate Content-Type HTTP request header for JSON, which is:
Content-Type: application/json
It looks like it wasn't sent and the Jetty component falls back to form data interpretation.
Make sure you also add expected bean type to your endpoint definition, like this:
.post()
.type(User.class)
.to("direct:your-queue")
This is strictly speaking not an answer, because in this case you get a different exception shown below. But I hope it helps someone.
org.apache.camel.InvalidPayloadException: No body available of type:
com.example.User but has value: {id=69, name=Hello world} of type:
java.util.LinkedHashMap
Related
My application uses spring-webflux, it still uses classic #Controllers with #RequestMapping-annotated handler methods.
Some methods produce application/json, while others produce text/event-stream.
When a request hits a controller, there is no problem: each mapping has produces with the corresponding media type defined.
The application also uses spring-security (the reactive flavor). If an unauthenticated request arrives, we must build a error response using correct format: JSON for application/json endpoints and a Server-Sent-Event for text/event-stream endpoints.
The problem is that security checks are made before the request handler is resolved, so Spring has no clue about the correct response media type at this point.
If a client sends Accept header, this solves the problem: we just parse it and decide what content type to use, with something like the following:
request.getHeaders().getAccept().contains(MediaType.TEXT_EVENT_STREAM)
(the algorithm is oversimplified, but you get the idea).
But some clients do not send Accept header at all. Strictly speaking, we have all the information we need: we have request, and, somewhere in spring-webflux beans information about all the mappings is stored.
So the question is: how (only having ServerWebExchange instance and access to Spring context) do you make use of this mapping information to find out what media types are supported by a handler corresponding to the current request?
P.S. What I tried/thought of so far:
Manually maintain list of all streaming endpoints... yuck!
Use a bean post-processor to collect information about all mappings and then try to emulate spring-webflux behavior... cumbersome and probably fragile.
We are using spring-integation (xml based configuration), In which we are performing below steps
Convert the payload (java-object) to json
Make the rest api call
Convert back to java-object
<int:object-to-json-transformer content-type="test.Request" input-channel="objectToJsonChannel" output-channel="apiChannel"/>
<int-http:outbound-gateway id="apiChannel"
request-channel="apiChannel"
reply-channel="jsonToObjectChannel"
....
/>
<int:json-to-object-transformer type="test.Response" input-channel="jsonToObjectChannel" output-channel="responseChannel"/>
Above code works till spring-integration version 5.1. When I upgrade to 5.2. It starts to throw the exception as
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [test.Request] to type [test.Response].
I have noticed that object-to-json-transformer add class type on the header with key json__TypeId__. Then it uses that class type for json-to-object-transformer.
But it is expected that type attribute mentioned on json-to-object-transformer should be used if mentioned.
Please suggest on fixing this issue or Is it really bug on spring integration (5.2).
Consider to add a <header-filter header-names="json_*" /> before calling your REST service. The <int:object-to-json-transformer> populates JsonHeaders to let downstream to know what the real type of JSON we curry in the payload.
A <int:json-to-object-transformer> prefers those headers instead of static type option.
But since the payload is already a different representation than those request headers it does a wrong thing.
I would suggest an option on the <int:json-to-object-transformer> to make a preference, but that would not be fully logical. Since we have changed a payload, it would be better to change its respective headers. Otherwise we just lying to ourselves.
On the other hand a HTTP Outbound Gateway can take care for your to convert request into a JSON for network and back from JSON response to some POJO type.
See https://docs.spring.io/spring-integration/docs/5.2.3.RELEASE/reference/html/http.html#http-outbound and its expected-response-type. As long as a contentType header is an application/json, you are good to avoid those <int:object-to-json-transformer> & <int:json-to-object-transformer>.
I've written a series of JAX-RS services that are deployed in a WAR file on Wildfly 11. I have the #Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) annotation on them, indicating that I want to receive JSON or XML as the response from the service. I have a series of data transfer objects annotated with JAXB annotations. These are the objects that will be returned by my service. I've tested using JSON and everything works as expected. However, when I went to test using an "Accept: application/xml" header so I could get back XML, I got the following Exception in my logs:
org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not
find MessageBodyWriter for response object of type: com.test.MyObject
of media type: application/xml;charset=UTF-8
I'm not sure where the ;charset=UTF-8 came from. I'm not specifying that in my Accept header. I also don't see ;charset=UTF-8 anywhere when I using application/json as my Accept type. Additionally, I'm logging all of my request headers, and don't see ;charset=UTF-8 appearing anywhere, so it's definitely not something being added from my side.
Has anyone encountered this before? I know I haven't, so I am at a loss as to why this is happening. Any thoughts?
Make sure your data transfer object (eg com.test.MyObject) is annotated with #XmlRootElement and not #XmlElement. If not, this might be the cause...
We started using Jersey/JAX-RS for internal REST endpoints that get used by our front-end code. Endpoints that have to return a result, always send JSON objects.
For debugging purposes, we are using the firefox restclient extension. Until recently, I would just enter the URL and hit send, and would get back content displayed as JSON.
But when I did that this morning, the FF extension comes back and tells me that I have to change the response type to binary (BLOB). Doing so results in displaying an encoded string instead of JSON.
I could resolve that by setting a request header (Accept: to be application/json).
Doing some more research, I came across this question. My conclusion is: probably we should add #Produces("application/json") to all these endpoints.
Question: is it really that simple, or are there good technical reasons to not do that?
You should always declare the #Produces and #Consumes annotations (either at the class level or method level) for the purpose of Content Negotiation and HTTP protocol correctness. Without these annotations, the result will be dependent on the client request and the default behavior of the server (which may be different across implementations), which leads to unpredictable and ambiguous results.
With these annotations, we advertise what media types we can produce and consume. On Retrieve (GET) requests, the client should send an Accept header with the media type of the resource they expect back. And on Create requests (PUT, POST), the client should send a Content-Type header telling the server what media type the data is that they are sending. If these headers don't match what the server is advertised to handle, then the client will get error responses back telling them what the problem is; with a Retrieve request and a non-matching Accept header, the response will be a 406 Not Acceptable. With a Create request and a non-matching Content-Type header, the response will be a 415 Unsupported Media Type.
This is how content negotiation works. And to make sure our server behaves as the clients expect, we should declare what we can handle on the server. The annotations do just this.
As you mentioned, when you left off the #Produces, the client told you you needed to change the response type. This is because the result was that the Content-Type response header was set to application/octet-stream, which is what the answers here conclude. Clients use the Content-Type header to determine how to handle the response.
That last example was for a Retrieve request. If we left off the #Consumes on a Create endpoint, a lot of different things can go wrong. Take for example we have an endpoint that we want to accept JSON, so we create a POJO to map the JSON to.
#POST
public Response create(Customer customer) {}
For this to work, it is dependent on the client setting the Content-Type header on the request to application/json. But without the #Consumes annotation, we are basically advertising this endpoint to be able to accept any media type, which is just ridiculous. The #Consumes annotation acts like a guard saying "If you don't send the right type of data, you cannot pass". But since we don't have the guard, all data is allowed through, and the result is unpredictable, because depending on what the client sets the Content-Type to, we don't know what MessageBodyReader1 will handle the conversion from the entity body to Customer. If the correct MessageBodyReader is not chosen (the one that converts JSON to POPJOs), then most likely it will lead to an exception, and the client will get back a 500 Internal Server Error, which is not as specific as getting a 415 Unsupported Media Type.
1. See chapter 8 and 9 of the Jersey docs. It will explain how entity bodies are converted to Java objects (and vice versa) using MessageBodyReader and MessageBodyWriter, respectively.
Is it good practice to use #Produces("application/json") on all JSON producing endpoints?
If your resource methods produce JSON as representation of your resources, they should be annotated with #Produces(MediaType.APPLICATION_JSON). As a result, the response will have a Content-Type header indicating the media type of the payload.
The #Produces annotation is also used for request matching: The JAX-RS runtime matches the media type sent in the Accept header with the media type defined in the #Produces annotation.
If you don't want to annotate every resource method in your application, you can annotate the resource classes instead. It will indicate that all methods defined in such class must produce JSON as representation of your resources.
The media type defined in the #Produces annotation indicates the media type that will be produced by the MessageBodyWriter instances registered in the application. Consider the following example:
#GET
#Produces(MediaType.APPLICATION_JSON)
public Foo getFoo() {
Foo foo = new Foo();
return Response.ok(foo).build();
}
Once the getFoo() method is annotated with #Produces(MediaType.APPLICATION_JSON), JAX-RS will write the Foo instance as a JSON document. It's done in a MessageBodyWriter implementation. If your application uses Jackson, for example, the JacksonJsonProvider will be used to convert Java objects to JSON documents.
I have a Rest API, created with Camel Rest-DSL. There is a rest, that consumes GET with a list of params, some of which are mandatory.
Route config:
rest().get("/{{camel.rest.version}}/myget")
.param()
.name("accountNumber")
.dataType("string")
.type(RestParamType.query)
.required(true)
.endParam()
.param()
.name("someId")
.dataType("string")
.type(RestParamType.query)
.required(false)
.endParam()
.produces(REST_PR_CN_TYPE)
.responseMessage().code("200").message("OK").endResponseMessage()
.responseMessage().code("500").endResponseMessage()
.route().routeId("rst_cardsInfo")
.log(LoggingLevel.INFO, "ApiRq Recieved http request")
.log(LoggingLevel.DEBUG, "AccountNumber: ${header.accountNumber}, SomeId: ${header.someId}")
.id("rst_rst_info_recieved")
.to("direct:drt_rst_info")
.endRest();
When I open swagger-ui generated page, my API is looks fine. Param accountNumber is marked as required, someId - as not required.
Using any other tool I can send a request without any params and receive HTTP.200 as a response. I expected, that if a param is required, but not present in request, the request would fail. Spring Rest for example makes sure that all mandatory params are present.
Is there any mandatory params presence validation in Camel? May be I misconfigured something?
Ah okay. There is no / only a little bit of validation today in the rest-dsl. It relies on the chosen HTTP component (servlet, restlet, undertow etc.) to do that.
But frankly we can improve thise and let camel-core do some pre-validation if the options has been specified as in your example.
I have logged a ticket: https://issues.apache.org/jira/browse/CAMEL-12533
Thank you, #Claus Ibsen.
From Camel 2.22 version onwards, now we can validate incoming client request.
The validation is turned off by default. In order to configure it, we need to use clientRequestValidation as follow :
restConfiguration()
.component("restlet").host("localhost").port("port")
.clientRequestValidation(true);
For more details visit : Client Request Validation - Apache Camel Manual