I'm working in Scala with Gatling.
I need to follow a redirection found in a HTML page, sent as a response for my first request (so the automatic redirection following of Gatling doesn't do the trick).
Right now my solution looks a bit like this:
private def scn: ScenarioBuilder = {
scenario("KLS test tool")
.repeat(nb_req) {
feed(feeder)
.exec {
http("request").
httpRequest("GET", "${url}")
.check(bodyString.saveAs("body"))
}
.exec {
session => {
val responseBody = session.attributes("body").toString
val redirect = findRedirectUrlIn(responseBody)
redirect match {
case Some(url) =>
java.lang.System.setProperty("redirect_url", url)
case None =>java.lang.System.setProperty("redirect_url", "")
}
}
}
.exec {
val redirect = java.lang.System.getProperty("redirect_url")
val url = "/redirect?url=" + redirect
http("redirect").
httpRequest("GET", url)
}
}
}
However, the second exec (which is supposed to scan the HTML page to find the URL to redirect to) is being run after the third exec (which is supposed to actually visit the URL), so the getProperty gives me "null"
How could I force Gatling to run the execs in the right order?
I have similar task, but did in another way... What if disable redirects?
Just add disableFollowRedirect to your HttpProtocolBuilder:
val protocol = http
.baseUrl(...)
.disableFollowRedirect
Then I made request which redirected and save to redirect_url
val getUrl: HttpRequestBuilder = http("getShortUrl")
.get("${shortUrl}")
.check(
status.is(302),
header("Location").saveAs("redirect_url")
)
What you're doing is wrong.
First, System properties are non thread safe and global. You need a user scope, which is the Session.
Then, in your 3rd exec, your code is executed when the Simulation is instantiated as it's not in a function like the 2nd one.
.exec { session =>
val redirect = findRedirectUrlIn(session("body").as[String])
session.set("redirect_url", redirect.getOrElse(""))
}
.exec(http("redirect").get("${redirect_url}"))
Related
I am trying to develop an app for my fantasy baseball league to use for our draft (we some kind of quirky stuff all the major sites don't account for) - I want to pull some player data to use for the app by using MLB's API. I have been able to get the response from MLB, but can't do anything with the data after I get it back. I am trying to store the JSON into an array, and if I console.log the array as a whole, it will give me the entire chunk of data, but if I try to call the specific index value of the 1st item, it comes back as undefined.
let lastName = 'judge';
let getData = new XMLHttpRequest;
let jsonData = [];
function getPlayer () {
getData.open('GET', `http://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, true)
getData.onload = function() {
if (this.status === 200) {
jsonData.push(JSON.parse(this.responseText));
}
}
getData.send();
console.log(jsonData);
}
When I change the above console.log to console.log(jsonData[0]) it comes back as undefined. If I go to the console and copy the property path, it displays as [""0""] - Either there has to be a better way to use the JSON data or storing it into an array is doing something abnormal that I haven't encountered before.
Thanks!
The jsonData array will be empty after calling getPlayer function because XHR loads data asynchronously.
You need to access the data in onload handler like this (also changed URL to HTTPS to avoid protocol mismatch errors in console):
let lastName = 'judge';
let getData = new XMLHttpRequest;
let jsonData = [];
function getPlayer () {
getData.open('GET', `https://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, true)
getData.onload = function() {
if (this.status === 200) {
jsonData.push(JSON.parse(this.responseText));
// Now that we have the data...
console.log(jsonData[0]);
}
}
getData.send();
}
First answer from How to force a program to wait until an HTTP request is finished in JavaScript? question:
There is a 3rd parameter to XmlHttpRequest's open(), which aims to
indicate that you want the request to by asynchronous (and so handle
the response through an onreadystatechange handler).
So if you want it to be synchronous (i.e. wait for the answer), just
specify false for this 3rd argument.
So, you need to change last parameter in open function as below:
getData.open('GET', `http://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, false)
But from other side, you should allow this method to act asynchronously and print response directly in onload function.
In a custom component I am making API calls. If the API call returns 403 I want to logout the user and redirect to the login. With the following code I get a response object without knowing if the response is a redirect or if the response is containing the data of the request. Besides get I have also other methods implemented in the Component so that I have at the end over 50 times a call to the RestAPIComponent.
Calling the RestAPIComponent
public function view($id)
{
$resource = $this->__getSingularResourceName();
$$resource = $this->RestApi->get($id)->json;
$this->set(compact($resource));
}
RestAPIComponent
public function get($id = null, array $query = [], $action = null)
{
$path = (is_null($id) === false) ? $id : '';
$response = $this->_http->get($path . '/' . $action, $query, $this->_getAuthHeader());
return $this->_handleResponse($response);
}
private function _handleResponse(Response $response)
{
if ($response->statusCode() == 403) {
$this->Cookie->delete(TOKEN);
$this->Cookie->delete(USER);
$controller = $this->_registry->getController();
return $controller->redirect($controller->Auth->logout());
} else {
return $response;
}
}
There may be following reasons behind it, getting the 403 error using auth component --
1.It is possible to get a 403 via code. Check this out from the CakePHP docs (http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#handling-unauthenticated-requests): If authenticator returns null, AuthComponent redirects user to login action. If it’s an ajax request and AuthComponent::$ajaxLogin is specified that element is rendered else a 403 http status code is returned.
2.Multiple Ajax calls shouldn't be the causing factor of a 403 error.
3.The standard routing is handled by CakePHP itself. If you need some different routing, you should configure this in routes.php. I would say using .htaccess is only for a really extreme routing need and should be a last resort.
4.Yes that could be a cause, since you would no longer be logged in, thus get Auth 403s
For more detail -- you could visit the link Common reasons behind 403 errors
I'm not sure if I'm doing something wrong or there's a bug of some kind. If it is a bug, then I'd think this would have cropped up immediately. Either way:
I'm using the extension method RequiresAuthentication from here:
https://github.com/NancyFx/Nancy/blob/9d3c650575cba109e620ab163f4fda26a0536036/src/Nancy/Security/ModuleSecurity.cs
Which uses SecurityHooks
https://github.com/NancyFx/Nancy/blob/9d3c650575cba109e620ab163f4fda26a0536036/src/Nancy/Security/SecurityHooks.cs
Or the code specifically:
public static void RequiresAuthentication(this INancyModule module)
{
module.AddBeforeHookOrExecute(SecurityHooks.RequiresAuthentication(), "Requires Authentication");
}
SecurityHooks:
public static Func<NancyContext, Response> RequiresAuthentication()
{
return UnauthorizedIfNot(ctx => ctx.CurrentUser.IsAuthenticated());
}
private static Func<NancyContext, Response> UnauthorizedIfNot(Func<NancyContext, bool> test)
{
return HttpStatusCodeIfNot(HttpStatusCode.Unauthorized, test);
}
private static Func<NancyContext, Response> HttpStatusCodeIfNot(HttpStatusCode statusCode, Func<NancyContext, bool> test)
{
return (ctx) =>
{
Response response = null;
if (!test(ctx))
{
response = new Response { StatusCode = statusCode };
}
return response;
};
}
As you can see HttpStatusCode.Unauthorized should be returned if the user isn't authenticated. Seems easy enough. When I make an AJAX call to a route like:
Post["/comment/flag/{Id}"] = s =>
{
this.RequiresAuthentication();
return new IdResponse { Id = commentRepo.FlagComment(Context.CurrentUser.UserName, s.Id) };
};
I'm getting back a 303, which then redirects to the login URL with a 200. Yucky. Not expected at all.
Just to make sure I'm not totally crazy, I did the following and it worked fine:
Post["/comment/{Id}"] = s =>
{
if ((Context.CurrentUser == null) ||
String.IsNullOrWhiteSpace(Context.CurrentUser.UserName))
{
return HttpStatusCode.Forbidden;
}
var req = this.Bind<CommentRequest>();
return commentRepo.AddComment(Context.CurrentUser.UserName, s.Id, req.Comment);
};
This returns the expected 403. But who wants to paste this all over the place...not me. I'm trying to figure out how to write my own thing that executes before the route is run but I'm new to Nancy.
Curiously, if I change the:
return HttpStatusCode.Forbidden;
into a
return new Response { StatusCode = HttpStatusCode.Forbidden };
Then I get the 303 and 200 responses again. Something seems broken. Can anyone give me some insight or an answer of what I'm doing wrong? Nancy seems broken to me, specifically the Response class...
I had the same problem as you - Nothing is broken.
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
This code adds functions to the before and after pipelines. It takes every 401 and and changes it to a 303, since you've defined that the user should be redirected if he's not logged in.
In order to prevent that from happening, you can set the DisableRedirect property to true. And you can actually do this automatically on all Ajax Requests like this:
new FormsAuthenticationConfiguration()
{
...,
DisableRedirect = context.Request.IsAjaxRequest()
};
Also, make sure your request is setting the X-Requested-With header to XMLHttpRequest otherwise nanacy can't detect it's an ajax request. You can of course feel free to detect the proper requests differently (based on url, ...) in order to set DisableRedirect to true.
Currently it returns HttpStatusCode.Unauthorized or a 401 which seems to be correct. I tried setting the ContentType to application/json as well and that didn't help.
When I changed it to return a 403 it sends back a 403. Although I suppose technically this isn't the correct response, it is better then the mysterious 303 I was getting before.
I've been writing a module that replaces the registration form in DNN to work more with our system. I've been trying to figure out how to programatically get the URL that is specified on the Admin Settings page for the "Redirect After Login" field. Under the PortalSettings object, I've been able to find a lot of different TabID properties, but none of them appear to be for this field. I believe the field I am looking for to be in a different class, but I know not where. Has anyone run into this field or know better where I can look? Thanks in advance!
Once again, I found the answer very soon after posting.
I found "Redirect_AfterRegistration" in the PortalSettings table which can be accessed via the PortalController.GetPortalSettingsDictionary(PortalId) method. This alone is not enough, since this value may be -1 and thus redirect you back to the same page (typically the registration page). There exists code in \DesktopModules\Admin\Security\Register.ascx.cs which provides functionality for redirection after registering. In 7.1.x this code has a bug, but with some modification I came up with this:
public string RedirectAfterRegisterUrl
{
get
{
const string key = "Redirect_AfterRegistration";
const string returnUrlKey = "returnurl";
var url = "";
var settings = DotNetNuke.Entities.Portals.PortalController.GetPortalSettingsDictionary(PortalId);
if (settings.ContainsKey(key))
{
var tabId = Convert.ToInt32(settings[key]);
if (tabId > 0)
{
url = DotNetNuke.Common.Globals.NavigateURL(tabId);
}
else
{
if (Request.QueryString[returnUrlKey] != null)
{
url = HttpUtility.UrlDecode(Request.QueryString[returnUrlKey]);
if (url.Contains("//"))
url = "";
if (url.Contains("?returnurl"))
{
string baseURL = url.Substring(0, url.IndexOf("?returnurl", StringComparison.Ordinal));
string returnURL = url.Substring(url.IndexOf("?returnurl", StringComparison.Ordinal) + 11);
url = string.Concat(baseURL, "?returnurl", HttpUtility.UrlEncode(returnURL));
}
}
}
}
return url;
}
}
My tests have shown that this redirects me to the specified Redirect After Registration page. I hope this solutions helps others as well!
I have the following routes:
/projects/{projectName}
and
/projects/{projectName}/Wall/{wallName}
Now I'd like to have that all GETs be allowed but PUT, POST, DELETE should only be allowed by project members i.e. users members of that project. I have a special class that given a user id and project name I can get the status of the user's membership - something like MyEnroler.getRole(userId, projectName) - where the userId is part of the request header and the projectName is taken from the URI.
I've tried a number of things but doesn't work. Here's the idea:
public class RoleMethodAuthorizer extends Authorizer {
#Override
protected boolean authorize(Request req, Response resp) {
//If it's a get request then no need for further authorization.
if(req.getMethod().equals(Method.GET))
return true;
else
{
String authorEmail = req.getClientInfo().getUser().getIdentifier();
String projectName = req.getAttributes().get("project").toString();
Role userRole = MyEnroler.getRole(authorEmail, projectName);
//forbid updates to resources if done by non-members of project
if(userRole.equals(MyEnroler.NON_MEMBER))
return false;
//for everybody else, return true
return true;
}
}
}
Now simply doing the following completely fails when creating inbound root in the Application:
Router projectRouter = new Router(getContext());
RoleMethodAuthorizer rma = new RoleMethodAuthorizer();
//Guard declaration here. Then setNext Restlet
guard.setNext(projectRouter);
projectRouter.attach("/projects/{project}",rma);
Router wallRouter = new Router(getContext());
wallRouter.attach("/Wall/{wallName}", WallResource.class);
rma.setNext(wallRouter);
//return guard;
So a request to /projects/stackoverflow/Wall/restlet fails. The URL is never found. I'm guessing since it's trying to match it with the projectRouter. Well I tried the various modes (MODE_BEST_MATCH or MODE_FIRST/NEXT_MATCH) to no avail.
Nothing seems to work. Conceptually this should work. I'm only intercepting a call and just being transparent to the request, but don't know how things are working on the inside.
I could move the authorizer just after the guard, but I'd lose access to the request attribute of projectName - I don't wish to parse the URL myself to search for the projectName since the URL pattern could change and would break the functionality - i.e. require 2 changes instead of one.
Any ideas how to achieve this?
I would use the standard RoleAuthorizer class to supply the list of allowed roles, along with your custom enroller probably split into two I would then add a custom Filter class that does something like this to call your Enrolers.
protected int beforeHandle(final Request request, final Response response) throws ResourceException {
final String projectName = (String) request.getAttributes().get("projectName");
// Check that a projectName is supplied, should not have got this far otherwise but lets check.
if (projectName == null || projectName.isEmpty()) {
throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND);
}
if (Method.GET.equals(request.getMethod())){
new ReadEnroler(projectName).enrole(request.getClientInfo());
}else{
new MutateEnroler(projectName).enrole(request.getClientInfo());
}
return super.beforeHandle(request, response);
}
the enrolers would then set the appropriate values in the clientInfo.getRoles() Collection when enrole was called.