mirror of
https://github.com/kataras/iris.git
synced 2026-01-10 21:45:57 +00:00
Add notes for the new lead maintainer of the open-source iris project and align with @get-ion/ion by @hiveminded
Former-commit-id: da4f38eb9034daa49446df3ee529423b98f9b331
This commit is contained in:
220
core/netutil/addr.go
Normal file
220
core/netutil/addr.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
loopbackRegex *regexp.Regexp
|
||||
loopbackSubRegex *regexp.Regexp
|
||||
machineHostname string
|
||||
)
|
||||
|
||||
func init() {
|
||||
loopbackRegex, _ = regexp.Compile(`^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$`)
|
||||
loopbackSubRegex, _ = regexp.Compile(`^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$`)
|
||||
machineHostname, _ = os.Hostname()
|
||||
}
|
||||
|
||||
// IsLoopbackSubdomain checks if a string is a subdomain or a hostname.
|
||||
var IsLoopbackSubdomain = func(s string) bool {
|
||||
if strings.HasPrefix(s, "127.0.0.1:") || s == "127.0.0.1" {
|
||||
return true
|
||||
}
|
||||
|
||||
valid := loopbackSubRegex.MatchString(s)
|
||||
if !valid { // if regex failed to match it, then try with the pc's name.
|
||||
if !strings.Contains(machineHostname, ".") { // if machine name's is not a loopback by itself
|
||||
valid = s == machineHostname
|
||||
}
|
||||
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
// IsLoopbackHost tries to catch the local addresses when a developer
|
||||
// navigates to a subdomain that its hostname differs from Application.Config.Addr.
|
||||
// Developer may want to override this function to return always false
|
||||
// in order to not allow different hostname from Application.Config.Addr in local environment (remote is not reached).
|
||||
var IsLoopbackHost = func(requestHost string) bool {
|
||||
// this func will be called if we have a subdomain actually, not otherwise, so we are
|
||||
// safe to do some hacks.
|
||||
|
||||
// if subdomain.127.0.0.1:8080/path, we need to compare the 127.0.0.1
|
||||
// if subdomain.localhost:8080/mypath, we need to compare the localhost
|
||||
// if subdomain.127.0.0.1/mypath, we need to compare the 127.0.0.1
|
||||
// if subdomain.127.0.0.1, we need to compare the 127.0.0.1
|
||||
|
||||
// find the first index of [:]8080 or [/]mypath or nothing(root with loopback address like 127.0.0.1)
|
||||
// remember: we are not looking for .com or these things, if is up and running then the developer
|
||||
// would probably not want to reach the server with different Application.Config.Addr than
|
||||
// he/she declared.
|
||||
portOrPathIdx := strings.LastIndexByte(requestHost, ':')
|
||||
|
||||
if portOrPathIdx == 0 { // 0.0.0.0:[...]/localhost:[...]/127.0.0.1:[...]/ipv6 local...
|
||||
return true
|
||||
}
|
||||
// this will not catch ipv6 loopbacks like subdomain.0000:0:0000::01.1:8080
|
||||
// but, again, is for developers only, is hard to try to navigate with something like this,
|
||||
// and if that happened, I provide a way to override the whole "algorithm" to a custom one via "IsLoopbackHost".
|
||||
if portOrPathIdx == -1 {
|
||||
portOrPathIdx = strings.LastIndexByte(requestHost, '/')
|
||||
if portOrPathIdx == -1 {
|
||||
portOrPathIdx = len(requestHost) // if not port or / then it should be something like subodmain.127.0.0.1
|
||||
}
|
||||
}
|
||||
|
||||
// remove the left part of subdomain[.]<- and the right part of ->[:]8080/[/]mypath
|
||||
// so result should be 127.0.0.1/localhost/0.0.0.0 or any ip
|
||||
subdomainFinishIdx := strings.IndexByte(requestHost, '.') + 1
|
||||
if l := len(requestHost); l <= subdomainFinishIdx || l < portOrPathIdx {
|
||||
return false // for any case to not panic here.
|
||||
}
|
||||
|
||||
hostname := requestHost[subdomainFinishIdx:portOrPathIdx]
|
||||
if hostname == "" {
|
||||
return false
|
||||
}
|
||||
// we use regex here to catch all posibilities, we compiled the regex at init func
|
||||
// so it shouldn't hurt so much, but we don't care a lot because it's a special case here
|
||||
// because this function will be called only if developer him/herself can reach the server
|
||||
// with a loopback/local address, so we are totally safe.
|
||||
valid := loopbackRegex.MatchString(hostname)
|
||||
if !valid { // if regex failed to match it, then try with the pc's name.
|
||||
valid = hostname == machineHostname
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
const (
|
||||
// defaultServerHostname returns the default hostname which is "localhost"
|
||||
defaultServerHostname = "localhost"
|
||||
// defaultServerPort returns the default port which is 8080, not used
|
||||
defaultServerPort = 8080
|
||||
)
|
||||
|
||||
var (
|
||||
// defaultServerAddr the default server addr which is: localhost:8080
|
||||
defaultServerAddr = defaultServerHostname + ":" + strconv.Itoa(defaultServerPort)
|
||||
)
|
||||
|
||||
// ResolveAddr tries to convert a given string to an address which is compatible with net.Listener and server
|
||||
func ResolveAddr(addr string) string {
|
||||
// check if addr has :port, if not do it +:80 ,we need the hostname for many cases
|
||||
a := addr
|
||||
if a == "" {
|
||||
// check for os environments
|
||||
if oshost := os.Getenv("ADDR"); oshost != "" {
|
||||
a = oshost
|
||||
} else if oshost := os.Getenv("HOST"); oshost != "" {
|
||||
a = oshost
|
||||
} else if oshost := os.Getenv("HOSTNAME"); oshost != "" {
|
||||
a = oshost
|
||||
// check for port also here
|
||||
if osport := os.Getenv("PORT"); osport != "" {
|
||||
a += ":" + osport
|
||||
}
|
||||
} else if osport := os.Getenv("PORT"); osport != "" {
|
||||
a = ":" + osport
|
||||
} else {
|
||||
a = ":http"
|
||||
}
|
||||
}
|
||||
if portIdx := strings.IndexByte(a, ':'); portIdx == 0 {
|
||||
if a[portIdx:] == ":https" {
|
||||
a = defaultServerHostname + ":443"
|
||||
} else {
|
||||
// if contains only :port ,then the : is the first letter, so we dont have setted a hostname, lets set it
|
||||
a = defaultServerHostname + a
|
||||
}
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ResolveHostname receives an addr of form host[:port] and returns the hostname part of it
|
||||
// ex: localhost:8080 will return the `localhost`, mydomain.com:8080 will return the 'mydomain'
|
||||
func ResolveHostname(addr string) string {
|
||||
if idx := strings.IndexByte(addr, ':'); idx == 0 {
|
||||
// only port, then return the localhost hostname
|
||||
return "localhost"
|
||||
} else if idx > 0 {
|
||||
return addr[0:idx]
|
||||
}
|
||||
// it's already hostname
|
||||
return addr
|
||||
}
|
||||
|
||||
// ResolveVHost tries to get the hostname if port is no needed for Addr's usage.
|
||||
// Addr is being used inside router->subdomains
|
||||
// and inside {{ url }} template funcs.
|
||||
// It should be the same as "browser's"
|
||||
// usually they removing :80 or :443.
|
||||
func ResolveVHost(addr string) string {
|
||||
if addr == ":https" || addr == ":http" {
|
||||
return "localhost"
|
||||
}
|
||||
|
||||
if idx := strings.IndexByte(addr, ':'); idx == 0 {
|
||||
// only port, then return the localhost hostname
|
||||
return "localhost" + addr[idx:]
|
||||
}
|
||||
|
||||
// with ':' in order to not replace the ipv6 loopback addresses
|
||||
addr = strings.Replace(addr, "0.0.0.0:", "localhost:", 1)
|
||||
port := ResolvePort(addr)
|
||||
if port == 80 || port == 443 {
|
||||
return ResolveHostname(addr)
|
||||
}
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
const (
|
||||
// SchemeHTTPS the "https" url scheme.
|
||||
SchemeHTTPS = "https"
|
||||
// SchemeHTTP the "http" url scheme.
|
||||
SchemeHTTP = "http"
|
||||
)
|
||||
|
||||
// ResolvePort receives an addr of form host[:port] and returns the port part of it
|
||||
// ex: localhost:8080 will return the `8080`, mydomain.com will return the '80'
|
||||
func ResolvePort(addr string) int {
|
||||
if portIdx := strings.IndexByte(addr, ':'); portIdx != -1 {
|
||||
afP := addr[portIdx+1:]
|
||||
p, err := strconv.Atoi(afP)
|
||||
if err == nil {
|
||||
return p
|
||||
} else if afP == SchemeHTTPS { // it's not number, check if it's :https
|
||||
return 443
|
||||
}
|
||||
}
|
||||
return 80
|
||||
}
|
||||
|
||||
// ResolveScheme returns "https://" if "isTLS" receiver is true,
|
||||
// otherwise "http://".
|
||||
func ResolveScheme(isTLS bool) string {
|
||||
if isTLS {
|
||||
return SchemeHTTPS
|
||||
}
|
||||
|
||||
return SchemeHTTP
|
||||
}
|
||||
|
||||
// ResolveSchemeFromVHost returns the scheme based on the "vhost".
|
||||
func ResolveSchemeFromVHost(vhost string) string {
|
||||
// pure check
|
||||
isTLS := strings.HasPrefix(vhost, SchemeHTTPS) || ResolvePort(vhost) == 443
|
||||
return ResolveScheme(isTLS)
|
||||
}
|
||||
|
||||
// ResolveURL takes the scheme and an address
|
||||
// and returns its URL, pure implementation but it does the job.
|
||||
func ResolveURL(scheme string, addr string) string {
|
||||
host := ResolveVHost(addr)
|
||||
return scheme + "://" + host
|
||||
}
|
||||
43
core/netutil/addr_test.go
Normal file
43
core/netutil/addr_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsLoopbackHost(t *testing.T) {
|
||||
tests := []struct {
|
||||
host string
|
||||
valid bool
|
||||
}{
|
||||
{"subdomain.127.0.0.1:8080", true},
|
||||
{"subdomain.127.0.0.1", true},
|
||||
{"subdomain.localhost:8080", true},
|
||||
{"subdomain.localhost", true},
|
||||
{"subdomain.127.0000.0000.1:8080", true},
|
||||
{"subdomain.127.0000.0000.1", true},
|
||||
{"subdomain.127.255.255.254:8080", true},
|
||||
{"subdomain.127.255.255.254", true},
|
||||
|
||||
{"subdomain.0000:0:0000::01.1:8080", false},
|
||||
{"subdomain.0000:0:0000::01", false},
|
||||
{"subdomain.0000:0:0000::01.1:8080", false},
|
||||
{"subdomain.0000:0:0000::01", false},
|
||||
{"subdomain.0000:0000:0000:0000:0000:0000:0000:0001:8080", true},
|
||||
{"subdomain.0000:0000:0000:0000:0000:0000:0000:0001", false},
|
||||
|
||||
{"subdomain.example:8080", false},
|
||||
{"subdomain.example", false},
|
||||
{"subdomain.example.com:8080", false},
|
||||
{"subdomain.example.com", false},
|
||||
{"subdomain.com", false},
|
||||
{"subdomain", false},
|
||||
{".subdomain", false},
|
||||
{"127.0.0.1.com", false},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
if expected, got := tt.valid, IsLoopbackHost(tt.host); expected != got {
|
||||
t.Fatalf("[%d] expected %t but got %t for %s", i, expected, got, tt.host)
|
||||
}
|
||||
}
|
||||
}
|
||||
33
core/netutil/server.go
Normal file
33
core/netutil/server.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// used on host/supervisor/task and router/path
|
||||
|
||||
// IsTLS returns true if the "srv" contains any certificates
|
||||
// or a get certificate function, meaning that is secure.
|
||||
func IsTLS(srv *http.Server) bool {
|
||||
if cfg := srv.TLSConfig; cfg != nil &&
|
||||
(len(cfg.Certificates) > 0 || cfg.GetCertificate != nil) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ResolveSchemeFromServer tries to resolve a url scheme
|
||||
// based on the server's configuration.
|
||||
// Returns "https" on secure server,
|
||||
// otherwise "http".
|
||||
func ResolveSchemeFromServer(srv *http.Server) string {
|
||||
return ResolveScheme(IsTLS(srv))
|
||||
}
|
||||
|
||||
// ResolveURLFromServer returns the scheme+host from a server.
|
||||
func ResolveURLFromServer(srv *http.Server) string {
|
||||
scheme := ResolveSchemeFromServer(srv)
|
||||
host := ResolveVHost(srv.Addr)
|
||||
return scheme + "://" + host
|
||||
}
|
||||
159
core/netutil/tcp.go
Normal file
159
core/netutil/tcp.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
// connections. It's used by Run, ListenAndServe and ListenAndServeTLS so
|
||||
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||
// go away.
|
||||
//
|
||||
// A raw copy of standar library.
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
// Accept accepts tcp connections aka clients.
|
||||
func (l tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := l.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
var (
|
||||
errPortAlreadyUsed = errors.New("port is already used")
|
||||
errRemoveUnix = errors.New("unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s")
|
||||
errChmod = errors.New("cannot chmod %#o for %q: %s")
|
||||
errCertKeyMissing = errors.New("you should provide certFile and keyFile for TLS/SSL")
|
||||
errParseTLS = errors.New("couldn't load TLS, certFile=%q, keyFile=%q. Trace: %s")
|
||||
)
|
||||
|
||||
// TCP returns a new tcp(ipv6 if supported by network) and an error on failure.
|
||||
func TCP(addr string) (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// TCPKeepAlive returns a new tcp keep alive Listener and an error on failure.
|
||||
func TCPKeepAlive(addr string) (ln net.Listener, err error) {
|
||||
// if strings.HasPrefix(addr, "127.0.0.1") {
|
||||
// // it's ipv4, use ipv4 tcp listener instead of the default ipv6. Don't.
|
||||
// ln, err = net.Listen("tcp4", addr)
|
||||
// } else {
|
||||
// ln, err = TCP(addr)
|
||||
// }
|
||||
|
||||
ln, err = TCP(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil
|
||||
}
|
||||
|
||||
// UNIX returns a new unix(file) Listener.
|
||||
func UNIX(socketFile string, mode os.FileMode) (net.Listener, error) {
|
||||
if errOs := os.Remove(socketFile); errOs != nil && !os.IsNotExist(errOs) {
|
||||
return nil, errRemoveUnix.Format(socketFile, errOs.Error())
|
||||
}
|
||||
|
||||
l, err := net.Listen("unix", socketFile)
|
||||
if err != nil {
|
||||
return nil, errPortAlreadyUsed.AppendErr(err)
|
||||
}
|
||||
|
||||
if err = os.Chmod(socketFile, mode); err != nil {
|
||||
return nil, errChmod.Format(mode, socketFile, err.Error())
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// TLS returns a new TLS Listener and an error on failure.
|
||||
func TLS(addr, certFile, keyFile string) (net.Listener, error) {
|
||||
|
||||
if certFile == "" || keyFile == "" {
|
||||
return nil, errCertKeyMissing
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, errParseTLS.Format(certFile, keyFile, err)
|
||||
}
|
||||
|
||||
return CERT(addr, cert)
|
||||
}
|
||||
|
||||
// CERT returns a listener which contans tls.Config with the provided certificate, use for ssl.
|
||||
func CERT(addr string, cert tls.Certificate) (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
PreferServerCipherSuites: true,
|
||||
}
|
||||
return tls.NewListener(l, tlsConfig), nil
|
||||
}
|
||||
|
||||
// LETSENCRYPT returns a new Automatic TLS Listener using letsencrypt.org service
|
||||
// receives three parameters,
|
||||
// the first is the host of the server,
|
||||
// second can be the server name(domain) or empty if skip verification is the expected behavior (not recommended)
|
||||
// and the third is optionally, the cache directory, if you skip it then the cache directory is "./certcache"
|
||||
// if you want to disable cache directory then simple give it a value of empty string ""
|
||||
//
|
||||
// does NOT supports localhost domains for testing.
|
||||
//
|
||||
// this is the recommended function to use when you're ready for production state.
|
||||
func LETSENCRYPT(addr string, serverName string, cacheDirOptional ...string) (net.Listener, error) {
|
||||
if portIdx := strings.IndexByte(addr, ':'); portIdx == -1 {
|
||||
addr += ":443"
|
||||
}
|
||||
|
||||
l, err := TCP(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cacheDir := "./certcache"
|
||||
if len(cacheDirOptional) > 0 {
|
||||
cacheDir = cacheDirOptional[0]
|
||||
}
|
||||
|
||||
m := autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
} // HostPolicy is missing, if user wants it, then she/he should manually
|
||||
|
||||
if cacheDir == "" {
|
||||
// then the user passed empty by own will, then I guess she/he doesnt' want any cache directory
|
||||
} else {
|
||||
m.Cache = autocert.DirCache(cacheDir)
|
||||
}
|
||||
tlsConfig := &tls.Config{GetCertificate: m.GetCertificate}
|
||||
|
||||
// use InsecureSkipVerify or ServerName to a value
|
||||
if serverName == "" {
|
||||
// if server name is invalid then bypass it
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
} else {
|
||||
tlsConfig.ServerName = serverName
|
||||
}
|
||||
|
||||
return tls.NewListener(l, tlsConfig), nil
|
||||
}
|
||||
Reference in New Issue
Block a user