camel exception redelivery with hystrix - apache-camel

I'm trying to configure a camel route so that it will retry when an endpoint within hystrix fails, in this case "direct:second". I've created a very simple camel route (see below). If I comment out hystrix in the route then everything works as expected in that it will retry on a failure. Any idea how I can get retry to work with hystrix?
from(URI)
.errorHandler(deadLetterChannel(format("log:%s?level=ERROR", MyClass.class.getName())))
.onException(Throwable.class)
.maximumRedeliveries(5).redeliveryDelay("1000")
.to("log:error?showCaughtException=true")
.end()
.to("direct:first")
.hystrix()
.hystrixConfiguration()
.end()
.to("direct:second")
.endHystrix()
.to("direct:third")
.to("log:success?level=INFO");
// ENDPOINTS
#Consume(uri="direct:first")
public String first(String payload) {
return payload + " first";
}
#Consume(uri="direct:second")
public String second(String payload) {
Random rand = new Random();
if ((rand.nextInt() % 3) == 0) {
throw new RuntimeException("msg");
}
return payload + " second";
}
#Consume(uri="direct:third")
public String third(String payload) {
return payload + " third";
}

Related

CsrfFilter always fires exception in Spring 6.0+

In the new Spring Boot 3.0.1 I'm adding support for CSRF protection by adding this simple configuration as per Spring documentation:
http.csrf().csrfTokenRepository(tokenRepository);
and
#Bean
public CsrfTokenRepository tokenRepository() {
var tokenRepo = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepo.setCookiePath("/");
return tokenRepo;
}
On the front-end side, there is an Angular app with import of the standard library
import {HTTP_INTERCEPTORS, HttpClientModule, HttpClientXsrfModule} from '#angular/common/http';
However I cannot make it work with the standard workflow, like
Execute request to get XCSRF-TOKEN cookie from the server.
Extract the token value from the cookie.
Add X-XSRF-TOKEN header with the extract value from the cookie.
The front-end sends both cookie and header with the same token value and it fails in check:
public final class XorCsrfTokenRequestAttributeHandler ...
private static String getTokenValue(String actualToken, String token) {
byte[] actualBytes;
try {
actualBytes = Base64.getUrlDecoder().decode(actualToken);
}
catch (Exception ex) {
return null;
}
byte[] tokenBytes = Utf8.encode(token);
int tokenSize = tokenBytes.length;
if (actualBytes.length < tokenSize) {
return null;
}
The methods always returns null for
if (actualBytes.length < tokenSize) {
return null;
}
The only way to make it work is to return encoded string to be pasted in the header directly by adding endpoint, like this:
DeferredCsrfToken deferredCsrfToken = repository.loadDeferredToken(request, response);
requestHandler.handle(request, response, deferredCsrfToken::get);
CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
return csrfToken.getToken();
So the returned value to be used in header looks like:
ab5tfqabXomGPuLjDQk96mVMZHNOh_JnpPVM4F_hQU1sOMyWW9sLSsCtb72rWNPabyQJ01EpSUsvtctKwcQphWeAJX9ZDPmg
instead of
5adaf830-40e6-43c9-b42a-e36fd713c1a6
Any advice on what I'm missing here?

Axios POST request passing an empty object to Spring REST API end doing

I am in the middle of learning react/redux and decided to take an old JQuery UI that makes request to Spring REST API and rewrite it in React. I am using Axios to make the request. In my old Query UI , I do the following when a form is submitted.
var formInputs = $(form).serialize();
$.post("/addAttrItem", formInputs, function(updated){
refresh();
showRecord(updated);
displayControlMsg("Record below was added successfully");
}
This is handled by the following code below in Spring
#ResponseBody
#RequestMapping(value="/someURL", method=RequestMethod.POST)
public AttrItem restfulAdd(AttrItem item) throws Exception
{
item.setLastUpdate(new java.util.Date());
itemService.create(item);
return item;
}
When sending the request through JQuery, AttrItem item param populated with all right values sent in by JQuery
However when I try the following axios
axios.post(someUrl, data).then
(res => {
dispatch(addAttributeSync(res));
}).catch(error =>{
alert('add item failed ' + error);
}
the AttrItem item param while not null itself, is empty with none of the fields set to values from the form. I confirmed that the data object contains right data prior to the post request.
See if mapping the HTTP request body to the method argument item using #RequestBody annotation helps.
#ResponseBody
#RequestMapping(value="/someURL", method=RequestMethod.POST)
public AttrItem restfulAdd(#RequestBody AttrItem item) throws Exception
{
item.setLastUpdate(new java.util.Date());
itemService.create(item);
return item;
}
The following seems to have resolved the issue. In React I added header config
return dispatch => {
var config = {
headers: { 'Content-Type': 'application/json' },
};
axios.post(someUrl, data).then
(res => {
dispatch(addAttributeSync(res));
}).catch(error =>{
alert('add item failed ' + error);
}
And I modified my Spring Controller endpoint to set the consumes and produces attribute as follows.
#ResponseBody
#RequestMapping(value="/attributeItem", method=RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public AttributeItem restfulAdd(#RequestBody AttributeItem attributeItem) throws Exception
{
attributeItem.setLastUpdate(new java.util.Date());
attributeItemService.create(attributeItem);
return attributeItem;
}

Setting up endpoints (Camel)

I am new to Camel and learning to setup routes.
So I started with a simple scenario, a URL that I hit and it returns me some data. For this example I have used http://services.groupkt.com/country/get/all for returning that data.
This is the setup for my path
from("direct:greet")
.autoStartup(true)
.routeId("greet")
.setHeader(Exchange.HTTP_METHOD, constant(HttpMethods.GET))
.to("http4://services.groupkt.com/country/get/all")
Now I have requestMapping to a URL /check and when I hit this URL http://localhost:8080/check it returns this
{
"timestamp": 1527882311362,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/check"
}
I was expecting the JSON response to show up with the data for all the countries listed which you see when you hit the URL(http://services.groupkt.com/country/get/all) in your browser.
The mapping is in other class:
#RequestMapping(value = "/check", method = RequestMethod.GET)
public String get(#RequestParam(value = "name") String name) {
return serviceProcessor.getServiceResponse(name);
getServiceResponse goes as follows:
public String getServiceResponse(String name) {
final ModelCamelContext context = userServiceRoute.getContext();
final ProducerTemplate template = new DefaultProducerTemplate(context);
try {
template.start();
} catch (Exception e) {
LOGGER.error("Error starting producerTemplate with userServiceRoute" + e);
}
final Endpoint endpoint = context.getEndpoint("direct:greet");
template.setDefaultEndpoint(endpoint);
return template.requestBody((Object)name, String.class);
}
Is there something wrong with the path setup or the approach itself is wrong here?

Error 404 when calling Google Cloud Endpoint API from Google Apps Script

I am trying to call a Google Cloud Endpoint API (developed on App Engine) via Google Apps Script. The endpoint is up and running, honestly I don't know which URL I should use but through Google Chrome Web Tools it looks like the URL is something like:
https://myapp.appspot.com/_ah/api/myendpointapi/v1/myEndPointMethod/
Along with API parameters directly included in the URL, separeted by slashes:
https://myapp.appspot.com/_ah/api/myendpointapi/v1/myEndPointMethod/param1value/param2value/...
Now, in order to call that API from Google App Script I am using the following code snippet:
function myFunction() {
var params =
{
"param1" : "param1value",
"param2" : "param2value",
};
var result = UrlFetchApp.fetch('https://myapp.appspot.com/_ah/api/myendpointapi/v1/myEndPointMethod/', params);
DocumentApp.getUi().alert(result);
}
However I always get a 404 error. If I have to be honest I don't even know if UrlFetchApp is the correct way of calling the API. I noticed this thread on StackOverflow but no one answered. What's the correct URL to use? Many thanks.
EDIT: Now I am trying with an API method which does not require any parameter. I found a way to call a specific URL (using method='get' as suggested by the answer below) but now I get a 401 error because it says I am not logged in. I believe I need to use some kind of OAuth parameter now. Any idea? I tryed using OAuthConfig but no luck with that as well :( From App Engine logs I can see the following error:
com.google.api.server.spi.auth.GoogleIdTokenUtils verifyToken: verifyToken: null
com.google.api.server.spi.auth.AppEngineAuthUtils getIdTokenEmail:
getCurrentUser: idToken=null
function myFunction() {
var result = UrlFetchApp.fetch('myurl', googleOAuth_());
result = result.getContentText();
}
function googleOAuth_() {
var SCOPE = 'https://www.googleapis.com/auth/drive';
var NAME = 'myAPIName';
var oAuthConfig = UrlFetchApp.addOAuthService(NAME);
oAuthConfig.setRequestTokenUrl('https://www.google.com/accounts/OAuthGetRequestToken?scope='+SCOPE);
oAuthConfig.setAuthorizationUrl('https://www.google.com/accounts/OAuthAuthorizeToken');
oAuthConfig.setAccessTokenUrl('https://www.google.com/accounts/OAuthGetAccessToken');
oAuthConfig.setConsumerKey('anonymous');
oAuthConfig.setConsumerSecret('anonymous');
return {oAuthServiceName:NAME, oAuthUseToken:'always'};
}
UrlFetchApp is the only way to call a Google Cloud Endpoints API at the moment. The second parameter to UrlFetchApp.fetch is a special key-value map of advanced options. To pass POST parameters, you need to do the following:
UrlFetchApp.fetch(url, {
method: 'post',
payload: {
"param1" : "param1value",
"param2" : "param2value",
}
});
I was fighting a similar (not the same) problem, when testing feasibility of a GCM backed by EndPoints server. Basically testing if it is possible to get the Google Spreadsheet Appscript to send notification to an Android device. Please bear with me, the following explanation may be a bit convoluted;
Starting with a standard 'Cloud Messaging for Android', backed by the 'App Engine Backend with Google Cloud Messaging', I managed to build a test system that would send messages between Android devices (Github here).
Here is a VERY sparse EndPoints server code that handles register / un-register Android devices, as well as reporting registered devices and sending a message to a list of registered devices.
WARNING: This is not a production quality code, it is stripped of any logging, error handling in order to keep it short.
#Api( name = "gcmEP", version = "v1",
namespace = #ApiNamespace(ownerDomain = "epgcm.example.com", ownerName = "epgcm.example.com", packagePath = "" )
)
public class GcmEP {
#ApiMethod(name = "registToken")
public void registToken(#Named("token") String token) {
if (ofy().load().type(TokenRec.class).filter("token", token).first().now() == null) {
ofy().save().entity(new TokenRec(token)).now();
}
}
#ApiMethod(name = "unregToken")
public void unregToken(#Named("token") String token) {
TokenRec record = ofy().load().type(TokenRec.class).filter("token", token).first().now();
if (record != null) {
ofy().delete().entity(record).now();
}
}
#ApiMethod(name = "listTokens")
public CollectionResponse<TokenRec> listTokens() {
return CollectionResponse.<TokenRec>builder().setItems(ofy().load().type(TokenRec.class).list()).build();
}
#ApiMethod(name = "sendMsg")
public void sendMsg(#Named("message") String message) throws IOException {
if (message != null && message.length() > 0) {
Sender sender = new Sender(System.getProperty("gcm.api.key"));
Message msg = new Message.Builder().addData("message", message).build();
for (TokenRec record : ofy().load().type(TokenRec.class).list()) {
Result result = sender.send(msg, record.getToken(), 4);
if (result.getMessageId() != null) {
// handle CanonicalRegistrationId
} else {
// handle errors, delete record
}
}
}
}
}
Android code for registration and message sending is shown here, even if it is not relevant.
GcmEP mRegSvc;
String mToken;
// register device on EndPoints backend server
private void registerMe() {
new Thread(new RegisterMe(this)).start();
}
private class RegisterMe implements Runnable {
Activity mAct;
public RegisterMe(Activity act) { mAct = act; }
public void run() {
String senderId = null;
if (mAct != null) try {
if (mRegSvc == null) {
mRegSvc = new GcmEP
.Builder(AndroidHttp.newCompatibleTransport(), new AndroidJsonFactory(), null).setRootUrl(UT.ROOT_URL).build();
}
senderId = getString(R.string.gcm_defaultSenderId);
mToken = InstanceID.getInstance(mAct).getToken(senderId, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
mRegSvc.registToken(mToken).execute();
GcmPubSub.getInstance(mAct).subscribe(mToken, "/topics/global", null); // subscribing to all 'topics' from 'mToken'
} catch (IOException e) { e.printStackTrace(); }
}
}
// send message to EndPoints backend server
new Thread(new Runnable() {
#Override
public void run() {
if (mRegSvc != null) try {
mRegSvc.sendMsg("hello").execute();
} catch (IOException e) { e.printStackTrace(); }
}
}).start();
// receive GCM message
public class GcmListenSvc extends GcmListenerService {
#Override
public void onMessageReceived(String senderId, Bundle data) {
Log.i("_X_", data.getString("message"));
}
}
What is relevant, thought, there is also an APIs Explorer created for the project, that can be used to send messages to your Android device from any browser.
If you use this Explorer, you can see the GET, POST requests for your EndPoints backend server, i.e.
list all registered devices:
GET https://epgcm.appspot.com/_ah/api/gcmEP/v1/tokenrec?fields=items
send a message to all registered devices:
POST https://epgcm.appspot.com/_ah/api/gcmEP/v1/sendMsg/Hello%20World!
Now, you can use this knowledge to send messages to your Android device from an AppScript code as shown:
Version 1: Get list of registered devices and send a GCM message to all of them (or a filtered set).
function sendMsg() {
var msg = 'test from CODE.GS';
var url = 'https://epgcm.appspot.com/_ah/api/gcmEP/v1/tokenrec?fields=items';
var params = { method : 'get'};
var response = UrlFetchApp.fetch(url, params);
var data = JSON.parse(response.getContentText());
var regIds = [];
for (i in data.items)
regIds.push(data.items[i].token);
var payload = JSON.stringify({
'registration_ids' : regIds,
'data' : { 'message' : msg }
});
var params = {
'contentType' : 'application/json',
'headers' : {'Authorization' : 'key=AIza............................'},
'method' : 'post',
'payload' : payload
};
url = 'https://android.googleapis.com/gcm/send';
UrlFetchApp.fetch(url, params);
}
This version relies on code from an old YouTube video, and I don't know if the call to 'android.googleapis.com' is still supported (but it works).
Version 2: Use the EndPoints's 'sendMsg' directly.
function sendMsg() {
var msg = 'test from CODE.GS';
var params = { method : 'post'};
var url = 'https://demoepgcm.appspot.com/_ah/api/gcmEP/v1/sendMsg/' + encodeURIComponent(msg.trim());
UrlFetchApp.fetch(url, params);
}
I have to admit I've never written a line of JavaScript code before, so it may not be up-to-par, but I made it work as a 'proof of concept'.
I would like to get feedback about this problem from people-who-know, since there is so little published info on this specific issue.

How to upload file on GAE with GWT?

I've been trying this for hours, so maybe a fresh set of eyes will help.
I'm trying to upload a file to the server using GWT (using UiBinder) on Google App Engine. Following all the examples I can find, things look like they should work, but the server's 'post' method is showing 0 items uploaded.
Here's the client code:
FormUploader.ui.xml:
<g:FormPanel action="/formImageUploader" ui:field="formPanel">
<g:VerticalPanel>
<g:FileUpload ui:field="uploader"></g:FileUpload>
<g:Button ui:field="submit">Submit</g:Button>
</g:VerticalPanel>
</g:FormPanel>
FormUploader.java:
#UiField
FormPanel formPanel;
#UiField
FileUpload uploader;
#UiField
Button submit;
public FormUploader() {
initWidget(uiBinder.createAndBindUi(this));
formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
formPanel.setMethod(FormPanel.METHOD_POST);
addSubmitHandlers();
}
private void addSubmitHandlers() {
formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() {
#Override
public void onSubmitComplete(SubmitCompleteEvent event) {
Core.log("Inside submitComplete");
Window.alert(event.getResults());
}
});
}
web.xml:
<servlet>
<servlet-name>formImageUploader</servlet-name>
<servlet-class>com.company.server.FormImageUploader</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>formImageUploader</servlet-name>
<url-pattern>/formImageUploader</url-pattern>
</servlet-mapping>
FormImageUploader.java:
public class FormImageUploader extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException {
log.info("Request.getContentLength(): " + request.getContentLength());
log.info("Content type: " + request.getContentType());
log.info("Parm names: ");
for (Object paramName : request.getParameterMap().keySet()) {
log.info("__Param name: " + paramName.toString());
}
log.info("Attribtue names: ");
Enumeration enu = request.getAttributeNames();
while (enu.hasMoreElements()) {
log.info("__Attribute name: " + enu.nextElement().toString());
}
log.info("Header names: ");
enu = request.getHeaderNames();
while (enu.hasMoreElements()) {
log.info("__Header name: " + enu.nextElement().toString());
}
resp.getWriter().println("Post Code has finished executing");
}
All this executes just fine, the client shows an alert box containing Post Code has finished executing, but I can't figure out how to get the content of the file I want to upload.
Here's the server side log:
Request.getContentLength(): 63
Content type: multipart/form-data; boundary=---------------------------194272772210834546661
Parm names:
Attribtue names:
__Attribute name: com.google.apphosting.runtime.jetty.APP_VERSION_REQUEST_ATTR
Can anyone help me see why I'm not seeing any files on the server after clicking "submit" on the client?
Thanks in advance
Edit:
Still can't figure out why I can't get the form data on the server. I'm following this example, and just trying to write the response out to the client, but the server is definitely not seeing any content from the client.
Server code:
try {
log.info("Inside try block");
ServletFileUpload upload = new ServletFileUpload();
log.info("created the servlet file upload");
res.setContentType("text/plain");
log.info("set the content type to text/plain");
FileItemIterator iterator = upload.getItemIterator(req);
log.info("Got the iterator for items");
while (iterator.hasNext()) {
log.info("__inside iterator.hasNext");
FileItemStream item = iterator.next();
log.info("__assigned the file item stream");
InputStream stream = item.openStream();
log.info("__opened said stream");
if (item.isFormField()) {
log.warning("Got a form field: " + item.getFieldName());
} else {
log.warning("Got an uploaded file: " + item.getFieldName() +
", name = " + item.getName());
int len;
byte[] buffer = new byte[8192];
while ((len = stream.read(buffer, 0, buffer.length)) != -1) {
res.getOutputStream().write(buffer, 0, len);
}
log.info("Done writing the input to the output stream");
}
}
} catch (Exception ex) {
log.severe("Exception; " + ex);
throw new ServletException(ex);
}
log.info("Done parseInput3");
Logs:
Inside parseInput3
Inside try block
created the servlet file upload
set the content type to text/plain
Got the iterator for items
Done parseInput3
Again, it's definitely not able to iterate over the file items on the server... any idea why?
Turns out, GAE has a completely different way of uploading files (Blobs):
http://code.google.com/appengine/docs/java/blobstore/overview.html#Uploading_a_Blob
You need to supply the form's on the client with a Blobstore Upload URL (which times out) from the server, retrieved using:
blobstoreService.createUploadUrl('urlToRedirectToAfterUploadComplete')
After setting that upload URL on the form's action, the form will be submitted and the blobstore service will redirect to the supplied URL, which can then access the BlobKey of the object stored by Google.
So with this, I added the following ChangeHandler to my GWT FileUpload:
private void addChangeListener() {
Core.log("Selected file: " + uploader.getFilename());
uploader.addChangeHandler(new ChangeHandler() {
#Override
public void onChange(ChangeEvent event) {
MyApp.SERVICES.getUploadUrl(new SuccessAsyncCallback<String>() {
#Override
public void onSuccess(String uploadUrl) {
form.setAction(uploadUrl);
form.submit();
Core.log("Submitted form with upload url: " + uploadUrl);
}
});
}
});
}
Then in the servlet I have the GAE redirect to after uploads:
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception {
BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(req);
// where 'uploader' is the name of the FileUploader (<input type='file'>) control on the client
List<BlobKey> blobKeys = blobs.get("uploader");
resp.getOutputStream().print(blobKeys.get(0).getKeyString());
}
Note!
GAE will throw a OutOfMemory exception when trying to get the uploaded blob's value if the FileUpload control doesn't have a name="whateveryouwantthenametobe" attribute defined, even if you tell it (using UiBinder) that the id is ui:field="nameofControl"
You cannot write to the local file system - you should take a look at the files api.

Resources