Reverse routing on an application deployed in a Tomcat context - rest

I am developing a Play 1.2.5 application that will be installed in a Tomcat context:
http://mytomcat:8080/myapp
And in my routes file I have:
GET /companies/{companyID}/employees Employees.getForCompany
As per the instructions for deploying a Play application in a Tomcat context, I am generating URLs exlusively using the Reverse Router. This works fine, but I am not sure what to do in the case of a jQuery method such as an Ajax request of this style:
var companyID = $('#companies').find(":selected").val();
$.ajax({
url : "#{Employees.getForCompany(companyID)}",
...
});
Obviously the value of companyID isn't known at the time of the generation of the HTML and the resolution of routes by the reverse router.
The only solution that I can see is to reconfigure my routes file so that the URLs are different and the parameters are always at the end, but that makes the REST URLs less logical.
GET /companies/employees/{companyID} Employees.getForCompany
Is there a better way?

I have found the solution myself - Play includes the jsAction tag which generates a function that builds the correct URL:
var companyURL = #{jsAction #Employees.getForCompany(':companyID') /}
$.ajax({
url : companyURL({companyID:companyID}),
...
});
Like that I can maintain the preferred routes mapping:
GET /companies/{companyID}/employees Employees.getForCompany

Related

Dynamic Links from a CMS - Error: "redirect" can not be returned from getStaticProps during prerendering

