Camel - how to add request parameter(throwExceptionOnFailure) to url? - java

I hav following route:
from("quartz2:findAll//myGroup/myTimerName?cron=" + pushProperties.getQuartz())
//.setBody().constant("{ \"id\": \"FBJDBFJHSDBFJSBDfi\" }")
.to("mongodb:mongoBean?database=" + mongoDataConfiguration.getDatabase()
+ "&operation=findAll&collection=" + mongoDataConfiguration.getDataPointCollection())
.process(exchange -> {
exchange.getIn().setBody(objectMapper.writeValueAsString(exchange.getIn().getBody()));
}).streamCaching()
.setHeader(Exchange.HTTP_METHOD, constant(pushProperties.getHttpMethod()))
.setHeader(Exchange.CONTENT_TYPE, constant(MediaType.APPLICATION_JSON_VALUE))
.to(pushProperties.getUrl() + "&throwExceptionOnFailure=false").streamCaching()
As you can see I use throwExceptionOnFailure=false
and I take my url from configuration. But we found out that it works if
pushProperties.getUrl() = localhost:8080/url?action=myaction
and doesn't work in case of
pushProperties.getUrl() = localhost:8080/url
Is there universla way in camel to add request parameter to URL?
something like:
private String buildUrl() {
String url = pushProperties.getUrl();
return url + (url.contains("?") ? "&" : "?") + "throwExceptionOnFailure=false";
}
inside Camel api

That is because in case of localhost:8080/url, after appending it becomes like this
localhost:8080/url&throwExceptionOnFailure=false
which is wrong
It should be
localhost:8080/url?throwExceptionOnFailure=false,
In the first case it works you already have a requestpatam(?action=myaction) so the next one can be added with ampersand(&)

I think you have to add your own logic to compose the endpoint to the http component at the runtime. This is because the CamelContext will process it during the route itself. The parameter throwExceptionOnFailure is a property from the http component.
I don't think that adding the parameter via .setHeader(Exchange.HTTP_QUERY, constant("throwExceptionOnFailure=false")) shoud work because these parameters will be evaluated after the http component get processed, e.g. into the URL destination. Please, take a look at "How to use a dynamic URI in to()":
.toD(pushProperties.getUrl() + "&throwExceptionOnFailure=false")
You could use the simple expression to write a logic to do what you want based on the result of pushProperties.getUrl().

I don't like how Camel configure HTTP component in this case, but this is what it is.
What I suggest is to create a map of config, and append your args to it, and do a manual join with "&", then append it to the main url.
I do it like:
public class MyProcessor {
/**
* Part of Camel HTTP component config are done with URL query parameters.
*/
private static final Map<String, String> COMMON_QUERY_PARAMS = Map.of(
// do not throw HttpOperationFailedException; we handle them ourselves
"throwExceptionOnFailure", "false"
);
#Handler
void configure(Exchange exchange, ...) {
...
Map<String, String> queryParams = new HashMap<>();
queryParams.put("foo", "bar");
message.setHeader(Exchange.HTTP_QUERY, mergeAndJoin(queryParams));
...
}
private String mergeAndJoin(Map<String, String> queryParams) {
// make sure HTTP config params put after event params
return Stream.concat(queryParams.entrySet().stream(), COMMON_QUERY_PARAMS.entrySet().stream())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
}
}
Note that toD needs optimization but in that case, HTTP_QUERY cannot be used.
When the optimised component is in use, then you cannot use the headers Exchange.HTTP_PATH and Exchange.HTTP_QUERY to provide dynamic values to override the uri in toD. If you want to use these headers, then use the plain to DSL instead. In other words these headers are used internally by toD to carry the dynamic details of the endpoint.
https://camel.apache.org/components/3.20.x/eips/toD-eip.html

Related

Eliminate sensitive headers from spring RequestDumperFilter

