I am creating a new Web API and Angular application. I want to know how we can handle routes on server side. Everything is working fine. I am using ui-router for routing, when i refresh browser that its giving me.
HTTP Error 404.0 - Not Found
I want to know how we can handle this.
Code for ui-router
$stateProvider.state('login', {
url: '/',
templateUrl: templatesDirectores.wmAccount + 'login.html',
controller: 'login-controller',
controllerAs: 'vm'
})
.state('register', {
url: '/register',
templateUrl: templatesDirectores.wmAccount + 'register.html',
controller: 'register-controller',
controllerAs: 'vm'
})
.state('forgetPassword', {
url: '/forgetpassword',
templateUrl: templatesDirectores.wmAccount + 'forget-password.html',
controller: 'forget-password-controller',
controllerAs: 'vm'
})
The view is loading fine as per the configuration, but when the URL is localhost:1235/register, it's loading fine first time but when I hit the refresh button I get 404 error.
This is occurring because you are using HTML5Mode = true in your Angular Application. Angular uses "hash" (/#register) routes to handle it's routing by default, in order to ensure that the browser does not perform a page reload from the server.
Using HTML5Mode, you can suppress the #, but at a cost. The browser must be HTML5 compliant, because HTML5Mode uses HTML Push State (HistoryAPI).
Also, your server must be configured to handle requests for routes which may exist in Angular but do not exist on the server. When you directly load a page, either by typing it in or by using refresh, a request is sent to the server, with a URL that the server may not know how to handle. Your server should be configured to return your Index.html page for any routes it does not expressly handle. This configuration varies per server.
Given you stated you are using IIS to host your site, something like the following would be necessary:
Web.config:
<system.webServer>
<rewrite>
<rules>
<rule name="Main Rule" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
Essentially, for all routes (.*), if the request is not a file or a directory, rewrite the url to return the base URL /.
This can also be done in code, something similar to this:
Global.asax:
private const string ROOT_DOCUMENT = "/default.aspx";
protected void Application_BeginRequest( Object sender, EventArgs e )
{
string url = Request.Url.LocalPath;
if ( !System.IO.File.Exists( Context.Server.MapPath( url ) ) )
Context.RewritePath( ROOT_DOCUMENT );
}
There are a few other caveats here:
Each platform handles rewrites differently. In the case of IIS, the URL is rewritten, and the remainder of the route is lost. i.e. for localhost:1235/register, you will actually load localhost:1235/, and Angular will never receive the /register route. Essentially, all routes not known by IIS will return your app entry point.
You can set up multiple entry points to your app, but any in-memory references to variables, settings, etc. will be lost when moving from one entry point to another.
Given these two points, you should always consider any external URLs to be a fresh copy of your Angular application, and handle them accordingly.
Here's another way to reroute 404's to your SPA:
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404" subStatusCode="-1"/>
<error statusCode="404" prefixLanguageFilePath="" path="/spa-location.html" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
Related
I have a node server like this:
var express = require('express');
var fs = require('fs');
var path = require('path');
var root = fs.realpathSync('.');
var app = express();
app.configure(function () {
app.use(express.static(__dirname + '/./'));
app.use(app.router);
});
app.get('/*', function (req, res) {
res.sendfile(path.join(root, './index.html'))
});
/*app.get('/dashboard', function (req, res) {
res.sendfile(path.join(root, 'index.html'))
});*/
app.listen(3000);
console.log('Listening on port 3000');
On the frontend, I am using backbone routes to route the application modules using HTML History API. So its always a well formatted URL. The problem occurs when I refresh the page. It works fine in local node server but when I deploy the application on microsoft azure cloud, the refresh creates a problem and it says, it cannot find a resource.
Is there anything specific to azure that I need to configure to make it understand that it should serve index.html on new requests instead of looking up for the resource?
In apache, we use .htaccess to do so but I have no idea about how to do the same on azure cloud!
Found the solution:
You need to have a webconfig file in the root for this to work. Azure internally has nothing but IIS to serve the files. If you observe closely, server.js is of no use once you deploy the application. IIS takes the control to serve the index.html file and hence you need to do something in the IIS configuration. Here is the Web.config (case sensitive) file that you need to put in the application root:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="redirect all requests" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" pattern="" ignoreCase="false" />
</conditions>
<action type="Rewrite" url="index.html" appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
You might try just use()ing a general middleware instead of a method-specific route:
app.use(function (req, res) {
res.sendfile(path.join(root, './index.html'));
});
As far as I know, Express currently does not support method-specific middleware (e.g. app.get(function(req, res) { ... })).
Also on an unrelated note, var root = fs.realpathSync('.'); is unnecessary since the global __dirname should get you the same value.
Site is developed in AngualrJS and deployed on IIS web server. Where as we have only on page i.e. Index.html since it is single page application. For SEO purpose we are using prerender.io.
Added following code in web.config file to rewrite every single URL to root path
<rule name="AngularJS" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
Also handled all invalid URL into main.module.js with following code which will redirecting to error page.
$urlRouterProvider.otherwise(function ($injector, $location) {
var $state = $injector.get('$state');
var $rootScope = $injector.get('$rootScope');
var now = new Date(); now.setTime(now.getTime() + 1 * 1800 * 1000);
setContentCookie('dlerrorURL', $location.$$absUrl, now.toUTCString(), 'xyz.com')
$rootScope.error = true;
$state.go('app.newerror')
});
With the help of above code, status code 404 getting appeared on prerender.io, but if we look into netowrk tab in developer console it displays 200 status code for all invalid URL. Technically it is correct since we have added rule in web.config file to rewrite all the URL to root path.
After lots of finding we came to the solution that we have to keep all the URLs at server side by implementing Asp.Net MVC code and adding all routes into route.config file. So that if the URL is not matched it will return 404 status code.
But since we are having 300+ pages we are avoiding to implement the above solution. Is there any alternat way to get or set 404 status code for invalid URL.
As per my experience so far, I faced the same issue and we made few changes like this and it worked like charm for us!
Follow this steps:
Step 1:
Go to your RouteConfig.cs in your MVC application
you will see some route map like this..
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Now comment that default route and add an additional map route like this
routes.MapRoute(
name: "Home",
url: "dv/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "CatchAll",
url: "{*any}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
in above Map route name as HOME you will see something like dv you can replace it with any keyword for you like api or any initial of your project like abc or xyz we are doing this just to seperate the server route with angular route.
REMOVE THE CODE YOU HAVE WRITTEN IN YOUR WEB CONFIG FOR URL REWRITING.
FOR PRERENDER:
Add something like this in your web.config
<configSections>
<section name="prerender" type="Prerender.io.PrerenderConfigSection, Prerender.io, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</configSections>
<prerender token="YOUR_TOKEN_HERE"></prerender>
and add prerender.io DLL to your MVC project.
Now, in system.web add something like this for prerender.
<system.web>
<httpModules>
<add name="Prerender" type="Prerender.io.PrerenderModule, Prerender.io, Version=1.0.0.2, Culture=neutral, PublicKeyToken=null"/>
</httpModules>
</system.web>
Now in the App_Start folder of your MVC application add this class name PreApplicationStartCode.cs.
public static class PreApplicationStartCode
{
private static bool _isStarting;
public static void PreStart()
{
if (!_isStarting)
{
_isStarting = true;
DynamicModuleUtility.RegisterModule(typeof(Prerender.io.PrerenderModule));
}
}
}
And in your AssemblyInfo.cs under Properties of your MVC app add something like this for prerender at the last line of the file.
[assembly: PreApplicationStartMethod(typeof(yourappnamespace.PreApplicationStartCode), "PreStart")]
I have create an application with loopback and angular but i have a problem. When i refresh the browser loopback give me a 404 url not Found.
Added base tag to index.html
<base href="/"/>
Set middlelware.json properly to serve static content:
"files": {
"loopback#static": {
"params": "$!../client"
}
Set HTML5 mode in angular router (I'm using ui-router)
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/');
I have yet deleted root.js from the project.
What i'm doing wrong? Thanks in advance
When you have enabled html5 mode/ push state mode ON in angularjs, this is a common scenario which your server should handle. If I understood your problem correctly, server returns 404 when you refresh the page which otherwise render fine if navigated from landing page. Let me know if this is the scenario:
Angular app lands into Home screen, say your_domain/
Navigate to some other page - your_domain/somepage (which will be your_domain/#somepage in case of hash bang mode)
Refresh the page -> throws 404
If the scenario which you face is like given above, then this is what happens:
Loads the home page -> angular is bootstraped and routing is set up
Navigates to "somepage" -> angular route handles this and show "somepage"
Refresh the page -> request hits the server and requests for your_domain/somepage - which is not available in server
server returns 404
How to handle this?
Do Url rewrite back to your_domain/ from server in case of 404. This will bootstrap the angular app and angular route will handle the request
More details here - https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-your-server-to-work-with-html5mode
Copy pasting from above website
Apache Rewrites
<VirtualHost *:80>
ServerName my-app
DocumentRoot /path/to/app
<Directory /path/to/app>
RewriteEngine on
# Don't rewrite files or directories
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# Rewrite everything else to index.html to allow html5 state links
RewriteRule ^ index.html [L]
</Directory>
</VirtualHost>
Nginx Rewrites
server {
server_name my-app;
root /path/to/app;
location / {
try_files $uri $uri/ /index.html;
}
}
Azure IIS Rewrites
<system.webServer>
<rewrite>
<rules>
<rule name="Main Rule" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
Express Rewrites
var express = require('express');
var app = express();
app.use('/js', express.static(__dirname + '/js'));
app.use('/dist', express.static(__dirname + '/../dist'));
app.use('/css', express.static(__dirname + '/css'));
app.use('/partials', express.static(__dirname + '/partials'));
app.all('/*', function(req, res, next) {
// Just send the index.html for other files to support HTML5Mode
res.sendFile('index.html', { root: __dirname });
});
app.listen(3006); //the port you want to use
ASP.Net C# Rewrites
In Global.asax
private const string ROOT_DOCUMENT = "/default.aspx";
protected void Application_BeginRequest( Object sender, EventArgs e )
{
string url = Request.Url.LocalPath;
if ( !System.IO.File.Exists( Context.Server.MapPath( url ) ) )
Context.RewritePath( ROOT_DOCUMENT );
}
This can be frustrating BUT I got you covered.
This is tested and works on Nginx Ubuntu 14/16/18.. with angular 5+
Edit /etc/nginx/sites-enabled/default OR where ever you set your server settings.
This is the configuration that works:
server {
listen 80;
server_name <YOUR SERVER DOMAIN>;
access_log /var/log/nginx/nginx.vhost.access.log;
error_log /var/log/nginx/nginx.vhost.error.log;
index index.html index.htm;
location / {
root /wwweb/<location to angular dist>/dist;
try_files $uri $uri/ /index.html?$query_string;
}
}
I think that is because you are using $locationProvider.html5Mode(true); . try this $locationProvider.html5Mode(false);
i have enabled the html5mode to remove Hashing from web url,after that i get an error.so i have mentioned the base href in master page. set rewrite url in web config.
when i goes into state view page and refresh the page it will show the wrong url.when launch the application in iis 7.5 getting error.
for that i have rewrite the url.
<rewrite>
<rules>
<rule name="RewriteRules stopProcessing="true">
<match url=".*"/>
<conditions logicalGrouping="MatchAll">
<add input ="{REQUEST_FILEName}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILEName}" matchType="IsDirectory" negate="true"/>
<add input ="{REQUEST_URI}" pattern="^/(api)" negate="true"/>
</conditions>
<action type="Rewrite" url="home/home"/>
</rule>
</rules>
</rewrite>
</system.webServer>
Can anyone helpme
when you enable html5mode, your all request should redirected to your main url (where your application start ex. home/index). try add below code in the RegisterRoutes method
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default1",
url: "{*.}",
defaults: new
{
controller = "Home",
action = "Index",
}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
I have an AngularJS app built on top of Node.JS that is hosted in Azure (IIS) and uses HTML5 mode. In situations where I don't use Node.JS and just use AngularJS with IIS I can rewrite URLs so that a page refresh doesn't cause a 404 error using the following rule in the IIS Web.config:
<rule name="AngularJSHTML5Mode" stopProcessing="true">
<match url=".*"/>
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
<add input="{REQUEST_URI}" pattern="^/(api)" negate="true"/>
</conditions>
<action type="Rewrite" url="/"/>
</rule>
However, when using Angular on top of Node.js (with ExpressJS) I get an error such as "Cannot GET /myroute" on page refresh, even with the above rule in IIS. Is there something I need to configure in Server.js or a different rule in Web.config.
Here is what I have in Express at the moment. I am using Express 4:
// Initialize Express App
var app = express();
var listener = app.listen(1337, function () {
console.log('Listening on port ' + listener.address().port); //Listening on port 1337
});
app.use(express.static(__dirname + "/public"));
I understand that page refreshes shouldn't happen in a SPA as a matter of practice, but they do, and I need to account for this.
I've experimented and this is what has worked for me:
web.config (site.js is the entry point to my node app)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpErrors existingResponse="PassThrough" />
<iisnode nodeProcessCommandLine=""c:\program files\nodejs\node.exe"" watchedFiles="*.js"
interceptor=""%programfiles%\iisnode\interceptor.js"" promoteServerVars="LOGON_USER"/>
<handlers>
<add name="iisnode" path="site.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<clear />
<rule name="default">
<match url="/*" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
<action type="Rewrite" url="site.js" />
</rule>
</rules>
</rewrite>
<security>
<requestFiltering>
<hiddenSegments>
<add segment="node_modules" />
</hiddenSegments>
</requestFiltering>
</security>
</system.webServer>
</configuration>
static file server in express + url rewrite
app.use(express.static(clientDir));
// for SPA
app.get('/*', function(req,res, next){
res.format({
html: function(){
res.sendFile(path.join(clientDir, 'index.html'));
},
json: function(){
next();
}
});
});
//routes...
I have also tried to implement url rewrite with virtual app in iis. This doesn't work. Instead, I had to configure a path within my node module to strip the leading part of the route.
However, you can have iis serve the static files once your app has bootstrapped.
Upon further digging, I didn't need the Web config. I have this in my server.js to institute the connection to the Angular index file:
var app = express();
app.use(express.static(__dirname + "/public"));
var listener = app.listen(1337, function () {
console.log('Listening on port ' + listener.address().port); //Listening on port 1337
});
This part is critical, at the end of all your GETs, POSTs, etc. I added this as the last function. It needs to be last since it is a catch all and if you put it before a GET or POST function it won't see it as Express but a broken Angular route, and if your App.js is configured correctly, will route to the homepage (not desired):
// Placed at end for routing non-Express calls/AngularJS refreshes
app.use('/*', function (req, res) {
res.sendFile(__dirname + '/public/index.html');
});