From e176ff7b0c7177344e3572514fcf7325bd138e49 Mon Sep 17 00:00:00 2001
From: "Gerasimos (Makis) Maropoulos" <kataras2006@hotmail.com>
Date: Sat, 20 Jan 2018 05:17:31 +0200
Subject: [PATCH] NEW: `Application#SubdomainRedirect`. Example:
 https://github.com/kataras/iris/blob/master/_examples/subdomains/redirect/main.go

Former-commit-id: d8dd7c426dc9f14c870f103fef703595a2915612
---
 HISTORY_GR.md                                 |   2 +-
 README_RU.md                                  |   2 +-
 _examples/README.md                           |   4 +-
 _examples/subdomains/redirect/hosts           |   4 +
 _examples/subdomains/redirect/main.go         |  68 ++++++++
 _examples/subdomains/redirect/main_test.go    |  33 ++++
 _examples/subdomains/www/main.go              |  35 +++-
 configuration.go                              |   4 +-
 context/context.go                            |  25 ++-
 core/netutil/addr.go                          |   4 +-
 core/router/api_builder.go                    |  16 +-
 core/router/party.go                          |   5 +
 core/router/router.go                         |   6 +-
 .../router_subdomain_redirect_wrapper.go      | 163 ++++++++++++++++++
 iris.go                                       |  37 +++-
 15 files changed, 384 insertions(+), 24 deletions(-)
 create mode 100644 _examples/subdomains/redirect/hosts
 create mode 100644 _examples/subdomains/redirect/main.go
 create mode 100644 _examples/subdomains/redirect/main_test.go
 create mode 100644 core/router/router_subdomain_redirect_wrapper.go

diff --git a/HISTORY_GR.md b/HISTORY_GR.md
index a4beb019..fcad3d94 100644
--- a/HISTORY_GR.md
+++ b/HISTORY_GR.md
@@ -21,7 +21,7 @@
 
 ## Ασφάλεια | `iris.AutoTLS`
 
-**Όλοι οι servers πρέπει να αναβαθμιστούν σε αυτήν την έκδοση**, περιέχει διορθώσεις για το _tls-sni challenge_ το οποίο απενεργοποιήθηκε μερικές μέρες πριν από το letsencrypt.org το οποίο προκάλεσε σχεδόν όλα τα golang https-ενεργποιημένα servers να να μην είναι σε θέση να λειτουργήσουν, έτσι υποστήριξη για το _http-01 challenge_ προστέθηκε σαν αναπλήρωση. Πλέον ο διακομιστής δοκιμάζει όλες τις διαθέσιμες προκλήσεις(challeneges) letsencrypt.
+**Όλοι οι servers πρέπει να αναβαθμιστούν σε αυτήν την έκδοση**, περιέχει διορθώσεις για το _tls-sni challenge_ το οποίο απενεργοποιήθηκε μερικές μέρες πριν από το letsencrypt.org το οποίο προκάλεσε σχεδόν όλα τα golang https-ενεργποιημένα servers να να μην είναι σε θέση να λειτουργήσουν, έτσι υποστήριξη για το _http-01 challenge_ προστέθηκε σαν αναπλήρωση. Πλέον ο διακομιστής δοκιμάζει όλες τις διαθέσιμες προκλήσεις(challenges) letsencrypt.
 
 Διαβάστε περισσότερα:
 
diff --git a/README_RU.md b/README_RU.md
index 85bcb688..85715d81 100644
--- a/README_RU.md
+++ b/README_RU.md
@@ -1,4 +1,4 @@
-# Веб-фреймворк Iris <a href="README.md"> <img width="20px" src="https://iris-go.com/images/flag-unitedkingdom.svg?v=10" /></a> <a href="README_ZH.md"><img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /></a> <a href="README_GR.md"><img width="20px" src="https://iris-go.com/images/flag-greece.svg?v=10" /></a>
+# Iris Web Framework <a href="README.md"> <img width="20px" src="https://iris-go.com/images/flag-unitedkingdom.svg?v=10" /></a> <a href="README_ZH.md"><img width="20px" src="https://iris-go.com/images/flag-china.svg?v=10" /></a> <a href="README_GR.md"><img width="20px" src="https://iris-go.com/images/flag-greece.svg?v=10" /></a>
 
 <img align="right" width="169px" src="https://iris-go.com/images/icon.svg?v=a" title="logo created by @merry.dii" />
 