Is it possible to configure Spring RequestDumperFilter so that it will not log specific headers that contain sensitive information (for example the "Authorization" header)?
Looking at the source https://github.com/apache/tomcat/blob/8e2aa5e45ce13388da62386e3cb1dbfa3b242b4b/java/org/apache/catalina/filters/RequestDumperFilter.java it appears there is no way to do that:
Enumeration<String> hnames = hRequest.getHeaderNames();
while (hnames.hasMoreElements()) {
String hname = hnames.nextElement();
Enumeration<String> hvalues = hRequest.getHeaders(hname);
while (hvalues.hasMoreElements()) {
String hvalue = hvalues.nextElement();
doLog(" header", hname + "=" + hvalue);
}
}
But it would be very easy to copy that file and modify it to do what you want.

Encoding a URL Query Parameter so it can have a '+'

Apparently, in the move from Spring Boot 1 to Spring Boot 2 (Spring 5), the encoding behavior of URL parameters for RestTemplates changed. It seems unusually difficult to get a general query parameter on rest templates passed so that characters that have special meanings such as "+" get properly escaped. It seems that, since "+" is a valid character, it doesn't get escaped, even though its meaning gets altered (see here). This seems bizarre, counter-intuitive, and against every other convention on every other platform. More importantly, I can't figure out how to easily get around it. If I encode the string first, it gets double-encoded, because the "%"s get re-encoded. Anyway, this seems like it should be something very simple that the framework does, but I'm not figuring it out.
Here is my code that worked in Spring Boot 1:
String url = "https://base/url/here";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
for (Map.Entry<String, String> entry : query.entrySet()) {
builder.queryParam(entry.getKey(), entry.getValue());
}
HttpEntity<TheResponse> resp = myRestTemplate.exchange(builder.toUriString(), ...);
However, now it won't encode the "+" character, so the other end is interpreting it as a space. What is the correct way to build this URL in Java Spring Boot 2?
Note - I also tried this, but it actually DOUBLE-encodes everything:
try {
for (Map.Entry<String, String> entry : query.entrySet()) {
builder.queryParam(entry.getKey(), URLEncoder.encode(entry.getValue(),"UTF-8" ));
}
} catch(Exception e) {
System.out.println("Encoding error");
}
In the first one, if I put in "q" => "abc+1#efx.com", then, exactly in the URL, I get "abc+1#efx.com" (i.e., not encoded at all). However, in the second one, if I put in "abc+1#efx.com", then I get "abc%252B1%2540efx.com", which is DOUBLE-encoded.
I could hand-write an encoding method, but this seems (a) like overkill, and (b) doing encoding yourself is where security problems and weird bugs tend to creep in. But it seems insane to me that you can't just add a query parameter in Spring Boot 2. That seems like a basic task. What am I missing?
Found what I believe to be a decent solution. It turns out that a large part of the problem is actually the "exchange" function, which takes a string for a URL, but then re-encodes that URL for reasons I cannot fathom. However, the exchange function can be sent a java.net.URI instead. In this case, it does not try to interpolate anything, as it is already a URI. I then use java.net.URLEncoder.encode() to encode the pieces. I still have no idea why this isn't standard in Spring, but this should work.
private String mapToQueryString(Map<String, String> query) {
List<String> entries = new LinkedList<String>();
for (Map.Entry<String, String> entry : query.entrySet()) {
try {
entries.add(URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8"));
} catch(Exception e) {
log.error("Unable to encode string for URL: " + entry.getKey() + " / " + entry.getValue(), e);
}
}
return String.join("&", entries);
}
/* Later in the code */
String endpoint = "https://baseurl.example.com/blah";
String finalUrl = query.isEmpty() ? endpoint : endpoint + "?" + mapToQueryString(query);
URI uri;
try {
uri = new URI(finalUrl);
} catch(URISyntaxException e) {
log.error("Bad URL // " + finalUrl, e);
return null;
}
}
/* ... */
HttpEntity<TheResponse> resp = myRestTemplate.exchange(uri, ...)

SparkJava: Get route path from request