I have a Next JS app connected to a CMS and hosted on Vercel - all links are dynamic and the pages are created by the content authors.
I am trying to create dynamic redirects that will force URLs to adhere to formats that are better for SEO. For example:
Enforce lowercase URLs
Replace spaces with dashes
Remove trailing slashes
For example, /test/Author Name/ would redirect to /test/author-name
Since I need to trigger a 301 redirect for these wrong URLs, the only way to do this with Next JS from what I have found is to return a Redirect from getStaticProps, this is what I have so far:
export const getStaticProps: GetStaticProps = async (context) => {
let requestedUrl = '/';
if (context?.params?.path) {
requestedUrl = '/' + (context?.params?.path as string[]).join('/');
}
//check for URLs with uppercases, spaces, etc. and clean them up
let modifiedUrl = requestedUrl;
modifiedUrl = modifiedUrl.trim().toLowerCase().replace(/\s\s+/g, ' ').replace(/\s/g, '-');
if (modifiedUrl != requestedUrl) {
return {
redirect: {
destination: modifiedUrl,
permanent: true,
},
};
}
This works wonderfully well running locally and connected to the CMS - everything is working as it should and all "faulty" URLs are corrected with the correct response code.
Sadly, this does not work on build, I have spent so much time so far trying to find an alternative, but no matter what I do, the build on Vercel fails with the error:
"redirect" can not be returned from getStaticProps during prerendering
The next best potential solution is to use Middleware, but that requires v.12 at least. Due to limitations from the CMS connector, we are forced to use Node v.11 :(
The alternative that I have built is to use router.push on the client side, but this... just looks terrible. The page loads, returns a 200, and then loads again with the corrected URL. Not good for the user's experience.
Any advice or suggestions? I am baffled that something this simple is this complicated with Next JS!
I resolved the issue... it looks like redirects on statically generated pages are not possible unfortunately. I removed getStaticProps and getStaticPaths, and added getServerSideProps instead. The redirects are now working correctly, but the site is not as fast as we are losing out on SSG.

ASP.NET Core 5 route redirection

We have an ASP.NET Core 5 Rest API where we have used a pretty simple route:
[Route("api/[controller]")]
The backend is multi-tenant, but tenant-selection has been handled by user credentials.
Now we wish to add the tenant to the path:
[Route("api/{tenant}/{subtenant}/[controller]")]
This makes cross-tenant queries simpler for tools like Excel / PowerQuery, which unfortunately tend to store credentials per url
The problem is to redirect all existing calls to the old route, to the new. We can assume that the missing pieces are available in the credentials (user-id is on form 'tenant/subtenant/username')
I had hope to simply intercept the route-parsing and fill in the tenant/subtenant route values, but have had not luck so far.
The closes thing so far is to have two Route-attributes, but that unfortunately messes up our Swagger documentation; every method will appear with and without the tenant path
If you want to transparently change the incoming path on a request, you can add a middleware to set Path to a new value, for example:
app.Use(async (context,next) =>
{
var newPath = // Logic to determine new path
// Rewrite and continue processing
context.Request.Path = newPath;
await next();
});
This should be placed in the pipeline after you can determine the tenant and before the routing happens.

Durandal JS: typing url without hash give HTTP Error 404.0 - Not Found

I am building a SPA application using Durandaljs. When I enter a URL WITHOUT hash, it shows the error: HTTP error 404.0 - NOT FOUND. However, it is working fine WITH hash.
Example:
www.domain.com/page => HTTP error 404.0 - NOT FOUND
www.domain.com/#page = > working fine.
How can I map the www.domain.com/page to www.domain.com/#page?
In order for pushState to work correctly in your app (including deep linking), you must configure the router for pushState and tell the server to ignore all but certain paths/routes.
The server should ignore paths that contain resources or services used by your SPA, and should always send the Durandal.cshtml (or whatever you name it) for these ignored routes.
These routes should be ignored even if there's additional route info in the request. This will allow for deep linking using pushState-style URLs.
Here's the MVC routing from a Durandal-based app I recently completed:
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Ignore("{resourcedir}/{*resource}",
new {resourcedir = #"(api|breeze|app|scripts|content|assets|signalr)"});
routes.MapRoute(
name: "Default",
url: "{*sparoute}",
defaults: new { controller = "Durandal", action = "Index"});
}
Basically, we're telling the server to ignore any requests that start with:
api/
breeze/
app/
scripts/
content/
assets/
signalr/
We're ignoring api, breeze and singlar because WEBApi and SignalR will handle those requests. Also, app, scripts, content, and assets are ignored because the underlying ASP.NET engine will serve those requests.
The final statement (routes.MapRoute) causes all non-ignored requests to send back the default page (the only real page in the SPA).
For example, www.domain.com/page will send the same response from the server as if you'd asked for www.domain.com/. Once the SPA loads and Durandal's router/history has initialized, the suffix is converted to hash if needed (like you're using IE) and is then dispatched through the router.
Note that if your application is not rooted at /, you need to specify the root path. You should also include hashChange: true so that your application works in IE, like this:
router.activate({ pushState: true, hashChange: true, root: '/approot' })
You can, I'm using it in my latest Durandal site which is based on the DurandalAuth template.
When you are initializing your router, set pushState: true, like this:
return router.map([
{ route: 'somroute', moduleId: 'somroute', title: 'Home', nav: true, hash: "#somroute" }
])
.buildNavigationModel()
.mapUnknownRoutes("notfound", "notfound")
.activate({ pushState: true });
You can see a working example on my site: noadou

Integrate AngularJS App with SoftwareAG webMethods Integration Server

I have been trying to set up a sample AngularJS app with webMethods Integration Server on the backend. Using $resource, I can easily pull normal JSON files and manipulate the data within the file. However, the goal is that I want to create services in webMethods Designer and call them from AngularJS using $resource to display the data in my app. The problem is that from AngularJS I cannot extract the data I need from the service that I'm creating in Designer. In Designer I can use (in WMPublic) documentToJSONString, and output something like:
jsonString {"id":"1", "name":"Dan", "quantity":"3"}
But I cannot extract the data because this is not a pure JSON string. Does anyone know how to (1) extract the JSON string output data using AnularJS or (2) output a JSON document from Designer? I am calling a REST service; something to the effect of
http://localhost:2222/rest/Get/getOrderData
from my services.js file in AngularJS.
Here is my services.js file:
/* Services */
var orderServices = angular.module('orderServices', ['ngResource']);
orderServices.factory('Order', ['$resource',
function($resource){
return $resource('http://localhost:2222/rest/REST/getOrderData', {}, {
query: {method:'GET', isArray:true}
});
}]);
Then, in my app, I want to use an ng-repeat to call things like {{order.id}}, {{order.name}} etc. Is anyone good with webMethods and Angular or done this before?
To force the response that you want, I would have used the service
pub.flow:setResponse mapping the jsonString to it's string parameter and probably hardcoded (eww!) the contentType parameter to 'application/json'
You may also need to use the service pub.flow:setResponseCode to set the response code.
They would be the last services in getOrderData
I would have invoked it using the below (where namespace is the folder structure in designer)
http://localhost:2222/invoke/namespace:getOrderData
The above applies to Integration Server V8 and it looks like you're using V9 since some of the services that you mention didn't exist in V8. This would also apply to a normal flow service, not a specific REST one (assuming they exist in V9).

Play Framework 2.1: Scala: how to get the whole base url (including protocol)?

Currently I am able to get the host from the request, which includes domain and optional port. Unfortunately, it does not include the protocol (http vs https), so I cannot create absolute urls to the site itself.
object Application extends Controller {
def index = Action { request =>
Ok(request.host + "/some/path") // Returns "localhost:9000/some/path"
}
}
Is there any way to get the protocol from the request object?
Actually there's a simple way to do it using Call class that reverse routers use to achieve similar thing.
Given that you are within the scope of implicit request, you can do something like this:
new Call(request.method, input.request).absoluteURL()
and it will provide you with the complete url (protocol, host, route and parameters).
In Play 2.3 and later you can use the secure property of the Request class.
I don't think there is.
Play Framework 2.0 itself does not support https, see: play-framework [2.0] HTTPS
The implementation of absoluteURL method of the Call class of the Play Framework 2.0 does not suggest it.
A workaround is to use a protocol relative urls using //domain.com/path.
This however does not help you with links in email. In that case you could put the protocol in the application.conf. In most cases the difference is made because production supports https and development does not.
I have yet to find a situation where the above workarounds do not work.
Actually your portnumber will give you if it's http or https.
Start your Play server with https support JAVA_OPTS=-Dhttps.port=9001 play start
Here's a code snippet (you can make the validation more stable with a regex, take the https port number from properties ...)
def path = Action { request =>
val path =
if(request.host.contains(":9000"))
"http://" + request.host + "/some/path"
else
"https://" + request.host + "/some/path"
Ok(path)
}
The code will return
http://ServerIp:9000/some/path if it's thru http
https://ServerIp:9001/some/path if it's thru https
My solution was to pass the beginning of the url as an additional parameter from javascript.
The application.conf solution does not work for me, because the same application is accessible on http and https but from different subnet and domain.