diff --git a/_examples/README.md b/_examples/README.md
index f5a1e0e5..b80ccda7 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -10,6 +10,7 @@ It doesn't always contain the "best ways" but it does cover each important featu
 
 - [Hello world!](hello-world/main.go)
 - [Glimpse](overview/main.go)
+- [WWW](www/main.go)
 - [Tutorial: Online Visitors](tutorial/online-visitors/main.go)
 - [Tutorial: A Todo MVC Application using Iris and Vue.js](https://hackernoon.com/a-todo-mvc-application-using-iris-and-vue-js-5019ff870064)
 - [Tutorial: URL Shortener using BoltDB](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7)
@@ -267,7 +268,8 @@ Follow the examples below,
 - [Single](subdomains/single/main.go)
 - [Multi](subdomains/multi/main.go)
 - [Wildcard](subdomains/wildcard/main.go)
-- [WWW](subdomains/www/main.go) 
+- [WWW](subdomains/www/main.go)
+- [Redirect fast](subdomains/redirect/main.go)
 
 ### Convert `http.Handler/HandlerFunc`
 
diff --git a/_examples/subdomains/redirect/hosts b/_examples/subdomains/redirect/hosts
new file mode 100644
index 00000000..7437d713
--- /dev/null
+++ b/_examples/subdomains/redirect/hosts
@@ -0,0 +1,4 @@
+127.0.0.1		mydomain.com
+127.0.0.1		www.mydomain.com
+
+# Windows: Drive:/Windows/system32/drivers/etc/hosts, on Linux: /etc/hosts
\ No newline at end of file
diff --git a/_examples/subdomains/redirect/main.go b/_examples/subdomains/redirect/main.go
new file mode 100644
index 00000000..27e478fe
--- /dev/null
+++ b/_examples/subdomains/redirect/main.go
@@ -0,0 +1,68 @@
+// Package main shows how to register a simple 'www' subdomain,
+// using the `app.WWW` method, which will register a router wrapper which will
+// redirect all 'mydomain.com' requests to 'www.mydomain.com'.
+// Check the 'hosts' file to see how to test the 'mydomain.com' on your local machine.
+package main
+
+import "github.com/kataras/iris"
+
+const addr = "mydomain.com:80"
+
+func main() {
+	app := newApp()
+
+	// http(s)://mydomain.com, will be redirect to http(s)://www.mydomain.com.
+	// The `www` variable is the `app.Subdomain("www")`.
+	//
+	// app.WWW() wraps the router so it can redirect all incoming requests
+	// that comes from 'http(s)://mydomain.com/%path%' (www is missing)
+	// to `http(s)://www.mydomain.com/%path%`.
+	//
+	// Try:
+	// http://mydomain.com             -> http://www.mydomain.com
+	// http://mydomain.com/users       -> http://www.mydomain.com/users
+	// http://mydomain.com/users/login -> http://www.mydomain.com/users/login
+	app.Run(iris.Addr(addr))
+}
+
+func newApp() *iris.Application {
+	app := iris.New()
+	app.Get("/", func(ctx iris.Context) {
+		ctx.Writef("This will never be executed.")
+	})
+
+	www := app.Subdomain("www") // <- same as app.Party("www.")
+	www.Get("/", index)
+
+	// www is an `iris.Party`, use it like you already know, like grouping routes.
+	www.PartyFunc("/users", func(p iris.Party) { // <- same as www.Party("/users").Get(...)
+		p.Get("/", usersIndex)
+		p.Get("/login", getLogin)
+	})
+
+	// redirects mydomain.com/%anypath% to www.mydomain.com/%anypath%.
+	// First argument is the 'from' and second is the 'to/target'.
+	app.SubdomainRedirect(app, www)
+
+	// If you need to redirect any subdomain to 'www' then:
+	// app.SubdomainRedirect(app.WildcardSubdomain(), www)
+	// If you need to redirect from a subdomain to the root domain then:
+	// app.SubdomainRedirect(app.Subdomain("mysubdomain"), app)
+	//
+	// Note that app.Party("mysubdomain.") and app.Subdomain("mysubdomain")
+	// is the same exactly thing, the difference is that the second can omit the last dot('.').
+
+	return app
+}
+
+func index(ctx iris.Context) {
+	ctx.Writef("This is the www.mydomain.com endpoint.")
+}
+
+func usersIndex(ctx iris.Context) {
+	ctx.Writef("This is the www.mydomain.com/users endpoint.")
+}
+
+func getLogin(ctx iris.Context) {
+	ctx.Writef("This is the www.mydomain.com/users/login endpoint.")
+}
diff --git a/_examples/subdomains/redirect/main_test.go b/_examples/subdomains/redirect/main_test.go
new file mode 100644
index 00000000..cc4f931d
--- /dev/null
+++ b/_examples/subdomains/redirect/main_test.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/kataras/iris/httptest"
+)
+
+func TestSubdomainRedirectWWW(t *testing.T) {
+	app := newApp()
+	root := strings.TrimSuffix(addr, ":80")
+
+	e := httptest.New(t, app)
+
+	tests := []struct {
+		path     string
+		response string
+	}{
+		{"/", fmt.Sprintf("This is the www.%s endpoint.", root)},
+		{"/users", fmt.Sprintf("This is the www.%s/users endpoint.", root)},
+		{"/users/login", fmt.Sprintf("This is the www.%s/users/login endpoint.", root)},
+	}
+
+	for _, test := range tests {
+		req := e.GET(test.path)
+		// req.WithURL("http://www." + root)
+
+		req.Expect().Status(httptest.StatusOK).Body().Equal(test.response)
+	}
+
+}
diff --git a/_examples/subdomains/www/main.go b/_examples/subdomains/www/main.go
index b80a28e3..26dc2672 100644
--- a/_examples/subdomains/www/main.go
+++ b/_examples/subdomains/www/main.go
@@ -11,25 +11,41 @@ func newApp() *iris.Application {
 	app.Get("/about", info)
 	app.Get("/contact", info)
 
-	usersAPI := app.Party("/api/users")
-	{
-		usersAPI.Get("/", info)
-		usersAPI.Get("/{id:int}", info)
+	app.PartyFunc("/api/users", func(r iris.Party) {
+		r.Get("/", info)
+		r.Get("/{id:int}", info)
 
-		usersAPI.Post("/", info)
+		r.Post("/", info)
 
-		usersAPI.Put("/{id:int}", info)
-	}
+		r.Put("/{id:int}", info)
+	}) /* <- same as:
+	 usersAPI := app.Party("/api/users")
+	 {  // those brackets are just syntactic-sugar things.
+		// This method is rarely used but you can make use of it when you want
+	    // scoped variables to that code block only.
+		usersAPI.Get/Post...
+	 }
+	 usersAPI.Get/Post...
+	*/
 
 	www := app.Party("www.")
 	{
-		// get all routes that are registered so far, including all "Parties" but subdomains:
+		// http://www.mydomain.com/hi
+		www.Get("/hi", func(ctx iris.Context) {
+			ctx.Writef("hi from www.mydomain.com")
+		})
+
+		// Just to show how you can get all routes and copy them to another
+		// party or subdomain:
+		// Get all routes that are registered so far, including all "Parties" but subdomains:
 		currentRoutes := app.GetRoutes()
-		// register them to the www subdomain/vhost as well:
+		// Register them to the www subdomain/vhost as well:
 		for _, r := range currentRoutes {
 			www.Handle(r.Method, r.Path, r.Handlers...)
 		}
 	}
+	// See also the "subdomains/redirect" to register redirect router wrappers between subdomains,
+	// i.e mydomain.com to www.mydomain.com (like facebook does for SEO reasons(;)).
 
 	return app
 }