If I have a route like foo/:id in my SparkJava server, I want to get that route string in my handler. I can get the pathInfo as in the following:
Spark.get("foo/:id", (request, response) -> {
var matchedRoute = request.pathInfo();
System.out.println(matchedRoute);
})
But if I curl localhost:8080/foo/1, then this will print /foo/1, instead of /foo/:id.
Is it possible to get the route that SparkJava matched the request against?
There's no inherent way to do that because the anonymous function doesn't receive the route itself as an argument. But you could add the route to the request in a 'before' part:
// For every route add these two lines:
String API_PATH_1 = "foo/:id";
before(API_PATH_1, (req, res) -> req.attribute("route", API_PATH_1));
get(API_PATH_1, (req, res) -> {
String route = req.attribute("route");
String matchedRoute = req.pathInfo();
System.out.println("Route: " + route);
System.out.println("Matched Route: " + matchedRoute);
});
It adds a little bit of code but if you're consistent with this pattern with all your routes then in the get() part you can always just call req.attribute("route") which will give you what wanted.

Changing the format of list / array in URL [Retrofit 2]

As stated in the Retrofit documentation above the #Query annotation:
Passing a List or array will result in a query parameter for each
non-null item.
As of now my call looks something like this:
#GET("questions")
Call<List<QuestionHolder>> getQuestionsExcludingTheSpecified(
#Query("exclude_ids") long[] excludedQuestionIds
);
This works but results in fairly long URLs quite fast.
E.g. for excludedQuestionIds = new long[]{1L, 4L, 16L, 64L} the request URL already will be /questions?exclude_ids=1&exclude_ids=4&exclude_ids=16&exclude_ids=64.
Is there an easy way to exchange this behaviour resulting in arrays formatted as exclude_ids=[1,4,16,64] or something similar?
What came to my mind yet was, to:
use JsonArray as parameter, but then I need to convert every array / list before making the call
intercept every request and compress duplicated keys
override the built-in #Query decorator
Any ideas?
I decided to go with the Interceptor approach. I simply change any outgoing request that includes more than one value for a single query parameter.
public class QueryParameterCompressionInterceptor implements Interceptor {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url();
for (String parameterName : url.queryParameterNames()) {
List<String> queryParameterValues = url.queryParameterValues(parameterName);
if (queryParameterValues.size() > 1) {
String formattedValues= "[" + TextUtils.join(",", queryParameterValues) + "]";
request = request.newBuilder()
.url(
url.newBuilder()
.removeAllQueryParameters(parameterName)
.addQueryParameter(parameterName, formattedValues)
.build()
).build();
}
}
return chain.proceed(request);
}
non android solution
TextUtils is part of the Android SDK, in case you're not developing for Android you might exchange TextUtils.join for a method like this:
public static String concatListOfStrings(String separator, Iterable<String> strings) {
StringBuilder sb = new StringBuilder();
for (String str : strings) {
sb.append(separator).append(str);
}
sb.delete(0, separator.length());
return sb.toString();
}
}
You may also have a look at this SO question for more solutions regarding the concatenation.

unable to pass map to restful method

I am trying to call this API via postman:
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void printDetails(final MultivaluedMap<String, String> formParams) {
for(String key : formParams.keySet()) {
System.out.println(key + " " + formParams.get(key));
}
}
But the map turns out to be empty. Please help me with the same.
PS: This is the first time I am trying to pass variable number of parameters to the api. I have referred to
sending List/Map as POST parameter jersey and How to access parameters in a RESTful POST method.
I think my mistake is in the way I am passing the parameters in postman: postman image
Please help me with the same. Also please help with how to call this API via an ajax (in JS) call.
Set the request header as "application/x-www-form-urlencoded".
Request body - Select raw and provide values as mentioned below:-
{
"LOCATION": "Singapore"
}
I have found out one possible answer.
#POST
public void printDetails() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
Map<String, String[]> mapp = request.getParameterMap();
for(String key : mapp.keySet()) {
System.out.println(key + " " + mapp.get(key)[0]);
}
}
Still not sure how to do it by passing "final MultivaluedMap" in the arguments

Categories