mirror of
https://github.com/kataras/iris.git
synced 2025-12-27 06:47:08 +00:00
20 days of unstoppable work. Waiting fo go 1.8, I didn't finish yet, some touches remains.
Former-commit-id: ed84f99c89f43fe5e980a8e6d0ee22c186f0e1b9
This commit is contained in:
299
addr.go
Normal file
299
addr.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package iris
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/go-errors"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
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")
|
||||
)
|
||||
|
||||
// TCP4 returns a new tcp4 Listener
|
||||
func TCP4(addr string) (net.Listener, error) {
|
||||
return net.Listen("tcp4", ParseHost(addr))
|
||||
}
|
||||
|
||||
// TCPKeepAlive returns a new tcp4 keep alive Listener
|
||||
func TCPKeepAlive(addr string) (net.Listener, error) {
|
||||
ln, err := TCP4(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return TCPKeepAliveListener{ln.(*net.TCPListener)}, err
|
||||
}
|
||||
|
||||
// UNIX returns a new unix(file) Listener
|
||||
func UNIX(addr string, mode os.FileMode) (net.Listener, error) {
|
||||
if errOs := os.Remove(addr); errOs != nil && !os.IsNotExist(errOs) {
|
||||
return nil, errRemoveUnix.Format(addr, errOs.Error())
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unix", addr)
|
||||
if err != nil {
|
||||
return nil, errPortAlreadyUsed.AppendErr(err)
|
||||
}
|
||||
|
||||
if err = os.Chmod(addr, mode); err != nil {
|
||||
return nil, errChmod.Format(mode, addr, err.Error())
|
||||
}
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
// TLS returns a new TLS Listener
|
||||
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) {
|
||||
ln, err := TCP4(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
PreferServerCipherSuites: true,
|
||||
}
|
||||
return tls.NewListener(ln, tlsConfig), nil
|
||||
}
|
||||
|
||||
// LETSENCRYPT returns a new Automatic TLS Listener using letsencrypt.org service
|
||||
// receives two parameters, the first is the domain of the server
|
||||
// and the second 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, cacheDirOptional ...string) (net.Listener, error) {
|
||||
if portIdx := strings.IndexByte(addr, ':'); portIdx == -1 {
|
||||
addr += ":443"
|
||||
}
|
||||
|
||||
ln, err := TCP4(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
|
||||
// configure the autocertmanager and use the `iris.Default.Serve` to pass that listener
|
||||
|
||||
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}
|
||||
tlsLn := tls.NewListener(ln, tlsConfig)
|
||||
|
||||
return tlsLn, nil
|
||||
}
|
||||
|
||||
// TCPKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
// connections.
|
||||
// Dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||
// go away
|
||||
// It is not used by default if you want to pass a keep alive listener
|
||||
// then just pass the child listener, example:
|
||||
// listener := iris.TCPKeepAliveListener{iris.TCP4(":8080").(*net.TCPListener)}
|
||||
type TCPKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
// Accept implements the listener and sets the keep alive period which is 3minutes
|
||||
func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = tc.SetKeepAlive(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
///TODO:
|
||||
// func (ln TCPKeepAliveListener) Close() error {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// ParseHost tries to convert a given string to an address which is compatible with net.Listener and server
|
||||
func ParseHost(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
|
||||
}
|
||||
}
|
||||
|
||||
/* changed my mind, don't add 80, this will cause problems on unix listeners, and it's not really necessary because we take the port using parsePort
|
||||
if portIdx := strings.IndexByte(a, ':'); portIdx < 0 {
|
||||
// missing port part, add it
|
||||
a = a + ":80"
|
||||
}*/
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ParseHostname 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 ParseHostname(addr string) string {
|
||||
idx := strings.IndexByte(addr, ':')
|
||||
if idx == 0 {
|
||||
// only port, then return 0.0.0.0
|
||||
return "0.0.0.0"
|
||||
} else if idx > 0 {
|
||||
return addr[0:idx]
|
||||
}
|
||||
// it's already hostname
|
||||
return addr
|
||||
}
|
||||
|
||||
// ParsePort 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 ParsePort(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 == "https" { // it's not number, check if it's :https
|
||||
return 443
|
||||
}
|
||||
}
|
||||
return 80
|
||||
}
|
||||
|
||||
const (
|
||||
// SchemeHTTPS returns "https://" (full)
|
||||
SchemeHTTPS = "https://"
|
||||
// SchemeHTTP returns "http://" (full)
|
||||
SchemeHTTP = "http://"
|
||||
)
|
||||
|
||||
// ParseScheme returns the scheme based on the host,addr,domain
|
||||
// Note: the full scheme not just http*,https* *http:// *https://
|
||||
func ParseScheme(domain string) string {
|
||||
// pure check
|
||||
if strings.HasPrefix(domain, SchemeHTTPS) || ParsePort(domain) == 443 {
|
||||
return SchemeHTTPS
|
||||
}
|
||||
return SchemeHTTP
|
||||
}
|
||||
|
||||
// ProxyHandler returns a new net/http.Handler which works as 'proxy', maybe doesn't suits you look its code before using that in production
|
||||
var ProxyHandler = func(redirectSchemeAndHost string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// override the handler and redirect all requests to this addr
|
||||
redirectTo := redirectSchemeAndHost
|
||||
fakehost := r.URL.Host
|
||||
path := r.URL.EscapedPath()
|
||||
if strings.Count(fakehost, ".") >= 3 { // propably a subdomain, pure check but doesn't matters don't worry
|
||||
if sufIdx := strings.LastIndexByte(fakehost, '.'); sufIdx > 0 {
|
||||
// check if the last part is a number instead of .com/.gr...
|
||||
// if it's number then it's propably is 0.0.0.0 or 127.0.0.1... so it shouldn' use subdomain
|
||||
if _, err := strconv.Atoi(fakehost[sufIdx+1:]); err != nil {
|
||||
// it's not number then process the try to parse the subdomain
|
||||
redirectScheme := ParseScheme(redirectSchemeAndHost)
|
||||
realHost := strings.Replace(redirectSchemeAndHost, redirectScheme, "", 1)
|
||||
redirectHost := strings.Replace(fakehost, fakehost, realHost, 1)
|
||||
redirectTo = redirectScheme + redirectHost + path
|
||||
http.Redirect(w, r, redirectTo, StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if path != "/" {
|
||||
redirectTo += path
|
||||
}
|
||||
if redirectTo == r.URL.String() {
|
||||
return
|
||||
}
|
||||
|
||||
// redirectTo := redirectSchemeAndHost + r.RequestURI
|
||||
|
||||
http.Redirect(w, r, redirectTo, StatusMovedPermanently)
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy not really a proxy, it's just
|
||||
// starts a server listening on proxyAddr but redirects all requests to the redirectToSchemeAndHost+$path
|
||||
// nothing special, use it only when you want to start a secondary server which its only work is to redirect from one requested path to another
|
||||
//
|
||||
// returns a close function
|
||||
func Proxy(proxyAddr string, redirectSchemeAndHost string) func() error {
|
||||
proxyAddr = ParseHost(proxyAddr)
|
||||
|
||||
// override the handler and redirect all requests to this addr
|
||||
h := ProxyHandler(redirectSchemeAndHost)
|
||||
prx := New(OptionDisableBanner(true))
|
||||
prx.Adapt(RouterBuilderPolicy(func(RouteRepository, ContextPool) http.Handler {
|
||||
return h
|
||||
}))
|
||||
|
||||
go prx.Listen(proxyAddr)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
return func() error { return prx.Close() }
|
||||
}
|
||||
Reference in New Issue
Block a user