I'm new to HtmlUnit (using version 2.30). Working in Eclipse on a Mac. I'm trying to create a stock data scraper by logging onto my Ameritrade account and manipulating the watch lists I've created there. First login form leads to the two-step security page where the challenge question is asked. I don't know why/how the site knows that it wants to challenge my userid/password in the first place. Because it looks like a new browser?
But anyway I fill out the form on the second page with the answer to the challenge question and submit. Instead of taking me to my account's home page, it once again takes me to the two-step security page with the same challenge question. Here is the relevant code:
final int sleepMinSeconds = 1;
final int sleepRandomSeconds = 4;
final long javascriptTimeout = 10000;
System.out.println("HtmlUnitTest");
String applicationName = "Mozilla";
String applicationVersion = "5.0 (Windows NT 6.3; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0";
final String userAgent = applicationName + "/" + applicationVersion;
BrowserVersion browserVersion = new BrowserVersion.BrowserVersionBuilder(BrowserVersion.FIREFOX_52)
.setApplicationName(applicationName)
.setApplicationVersion(applicationVersion)
.setUserAgent(userAgent)
.build();
WebClient webClient = new WebClient(browserVersion);
java.util.logging.Logger.getLogger("com.gargoylesoftware.htmlunit").setLevel(java.util.logging.Level.ALL);
java.util.logging.Logger.getLogger("org.apache.commons.httpclient").setLevel(java.util.logging.Level.ALL);
webClient.setIncorrectnessListener(new com.gargoylesoftware.htmlunit.IncorrectnessListener() {
#Override public void notify(String arg0, Object arg1) {}
});
webClient.setJavaScriptErrorListener(new com.gargoylesoftware.htmlunit.javascript.JavaScriptErrorListener() {
#Override public void timeoutError(HtmlPage arg0, long arg1, long arg2) {}
#Override public void scriptException(final HtmlPage arg0, final com.gargoylesoftware.htmlunit.ScriptException arg1) {}
#Override public void malformedScriptURL(HtmlPage arg0, String arg1, java.net.MalformedURLException arg2) {}
#Override public void loadScriptError(HtmlPage arg0, java.net.URL arg1, Exception arg2) {}
});
webClient.setCssErrorHandler(new com.gargoylesoftware.htmlunit.SilentCssErrorHandler());
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getOptions().setThrowExceptionOnScriptError(false);
webClient.getOptions().setDoNotTrackEnabled(true);
webClient.getOptions().setActiveXNative(true);
webClient.getOptions().setRedirectEnabled(true);
webClient.getOptions().setPrintContentOnFailingStatusCode(true);
webClient.getCookieManager().setCookiesEnabled(true);
webClient.getOptions().setDownloadImages(true);
String loginURL = "https://www.tdameritrade.com/home.page";
System.out.println("Connecting to " + loginURL + " (" + webClient.getBrowserVersion() + ")");
System.out.print(" Waiting to avoid being detected as a robot...");
Thread.sleep((long)(Math.random()*sleepRandomSeconds) * 1000);
System.out.print(" Done waiting.\n");
HtmlPage page = webClient.getPage(loginURL);
System.out.println("title text: " + page.getTitleText());
System.out.print(" \nWaiting for Javascript to complete...");
webClient.waitForBackgroundJavaScript(javascriptTimeout);
System.out.println("\nOK");
System.out.print(" Waiting to avoid being detected as a robot...");
Thread.sleep((long)(sleepMinSeconds + Math.random()*sleepRandomSeconds) * 1000);
System.out.print(" Done waiting.\n");
System.out.println("Logging in...");
HtmlForm form = page.getFormByName("form-login");
HtmlTextInput useridField = form.getInputByName("tbUsername");
HtmlPasswordInput passwordField = form.getInputByName("tbPassword");
useridField.type("<userid>");
passwordField.type("<password>");
HtmlButton button = form.getButtonByName("");
System.out.println("button value: " + button.getValueAttribute());
// Did this to make sure I had right button, which was unnamed.
// Value is "Log in", so I proceed.
HtmlPage page2 = button.click();
System.out.print(" \nWaiting for Javascript to complete...");
webClient.waitForBackgroundJavaScript(javascriptTimeout);
System.out.println("\nOK");
System.out.print(" Waiting to avoid being detected as a robot...");
Thread.sleep((long)(sleepMinSeconds + Math.random()*sleepRandomSeconds) * 1000);
System.out.print(" Done waiting.\n");
HtmlElement element = page2.getHtmlElementById("loginBlock");
HtmlForm form2 = element.getEnclosingForm();
HtmlPasswordInput challengeField = form2.getInputByName("challengeAnswer");
if(page2.asXml().contains("boss")) {
System.out.println("boss question...");
challengeField.type("<answer to boss question>");
}
else if(page2.asXml().contains("street")) {
System.out.println("street question...");
challengeField.type("<answer to street question>");
}
else {
System.out.println("What?");
}
HtmlCheckBoxInput checkBox = form2.getInputByName("rememberDevice");
checkBox.setChecked(true);
HtmlInput button2 = form2.getInputByName("mAction");
System.out.println("button2 value: " + button2.getValueAttribute());
// value here is "submit" - so I proceed
HtmlPage page3 = button2.click();
System.out.print(" \nWaiting for Javascript to complete...");
webClient.waitForBackgroundJavaScript(javascriptTimeout);
System.out.println("\nOK");
webClient.close();
In other words, page2 and page3 are the same, i.e., the two-step security page. I expected page3 to be my account's home page. (I confirmed this by writing them both out as XML to separate files.) I would appreciate any help I can get on this! Thanks!
OK, lets start with some comments to your code.
Not sure what you like to archive by using a different browser setup instead of using the build in default ones. There is nothing wrong with this but be aware that changing the browser setup will NOT have any effect on the browser behavior (e.g. the supported js functionality).
Second: if you are hunting for bugs/problems i fear it is a bad idea to disable all the listeners. It might be worth to have this listener output...
Regarding all the options: Why not starting with the default setup, it is really close to real browsers.
Now some words about the login process:
It will be a great help to try to understand the login process of the real application. All this 'modern' web applications are doing a lot of strange things (async/javascript) to simulate a rich ui on the not that rich platform.Tools like Charles WebProxy are of real help to get an idea of the communication done behind the scenes.
One common problem with this HtmlPage page3 = button2.click(); API is that the click method returns the sync result of the click. If the button is one of this fancy Ajax buttons, this is usually the page of the button itself. You are already waiting for the Ajax stuff to finish but the page will not change if there is a ajax redirect to a new page. In this case you have to do something like this after the wait call.
// there is an ajax redirect that loads a new page into this window
page3 = (HtmlPage) page.getEnclosingWindow().getEnclosedPage();
Hope that helps a bit...
Related
In my Vaadin Flow web app (version 14 or later), I want to present to my user a link that will download a data file to them. The default name of the file to be downloaded should be determined at the moment the user initiates the download.
I am aware of the Anchor widget in Vaadin Flow. With an Anchor, the default name for the downloaded file will be the resource name given in the URL of the link. Unfortunately, that is determined when the page loads rather than later when the user clicks the link. So this approach fails to meet my need to label the download with the date-time captured at the moment the user initiates the download.
String when = Instant.now().toString().replace( "-" , "" ).replace( ":" , "" ); // Use "basic" version of standard ISO 8601 format for date-time.
StreamResource streamResource = new StreamResource( "rows_" + when + ".txt" , ( ) -> this.makeContent() );
Anchor anchor = new Anchor( streamResource , "Download generated data" );
Perhaps the solution would be the use of a Button widget rather than an Anchor. Using a button for dynamically-created content is shown in the manual in the Advanced Topics > Dynamic Content page. Unfortunately, the example there loads a resource on the page rather than doing a download for the user.
➥ Can a Button in Vaadin Flow be used to initiate a download?
➥ Is there some other approach to initiating a download with a URL determined when the user initiates the download rather than when the page loads?
Can a Button in Vaadin Flow be used to initiate a download?
Sort of yes, but it requires some client side implementation. There is File Download Wrapper add-on in Directory, which does this. With it is possible to wrap e.g. Button. However I think it will not solve problem of yours fully. I suspect that the setting the filename in click event wont apply (it comes too late). But I do think, that it would be possible to add filename provider callback feature to this add-on.
Consider this HACK that simulates a click on a dynamically added Anchor on client-side:
private void downloadWorkaround(Component anyComponent, int delay) {
Anchor hiddenDownloadLink = new Anchor(createStreamResource(), "Workaround");
hiddenDownloadLink.setId("ExportLinkWorkaround_" + System.currentTimeMillis());
hiddenDownloadLink.getElement().setAttribute("style", "display: none");
var parent = anyComponent.getParent().orElseThrow();
var parenthc = (HasComponents) parent;
for (Component c : parent.getChildren().collect(Collectors.toList())) {
if (c.getId().orElse("").startsWith("ExportLinkWorkaround_")) {
// clean up old download link
parenthc.remove(c);
}
}
parenthc.add(hiddenDownloadLink);
UI.getCurrent().getPage().executeJs("setTimeout(function(){" + // delay as workaround for bug when dialog open before
"document.getElementById('" + hiddenDownloadLink.getId().orElseThrow() + "').click();"
+ "}, " + delay + ");"
);
}
Call the method on button click event or something. The additional delay is required sometimes. For me the delay was necessary to start the download from a modal dialog OK button that also closed the dialog. But perhaps you don't even need that.
I had no luck with the file download wrapper add-on mentioned by Tatu for my specific use case: I wanted to show a dialog under some circumstances before providing the download to user.
Based on the question of Clearing up Vaadin StreamResource after file download (which is partly the same as the answer of Steffen Harbich here in this question) I came to this solution (Vaadin 23):
Just provide a normal button with a normal clickHandler.
In the clickHandler you determine the file name, create a local StreamResource, add it to an invisible UI element and trigger a click event on this element.
Clickhandler:
private void doOnDownloadBtnClicked( Event e )
{
String filename = createFileName(); // implementation left to you
downloadFile( filename, this::inputStreamProvider );
}
InputStream provider:
private InputStream inputStreamProvider()
{
....
}
File download (may be extracted to an Utility class):
private final StreamResourceRegistry myStreamResourceRegistry;
private void downloadFile( String aFileName, InputStreamFactory aInputStreamFactory )
{
myStreamResourceRegistry = new StreamResourceRegistry(VaadinSession.getCurrent());
var executor = new DownloadExecutor( aInputStreamFactory );
var sr = new StreamResource( aFileName, executor );
StreamRegistration reg = myStreamResourceRegistry.registerResource( sr );
executor.myRegistration = reg;
var hiddenDownloadLink = new Anchor(sr, "Hidden");
var hiddenDownloadLinkId =
StringUtils.replaceChars( "DownloadLinkWorkaroundId-" + new SecureRandom().nextLong(),
'-',
'_' ) ;
hiddenDownloadLink.setId( hiddenDownloadLinkId);
var hiddenElement = hiddenDownloadLink.getElement();
executor.myHiddenElement = hiddenElement;
hiddenElement.setAttribute("style", "display: none");
var uiParent = UI.getCurrent().getElement();
executor.myParentElement = uiParent;
uiParent.appendChild(hiddenElement);
LOG.debug( "Going to simulate click event" );
UI.getCurrent().getPage().executeJs("$0.click();", hiddenElement );
}
/**
* Wrapper for the given InputStreamFactory.
* <p>
* It is needed to let Vaadin first give a chance to call the InputStream provider, before the
* temporary added hidden anchor is removed and the StreamRegistration is unregistered.
*/
private static final class DownloadExecutor implements InputStreamFactory
{
private InputStreamFactory myInputStreamFactory;
private StreamRegistration myRegistration;
private Element myHiddenElement;
private Element myParentElement;
private DownloadExecutor( InputStreamFactory aInputStreamFactory )
{
super();
myInputStreamFactory = aInputStreamFactory;
}
#Override
public InputStream createInputStream()
{
var result = myInputStreamFactory.createInputStream();
myParentElement.removeChild( myHiddenElement );
myRegistration.unregister();
return result;
}
}
Note: If the above fileDownload is extracted to an own helper class (e.g. a spring bean with #SessionScope), the initialization of myStreamResourceRegistry should be done in the constructor of the bean.
I have a preview button. When user press preview, form is submitted on new tab to show a pdf file have data in form.
I use a custom SubmitLink to do that
SubmitResourceLink
public abstract class SubmitResourceLink extends SubmitLink implements IResourceListener {
private final IResource resource;
#Override
public final void onResourceRequested() {
Attributes a = new Attributes(RequestCycle.get().getRequest(), RequestCycle.get().getResponse(), null);
resource.respond(a);
}
Implement on form
new SubmitResourceLink("previewBtn", form, new JasperReportsResource() {
private static final long serialVersionUID = -2596569027102924489L;
#Override
public byte[] getData(Attributes attributes) {
return control.getExportPreviewByteStream(estimateModel.getObject());
}
}) {
private static final long serialVersionUID = 1L;
#Override
protected String getTriggerJavaScript() {
String js = super.getTriggerJavaScript();
js = "document.getElementById('" + form.getMarkupId() + "').target='_blank';" + js;
return js;
}
#Override
public void onSubmit() {
form.add(AttributeModifier.append("target", Model.of("_blank")));
processInputs(form);
onResourceRequested();
}
}.setDefaultFormProcessing(false);
When I press preview, a new tab is opend. But when I input in any ajax component (ex:AutoCompleteTextField), ajax reponse data xml: <ajax-response><redirect>....</redirect></ajax-response> and refresh page.
Now, I want after press preview, I still use current form normaly.
Thank.
This is caused by the "stale page protection" in Wicket.
The first click opens the same page instance in a new tab/window. This increments the page's renderCount counter, i.e. it says "this page has been rendered N times".
The links in Wicket look like ?2-1.ILinkListener-component~path. Here '2' is the page id and '1' is the page render count.
So the links in tab1 have renderCount 'N', and the links in tab2 - 'N+1'.
Clicking on a link in tab1 will fail with StalePageException that tells Wicket "the user is trying to use an obsolete version of the page. Please render the latest version of the page so the user can try again".
This protection is needed because the user may do many actions in tab3, e.g. replace a panel that replaces/hides the link the user wants to click in tab1. If there is no such protection Wicket will either fail with
ComponentNotFoundException while trying to click the Link or even worse can do the wrong action if the Link/Button is in a repeater and the repeater has changed its items in tab2.
To overcome your problem you should open a new page instance in tab2, i.e. it submits the form but in onSubmit() does something like setResponsePage(getPage().getClass()). This way it won't re-render the current page instance N+1 time.
Scenario
Lotus Domino form with a button that made an Ajax call to an Xpage, that do some stuff (read a properties file).
Framework: prototype.js
Button code:
var now = new Date()
var n = $H({
........
now: now.getTime()
});
var url = "/" + $F("path") + "/myxpages.xsp";
var myAjax = new Ajax.Request(
url,
{
method: 'post',
parameters: n.toQueryString(),
onComplete: function(response) {
ajaxResult = response.responseText;
}
});
Xpage
myxpages.xsp has this SSJS code on afterPageLoad event
var request = facesContext.getExternalContext().getRequest();
var response = facesContext.getExternalContext().getResponse();
response.setHeader("Expires", -1);
response.setHeader("Cache-Control", "no-cache");
com.org.MyGetProperties.readProperties(request,response);
MyGetProperties class
This class is deployed in WebContent/WEB-INF/classes
public class MyGetProperties {
static PrintWriter out = null;
public static synchronized void readProperties(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
*(DO SOME STUFF HERE)*
out = new PrintWriter(response.getWriter());
// return result
out.println("OK");
} catch (Exception e) {..}
}
}
Sometimes ajaxResult variable in ajax response call is empty, sometimes is "OK", as expected (seems to be something related to cache, but i think i've managed it correctly).
The behavior is different on different production server, i don't know if depends on server configurations.
Could be a PrintWriter problem?
Short answer: don't. Long answer: use the Ajax control. You put that on your page and your URL changes to myxpages.xsp/nameyougavetheajaxcontrolproperty
This way you can be sure not to run foul of any cached result or pending operation. It also has a property where you can specify a Java class directly. That class extends (need to Google that - answered it on SO before) which gives you direct access to request/response
Update:
You need to tell that you are done:
facesContext.responseComplete();
See my original post on XAgents, the revision and some thought on testing.
In Spring I usually did a redirect-after-submit(get method mapping) to prevent the user of submitting a form ,
but when i pressing F5 it will go to get method mapping again and display me this kind of confirm message. how could i prevent this message every time on F5.
Here is the code for controller -
ScheduleDetail objScheduleDetail = new ScheduleDetail();
HttpSession session = request.getSession(true);
String condition = "";
try{
int careProfessionalIDF = (Integer) session.getAttribute("careProfessionalIDF");
condition = "CareProfessionalIDF = "+careProfessionalIDF;
objScheduleDetail.setCareProfessionalIDF(careProfessionalIDF);
}catch (Exception e) {
int careProviderIDF = (Integer) session.getAttribute("careProviderIDF");
condition = "CareProviderIDF = "+careProviderIDF;
objScheduleDetail.setCareProviderIDF(careProviderIDF);
}
List<ScheduleDetail> ScheduleDetailList = objScheduleDetailManager.getAllScheduleDetail(condition+" ORDER BY ScheduleDetailIDP DESC");
model.addObject("List_of_ScheduleDetail",ScheduleDetailList);
model.addAttribute("ScheduleDetail", objScheduleDetail);
return "hospital/scheduleDetail";//jsp page
edited code
#RequestMapping("/editAddressType.html")
public String editAddressType(ModelMap model,HttpServletRequest request)
{
int addressTypeIDP = Integer.parseInt(request.getParameter("AddressTypeIDP"));
AddressType objAddressType = new AddressType();
objAddressType = objAddressTypeManager.getByID(addressTypeIDP);
model.addAttribute("AddressType", objAddressType);
return "jsp/addressType";
it open addressType.jsp with data tht we bind with `model.addAttribute`. now if i press F5 it show alert message as above image.
**get method**
#RequestMapping(value="/getAddressType.html", method=RequestMethod.GET)
public String getAddressType(ModelMap model, HttpServletRequest request) throws RemoteException
{
AddressType objAddressType = new AddressType();
model.addAttribute("AddressType", objAddressType);
return "hospital/addressType";
}
If you implement the POST - REDIRECT - GET pattern correctly, you will not see the warning from the browser that you mentioned. The warning is shown when the Page being viewed is in response to a POST request. The traditional pattern, FORM - POST - SUCCESS page.
The code you provided in the question is not enough to reach the root cause of the problem. I'm listing key points of the implementation here, please compare with your code and you'll understand what the mistake is.
To show the user the form, where they are supposed to enter data for submission. (Just the starting point, could be any page in your application.)
#RequestMapping(value = "/checkout/{cartId}.htm", method = RequestMethod.GET)
public ModelAndView showCheckoutForm(...) {
......
return new ModelAndView("/WEB-INF/jsp/form.jsp")
}
The form POSTs to a handler method, which issues a redirect to the user, pointing to a URL that will show the details of the resource created as a result of the POST.
#RequestMapping(value = "/checkout/{cartId}.htm", method = RequestMethod.POST)
public View submitCheckoutForm(....) {
return new RedirectView("/checkout/confirmation/" + cartId + ".htm");
}
The details of the created resource will be shown by a handler method like the following. Note that at this point, if your implementation worked properly, the URL in the user's browser will change to the path redirected by the POST handler. And a fresh GET request will be issued to the server.
#RequestMapping(value = "/checkout/confirmation/{cartId}.htm", method = RequestMethod.GET)
public ModelAndView showCheckoutConfirmation(....) {
return new ModelAndView("viewThatShowsCheckoutConfirmation");
}
If your implementation is similar, the confirmation page is a result of a GET request, so browsers won't issue a warning for re-POSTing data if you refresh the page.
My suspicion is that your POST handler is not issuing a redirect to the user, it is instead rendering the confirmation page itself.
Hope this helps. Please let me know if this does not solve your problem.
I have been following Jesse Liberty's tutorial on MVVM Light for Windows Phone 7, but I'm stuck on this problem. I need to navigate from a main page to a detail page. Following the tutorial, I'm using a RelayCommand in the MainViewModel:
public RelayCommand<Customer> DetailsPageCommand { get; private set;}
I then initialize it in the constructor:
DetailsPageCommand = new RelayCommand<Customer>((msg) => GoToDetailsPage(msg));
Finally you implement the GoToDetailsPage method:
private object GoToDetailsPage(Customer msg)
{
System.Windows.MessageBox.Show("Go to details page with: " +
msg.First +
" " +
msg.Last );
return null;
}
Showing the message box works, but I'm not sure how to navigate to the detail page instead. In previous sections of the tutorial page navigation was handled with something like this:
var msg = new GoToPageMessage {PageName = "DetailPage"};
Messenger.Default.Send(msg);
You'll need to register to receive messages of that type and then navigate appropriately.
The following assumes a page name and that you're navigating to details of the specific customer by passing their Id in the query string.
Messenger.Default.Register<Customer>(
this,
c => NavigationService.Navigate("/Pages/CustomerDetails.xaml?cid=" + c.Id));
You'd then adjust your code accordingly:
private void GoToDetailsPage(Customer msg)
{
Messenger.Default.Send(msg);
}
I hope this helps.