I have http requests such as the one below being sent to an nginx server:
GET /app/handler?id=1234¶m1=cbd¶m2=234
Now, I want to rewrite the request to a different handler depending on the id param in the request. eg. redirect to handler_even for even ids and handler_odd for odd ids. This is shown below:
GET /app/handler?id=1234¶m1=cbd¶m2=234 => /app/handler_even?id=1234¶m1=cbd¶m2=234
GET /app/handler?id=123¶m1=cbd¶m2=234 => /app/handler_odd?id=123¶m1=cbd¶m2=234
I can do the rewrite using proxy_pass, but I'm unsure how to redirect using the id parameter value. Any idea how I could go about this? Would using "if" be the best way to go about this?
Any pointers would be useful
Rather than use an if directive, you could use a map. To internally rewrite the URI use:
map $arg_id $handler {
default /app/handler_even;
~[13579]$ /app/handler_odd;
}
server {
...
location = /app/handler {
rewrite ^ $handler last;
}
...
}
The map should be located at the same level as your server directive (as shown above), i.e. within the http container.
See this document for details.
Related
I have two routes in Mojolicious app as follows:
my $route = $r->any('/api')->to('API#');
$route->get('/get_data')->to('#process_forms');
$route->get('/get_data/?file=:file&name=:name')->to('#submit_forms');
if I go to /api/get_data I get redirected to process_forms function. I want the app to take me to submit_forms function if I pass additional arguments to that same route. for example, url /api/get_data/?file=myfile&name=myname should call submit_forms function, but that's not the case here.
In both scenarios, process_forms is called.
What option Mojolicious routing provides to help me with this?
Mojo's router connects URL and HTTP request methods to controllers. The GET and POST parameters are not used in routing. This makes sense, because a URL is typically supposed to target a resource by itself.
You have a path /get_data you need to send that to one controller. From there you want it sounds like you want to do is to go to another controller if you have GET parameters (passed in the URL). You can do this, but it's not normally what you want.
Just putting a conditional in a controller,
What you normally want when handling get parameters is simply to put them inside a block in the controller, IE,
my $query = $c->req->query_params
my $file = $query->param('foo');
my $name = $query->param('name');
if ( defined $file && defined $name) {
# we have file and name
}
else {
# or we do not
}
Redispatch
But you can always redispatch to a different controller (manually)
MyApp::Controller::submit_forms($self);
Or, through Mojo,
$self->routes->continue("#submit_forms")
As a last idea, you can also make it that the route doesn't match if the post variables aren't there. But, I've never needed to do this.
My middleware need is to:
add an extra query param to requests made by a REST API client derived from GuzzleHttp\Command\Guzzle\GuzzleClient
I cannot do this directly when invoking APIs through the client because GuzzleClient uses an API specification and it only passes on "legal" query parameters. Therefore I must install a middleware to intercept HTTP requests after the API client prepares them.
The track I am currently on:
$apiClient->getHandlerStack()-push($myMiddleware)
The problem:
I cannot figure out the RIGHT way to assemble the functional Russian doll that $myMiddleware must be. This is an insane gazilliardth-order function scenario, and the exact right way the function should be written seems to be different from the extensively documented way of doing things when working with GuzzleHttp\Client directly. No matter what I try, I end up having wrong things passed to some layer of the matryoshka, causing an argument type error, or I end up returning something wrong from a layer, causing a type error in Guzzle code.
I made a carefully weighted decision to give up trying to understand. Please just give me a boilerplate solution for GuzzleHttp\Command\Guzzle\GuzzleClient, as opposed to GuzzleHttp\Client.
The HandlerStack that is used to handle middleware in GuzzleHttp\Command\Guzzle\GuzzleClient can either transform/validate a command before it is serialized or handle the result after it comes back. If you want to modify the command after it has been turned into a request, but before it is actually sent, then you'd use the same method of Middleware as if you weren't using GuzzleClient - create and attach middleware to the GuzzleHttp\Client instance that is passed as the first argument to GuzzleClient.
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Command\Guzzle\GuzzleClient;
use GuzzleHttp\Command\Guzzle\Description;
class MyCustomMiddleware
{
public function __invoke(callable $handler) {
return function (RequestInterface $request, array $options) use ($handler) {
// ... do something with request
return $handler($request, $options);
}
}
}
$handlerStack = HandlerStack::create();
$handlerStack->push(new MyCustomMiddleware);
$config['handler'] = $handlerStack;
$apiClient = new GuzzleClient(new Client($config), new Description(...));
The boilerplate solution for GuzzleClient is the same as for GuzzleHttp\Client because regardless of using Guzzle Services or not, your request-modifying middleware needs to go on GuzzleHttp\Client.
You can also use
$handler->push(Middleware::mapRequest(function(){...});
Of sorts to manipulate the request. I'm not 100% certain this is the thing you're looking for. But I assume you can add your extra parameter to the Request in there.
private function createAuthStack()
{
$stack = HandlerStack::create();
$stack->push(Middleware::mapRequest(function (RequestInterface $request) {
return $request->withHeader('Authorization', "Bearer " . $this->accessToken);
}));
return $stack;
}
More Examples here: https://hotexamples.com/examples/guzzlehttp/Middleware/mapRequest/php-middleware-maprequest-method-examples.html
I want to redirect old site urls to new site. But new site has different page names and language chars have changed too.
for example:
en/about/info will redirect to en/com/information
but
ge/about/info will go to ka/com/information
map $request_uri $redirect_uri {
<lang>/about/info/ $lang/com/information/
}
any ideas how I would go about this? There are a lot of urls, so I don't want to write these urls hardcoded for each language.
The map directive can capture parts of a regular expression, but cannot use that capture in the mapped result.
So it is possible to create a named capture called lang (for example) and use it after the mapped variable is evaluated. For example:
map $request_uri $redirect_uri {
~*(?<lang>/\w\w/)about/info/ com/information/;
}
And in the server or location block:
if ($redirect_uri) {
return 301 $lang$redirect_uri;
}
Note that $lang is only created after the value of $redirect_uri is evaluated in the if statement.
See this document for details.
I have a website with pagination, which chooses the page to display using the 'p' query.
My issue is that
www.example.com/category.html and
www.example.com/category.html?p=1
Are the same content, and are showing as duplicates for SEO purposes, how do i redirect all ?p=1 queries in nginx to their query free counterpart.
Thanks
The value of the p argument is contained in the $arg_p variable. The if statement can be used to test the value of a variable. The actions available in an if block are limited (see this document for details), but a simple return statement is allowed.
So the simplest solution would be to obliterate the query string from any URI which contains p=1, like this:
server {
...
if ($arg_p = 1) {
return 301 $uri;
}
location / { ... }
}
Note that $uri is the normalised request URI, and is already missing the query string.
I want to call another internal url from my scalatra 'controller'. I can't do a simple redirect, as there's some security settings that mean a user has access only to the first url.
Is there a way to do this?
get("/foo") {
servletContext.getRequestDispatcher("/bar").forward(request, response)
}
The get() method is defined as (similar to POST, et al):
def get(transformers : org.scalatra.RouteTransformer*)(action : => scala.Any) : org.scalatra.Route
Depends on what you mean by internal redirect, I presume you just want to execute another route's action. You have a few options of what you can do. This seems to be working for me:
val canonicalEndpoint = get("/first/route") {
//do things in here
}
Then you could subsequently do:
get("/second/route")( canonicalEndpoint.action )
And I think you would get your desired response.
I like saving the whole Route response of the get() as you may also want to use that with scalatra's url() function in routing.