@@ -43,6 +59,7 @@ func main() {
 	// http://mydomain.com/api/users/42
 
 	// http://www.mydomain.com
+	// http://www.mydomain.com/hi
 	// http://www.mydomain.com/about
 	// http://www.mydomain.com/contact
 	// http://www.mydomain.com/api/users
diff --git a/configuration.go b/configuration.go
index 02526963..4fb3f0ec 100644
--- a/configuration.go
+++ b/configuration.go
@@ -735,7 +735,7 @@ func WithConfiguration(c Configuration) Configurator {
 
 		if v := c.RemoteAddrHeaders; len(v) > 0 {
 			if main.RemoteAddrHeaders == nil {
-				main.RemoteAddrHeaders = make(map[string]bool)
+				main.RemoteAddrHeaders = make(map[string]bool, len(v))
 			}
 			for key, value := range v {
 				main.RemoteAddrHeaders[key] = value
@@ -744,7 +744,7 @@ func WithConfiguration(c Configuration) Configurator {
 
 		if v := c.Other; len(v) > 0 {
 			if main.Other == nil {
-				main.Other = make(map[string]interface{})
+				main.Other = make(map[string]interface{}, len(v))
 			}
 			for key, value := range v {
 				main.Other[key] = value
diff --git a/context/context.go b/context/context.go
index 2422ad8d..a776143b 100644
--- a/context/context.go
+++ b/context/context.go
@@ -366,6 +366,8 @@ type Context interface {
 	// Subdomain returns the subdomain of this request, if any.
 	// Note that this is a fast method which does not cover all cases.
 	Subdomain() (subdomain string)
+	// IsWWW returns true if the current subdomain (if any) is www.
+	IsWWW() bool
 	// RemoteAddr tries to parse and return the real client's request IP.
 	//
 	// Based on allowed headers names that can be modified from Configuration.RemoteAddrHeaders.
@@ -1311,11 +1313,16 @@ func (ctx *context) RequestPath(escape bool) string {
 // 	return false
 // } no, it will not work because map is a random peek data structure.
 
-// Host returns the host part of the current url.
+// Host returns the host part of the current URI.
 func (ctx *context) Host() string {
-	h := ctx.request.URL.Host
+	return GetHost(ctx.request)
+}
+
+// GetHost returns the host part of the current URI.
+func GetHost(r *http.Request) string {
+	h := r.URL.Host
 	if h == "" {
-		h = ctx.request.Host
+		h = r.Host
 	}
 	return h
 }
@@ -1338,6 +1345,18 @@ func (ctx *context) Subdomain() (subdomain string) {
 	return
 }
 
+// IsWWW returns true if the current subdomain (if any) is www.
+func (ctx *context) IsWWW() bool {
+	host := ctx.Host()
+	if index := strings.IndexByte(host, '.'); index > 0 {
+		// if it has a subdomain and it's www then return true.
+		if subdomain := host[0:index]; !strings.Contains(ctx.Application().ConfigurationReadOnly().GetVHost(), subdomain) {
+			return subdomain == "www"
+		}
+	}
+	return false
+}
+
 // RemoteAddr tries to parse and return the real client's request IP.
 //
 // Based on allowed headers names that can be modified from Configuration.RemoteAddrHeaders.
diff --git a/core/netutil/addr.go b/core/netutil/addr.go
index 66ee0aef..d1d5f36c 100644
--- a/core/netutil/addr.go
+++ b/core/netutil/addr.go
@@ -195,8 +195,8 @@ func ResolvePort(addr string) int {
 	return 80
 }
 
-// ResolveScheme returns "https://" if "isTLS" receiver is true,
-// otherwise "http://".
+// ResolveScheme returns "https" if "isTLS" receiver is true,
+// otherwise "http".
 func ResolveScheme(isTLS bool) string {
 	if isTLS {
 		return SchemeHTTPS
diff --git a/core/router/api_builder.go b/core/router/api_builder.go
index ebc278c1..4fb3bb46 100644
--- a/core/router/api_builder.go
+++ b/core/router/api_builder.go
@@ -111,6 +111,14 @@ func NewAPIBuilder() *APIBuilder {
 	return api
 }
 
+// GetRelPath returns the current party's relative path.
+// i.e:
+// if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users".
+// if r := app.Party("www.") or app.Subdomain("www") then the `r.GetRelPath()` is the "www.".
+func (api *APIBuilder) GetRelPath() string {
+	return api.relativePath
+}
+
 // GetReport returns an error may caused by party's methods.
 func (api *APIBuilder) GetReport() error {
 	return api.reporter.Return()
@@ -292,7 +300,7 @@ func (api *APIBuilder) PartyFunc(relativePath string, partyBuilderFunc func(p Pa
 // this specific "subdomain".
 //
 // If called from a child party then the subdomain will be prepended to the path instead of appended.
-// So if app.Subdomain("admin.").Subdomain("panel.") then the result is: "panel.admin.".
+// So if app.Subdomain("admin").Subdomain("panel") then the result is: "panel.admin.".
 func (api *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler) Party {
 	if api.relativePath == SubdomainWildcardIndicator {
 		// cannot concat wildcard subdomain with something else
@@ -300,6 +308,12 @@ func (api *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler
 			api.relativePath, subdomain)
 		return api
 	}
+	if l := len(subdomain); l < 1 {
+		return api
+	} else if subdomain[l-1] != '.' {
+		subdomain += "."
+	}
+
 	return api.Party(subdomain, middleware...)
 }
 
diff --git a/core/router/party.go b/core/router/party.go
index f1a1a9d0..9f840539 100644
--- a/core/router/party.go
+++ b/core/router/party.go
@@ -14,6 +14,11 @@ import (
 //
 // Look the "APIBuilder" for its implementation.
 type Party interface {
+	// GetRelPath returns the current party's relative path.
+	// i.e:
+	// if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users".
+	// if r := app.Party("www.") or app.Subdomain("www") then the `r.GetRelPath()` is the "www.".
+	GetRelPath() string
 	// GetReporter returns the reporter for adding errors
 	GetReporter() *errors.Reporter
 	// Macros returns the macro map which is responsible
diff --git a/core/router/router.go b/core/router/router.go
index 805c0df1..9b81d2ea 100644
--- a/core/router/router.go
+++ b/core/router/router.go
@@ -114,13 +114,13 @@ type WrapperFunc func(w http.ResponseWriter, r *http.Request, firstNextIsTheRout
 //
 // Before build.
 func (router *Router) WrapRouter(wrapperFunc WrapperFunc) {
-	router.mu.Lock()
-	defer router.mu.Unlock()
-
 	if wrapperFunc == nil {
 		return
 	}
 
+	router.mu.Lock()
+	defer router.mu.Unlock()
+
 	if router.wrapperFunc != nil {
 		// wrap into one function, from bottom to top, end to begin.
 		nextWrapper := wrapperFunc
diff --git a/core/router/router_subdomain_redirect_wrapper.go b/core/router/router_subdomain_redirect_wrapper.go
new file mode 100644
index 00000000..beb438f6
--- /dev/null
+++ b/core/router/router_subdomain_redirect_wrapper.go
@@ -0,0 +1,163 @@
+package router
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/kataras/iris/context"
+	"github.com/kataras/iris/core/netutil"
+)
+
+type subdomainRedirectWrapper struct {
+	// the func which will give us the root domain,
+	// it's declared as a func because in that state the application is not configurated neither ran yet.
+	root func() string
+	// the from and to locations, if subdomains must end with dot('.').
+	from, to string
+	// true if from wildcard subdomain is given by 'from' ("*." or '*').
+	isFromAny bool
+	// true for the location that is the root domain ('/', '.' or "").
+	isFromRoot, isToRoot bool
+}
+
+func pathIsRootDomain(partyRelPath string) bool {
+	return partyRelPath == "/" || partyRelPath == "" || partyRelPath == "."
+}
+
+func pathIsWildcard(partyRelPath string) bool {
+	return partyRelPath == SubdomainWildcardIndicator || partyRelPath == "*"
+}
+
+// NewSubdomainRedirectWrapper returns a router wrapper which
+// if it's registered to the router via `router#WrapRouter` it
+// redirects(StatusMovedPermanently) a subdomain or the root domain to another subdomain or to the root domain.
+//
+// It receives three arguments,
+// the first one is a function which returns the root domain, (in the application it's the app.ConfigurationReadOnly().GetVHost()).
+// The second and third are the from and to locations, 'from' can be a wildcard subdomain as well (*. or *)
+// 'to' is not allowed to be a wildcard for obvious reasons,
+// 'from' can be the root domain when the 'to' is not the root domain and visa-versa.
+// To declare a root domain as 'from' or 'to' you MUST pass an empty string or a slash('/') or a dot('.').
+// Important note: the 'from' and 'to' should end with "." like we use the `APIBuilder#Party`, if they are subdomains.
+//
+// Usage(package-level):
+// sd := NewSubdomainRedirectWrapper(func() string { return "mydomain.com" }, ".", "www.")
+// router.WrapRouter(sd)
+//
+// Usage(high-level using `iris#Application.SubdomainRedirect`)
+// www := app.Subdomain("www")
+// app.SubdomainRedirect(app, www)
+// Because app's rel path is "/" it translates it to the root domain
+// and www's party's rel path is the "www.", so it's the target subdomain.
+//
+// All the above code snippets will register a router wrapper which will
+// redirect all http(s)://mydomain.com/%anypath% to http(s)://www.mydomain.com/%anypath%.
+//
+// One or more subdomain redirect wrappers can be used to the same router instance.
+//
+// NewSubdomainRedirectWrapper may return nil if not allowed input arguments values were received
+// but in that case, the `WrapRouter` will, simply, ignore that wrapper.
+//
+// Example: https://github.com/kataras/iris/tree/master/_examples/subdomains/redirect
+func NewSubdomainRedirectWrapper(rootDomainGetter func() string, from, to string) WrapperFunc {
+	// we can return nil,
+	// because if wrapper is nil then it's not be used on the `router#WrapRouter`.
+	if from == to {
+		// cannot redirect to the same location, cycle.
+		return nil
+	}
+
+	if pathIsWildcard(to) {
+		// cannot redirect to "any location".
+		return nil
+	}
+
+	isFromRoot, isToRoot := pathIsRootDomain(from), pathIsRootDomain(to)
+	if isFromRoot && isToRoot {
+		// cannot redirect to the root domain from the root domain.
+		return nil
+	}
+
+	sd := &subdomainRedirectWrapper{
+		root:       rootDomainGetter,
+		from:       from,
+		to:         to,
+		isFromAny:  pathIsWildcard(from),
+		isFromRoot: isFromRoot,
+		isToRoot:   isToRoot,
+	}
+
+	return sd.Wrapper
+}
+
+const sufscheme = "://"
+
+func getFullScheme(r *http.Request) string {
+	if !r.URL.IsAbs() {
+		// url scheme is empty.
+		return netutil.SchemeHTTP + sufscheme
+	}
+	return r.URL.Scheme + sufscheme
+}
+
+// Wrapper is the function that is being used to wrap the router with a redirect
+// service that is able to redirect between (sub)domains as fast as possible.
+// Please take a look at the `NewSubdomainRedirectWrapper` function for more.
+func (s *subdomainRedirectWrapper) Wrapper(w http.ResponseWriter, r *http.Request, router http.HandlerFunc) {
+	// Author's note:
+	// I use the StatusMovedPermanently(301) instead of the the StatusPermanentRedirect(308)
+	// because older browsers may not be able to recognise that status code (the RFC 7538, is not so old)
+	// although note that move is not the same thing as redirect: move reminds a specific address or location moved while
+	// redirect is a new location.
+
+	host := context.GetHost(r)
+	root := s.root()
+	hasSubdomain := host != root
+
+	if !hasSubdomain && !s.isFromRoot {
+		// if the current endpoint is not a subdomain
+		// and the redirect is not configured to be used from root domain to a subdomain.
+		// This check comes first because it's the most common scenario.
+		router(w, r)
+		return
+	}
+
+	if hasSubdomain {
+		// the current endpoint is a subdomain and
+		// redirect is used for a subdomain to another subdomain or to its root domain.
+		subdomain := strings.TrimSuffix(host, root) // with dot '.'.
+		if s.to == subdomain {
+			// we are in the subdomain we wanted to be redirected,
+			// remember: a redirect response will fire a new request.
+			// This check is needed to not allow cycles (too many redirects).
+			router(w, r)
+			return
+		}
+
+		if subdomain == s.from || s.isFromAny {
+			resturi := r.URL.RequestURI()
+			if s.isToRoot {
+				// from a specific subdomain or any subdomain to the root domain.
+				http.Redirect(w, r, getFullScheme(r)+root+resturi, http.StatusMovedPermanently)
+				return
+			}
+			// from a specific subdomain or any subdomain to a specific subdomain.
+			http.Redirect(w, r, getFullScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
+			return
+		}
+
+		// the from subdomain is not matched and it's not from root.
+		router(w, r)
+		return
+	}
+
+	if s.isFromRoot {
+		resturi := r.URL.RequestURI()
+		// we are not inside a subdomain, so we are in the root domain
+		// and the redirect is configured to be used from root domain to a subdomain.
+		http.Redirect(w, r, getFullScheme(r)+s.to+root+resturi, http.StatusMovedPermanently)
+		return
+	}
+
+	router(w, r)
+}
diff --git a/iris.go b/iris.go
index 6c6205bd..846a2c29 100644
--- a/iris.go
+++ b/iris.go
@@ -187,6 +187,41 @@ func Default() *Application {
 	return app
 }
 
+// WWW creates and returns a "www." subdomain.
+// The difference from `app.Subdomain("www")` or `app.Party("www.")` is that the `app.WWW()` method
+// wraps the router so all http(s)://mydomain.com will be redirect to http(s)://www.mydomain.com.
+// Other subdomains can be registered using the app: `sub := app.Subdomain("mysubdomain")`,
+// child subdomains can be registered using the www := app.WWW(); www.Subdomain("wwwchildSubdomain").
+func (app *Application) WWW() router.Party {
+	return app.SubdomainRedirect(app, app.Subdomain("www"))
+}
+
+// SubdomainRedirect registers a router wrapper which
+// redirects(StatusMovedPermanently) a (sub)domain to another subdomain or to the root domain as fast as possible,
+// before the router's try to execute route's handler(s).
+//
+// It receives two arguments, they are the from and to/target locations,
+// 'from' can be a wildcard subdomain as well (app.WildcardSubdomain())
+// 'to' is not allowed to be a wildcard for obvious reasons,
+// 'from' can be the root domain(app) when the 'to' is not the root domain and visa-versa.
+//
+// Usage:
+// www := app.Subdomain("www") <- same as app.Party("www.")
+// app.SubdomainRedirect(app, www)
+// This will redirect all http(s)://mydomain.com/%anypath% to http(s)://www.mydomain.com/%anypath%.
+//
+// One or more subdomain redirects can be used to the same app instance.
+//
+// If you need more information about this implementation then you have to navigate through
+// the `core/router#NewSubdomainRedirectWrapper` function instead.
+//
+// Example: https://github.com/kataras/iris/tree/master/_examples/subdomains/redirect
+func (app *Application) SubdomainRedirect(from, to router.Party) router.Party {
+	sd := router.NewSubdomainRedirectWrapper(app.ConfigurationReadOnly().GetVHost, from.GetRelPath(), to.GetRelPath())
+	app.WrapRouter(sd)
+	return to
+}
+
 // Configure can called when modifications to the framework instance needed.
 // It accepts the framework instance
 // and returns an error which if it's not nil it's printed to the logger.
@@ -670,7 +705,7 @@ var ErrServerClosed = http.ErrServerClosed
 // `Listener`, `Server`, `Addr`, `TLS`, `AutoTLS` and `Raw`.
 func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error {
 	// first Build because it doesn't need anything from configuration,
-	//  this give the user the chance to modify the router inside a configurator as well.
+	// this give the user the chance to modify the router inside a configurator as well.
 	if err := app.Build(); err != nil {
 		return errors.PrintAndReturnErrors(err, app.logger.Errorf)
 	}