mirror of
				https://github.com/ossrs/srs.git
				synced 2025-03-09 15:49:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			643 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			643 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
The MIT License (MIT)
 | 
						|
 | 
						|
Copyright (c) 2019 winlin
 | 
						|
 | 
						|
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
						|
of this software and associated documentation files (the "Software"), to deal
 | 
						|
in the Software without restriction, including without limitation the rights
 | 
						|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
						|
copies of the Software, and to permit persons to whom the Software is
 | 
						|
furnished to do so, subject to the following conditions:
 | 
						|
 | 
						|
The above copyright notice and this permission notice shall be included in all
 | 
						|
copies or substantial portions of the Software.
 | 
						|
 | 
						|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
						|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
						|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
						|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
						|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
						|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
						|
SOFTWARE.
 | 
						|
*/
 | 
						|
 | 
						|
/*
 | 
						|
 This the main entrance of https-proxy, proxy to api or other http server.
 | 
						|
*/
 | 
						|
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"crypto/tls"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	oe "github.com/ossrs/go-oryx-lib/errors"
 | 
						|
	oh "github.com/ossrs/go-oryx-lib/http"
 | 
						|
	"github.com/ossrs/go-oryx-lib/https"
 | 
						|
	ol "github.com/ossrs/go-oryx-lib/logger"
 | 
						|
	"io/ioutil"
 | 
						|
	"log"
 | 
						|
	"net"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httputil"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
)
 | 
						|
 | 
						|
type Strings []string
 | 
						|
 | 
						|
func (v *Strings) String() string {
 | 
						|
	return fmt.Sprintf("strings [%v]", strings.Join(*v, ","))
 | 
						|
}
 | 
						|
 | 
						|
func (v *Strings) Set(value string) error {
 | 
						|
	*v = append(*v, value)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func shouldProxyURL(srcPath, proxyPath string) bool {
 | 
						|
	if !strings.HasSuffix(srcPath, "/") {
 | 
						|
		// /api to /api/
 | 
						|
		// /api.js to /api.js/
 | 
						|
		// /api/100 to /api/100/
 | 
						|
		srcPath += "/"
 | 
						|
	}
 | 
						|
 | 
						|
	if !strings.HasSuffix(proxyPath, "/") {
 | 
						|
		// /api/ to /api/
 | 
						|
		// to match /api/ or /api/100
 | 
						|
		// and not match /api.js/
 | 
						|
		proxyPath += "/"
 | 
						|
	}
 | 
						|
 | 
						|
	return strings.HasPrefix(srcPath, proxyPath)
 | 
						|
}
 | 
						|
 | 
						|
// about x-real-ip and x-forwarded-for or
 | 
						|
// about X-Real-IP and X-Forwarded-For or
 | 
						|
// https://segmentfault.com/q/1010000002409659
 | 
						|
// https://distinctplace.com/2014/04/23/story-behind-x-forwarded-for-and-x-real-ip-headers/
 | 
						|
// @remark http proxy will set the X-Forwarded-For.
 | 
						|
func addProxyAddToHeader(remoteAddr, realIP string, fwd []string, header http.Header, omitForward bool) {
 | 
						|
	rip, _, err := net.SplitHostPort(remoteAddr)
 | 
						|
	if err != nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if realIP != "" {
 | 
						|
		header.Set("X-Real-IP", realIP)
 | 
						|
	} else {
 | 
						|
		header.Set("X-Real-IP", rip)
 | 
						|
	}
 | 
						|
 | 
						|
	if !omitForward {
 | 
						|
		header["X-Forwarded-For"] = fwd[:]
 | 
						|
		header.Add("X-Forwarded-For", rip)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func filterByPreHook(ctx context.Context, preHook *url.URL, req *http.Request) error {
 | 
						|
	target := *preHook
 | 
						|
	target.RawQuery = strings.Join([]string{target.RawQuery, req.URL.RawQuery}, "&")
 | 
						|
 | 
						|
	api := target.String()
 | 
						|
	r, err := http.NewRequestWithContext(ctx, req.Method, api, nil)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// Add real ip and forwarded for to header.
 | 
						|
	// We should append the forward for pass-by.
 | 
						|
	addProxyAddToHeader(req.RemoteAddr, req.Header.Get("X-Real-IP"), req.Header["X-Forwarded-For"], r.Header, false)
 | 
						|
	ol.Tf(ctx, "Pre-hook proxy addr req=%v, r=%v", req.Header, r.Header)
 | 
						|
 | 
						|
	r2, err := http.DefaultClient.Do(r)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer r2.Body.Close()
 | 
						|
 | 
						|
	if r2.StatusCode != http.StatusOK {
 | 
						|
		return fmt.Errorf("Pre-hook HTTP StatusCode=%v %v", r2.StatusCode, r2.Status)
 | 
						|
	}
 | 
						|
 | 
						|
	b, err := ioutil.ReadAll(r2.Body)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	ol.Tf(ctx, "Pre-hook %v url=%v, res=%v, headers=%v", req.Method, api, string(b), r.Header)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func NewComplexProxy(ctx context.Context, proxyUrl, preHook *url.URL, originalRequest *http.Request) http.Handler {
 | 
						|
	// Hook before proxy it.
 | 
						|
	if preHook != nil {
 | 
						|
		if err := filterByPreHook(ctx, preHook, originalRequest); err != nil {
 | 
						|
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
				ol.Ef(ctx, "Pre-hook err %+v", err)
 | 
						|
				http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Start proxy it.
 | 
						|
	proxy := &httputil.ReverseProxy{}
 | 
						|
	proxyUrlQuery := proxyUrl.Query()
 | 
						|
 | 
						|
	// Create a proxy which attach a isolate logger.
 | 
						|
	elogger := log.New(os.Stderr, fmt.Sprintf("%v ", originalRequest.RemoteAddr), log.LstdFlags)
 | 
						|
	proxy.ErrorLog = elogger
 | 
						|
 | 
						|
	proxy.Director = func(r *http.Request) {
 | 
						|
		// about the x-real-schema, we proxy to backend to identify the client schema.
 | 
						|
		if rschema := r.Header.Get("X-Real-Schema"); rschema == "" {
 | 
						|
			if r.TLS == nil {
 | 
						|
				r.Header.Set("X-Real-Schema", "http")
 | 
						|
			} else {
 | 
						|
				r.Header.Set("X-Real-Schema", "https")
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Add real ip and forwarded for to header.
 | 
						|
		// We should omit the forward header, because the ReverseProxy will doit.
 | 
						|
		addProxyAddToHeader(r.RemoteAddr, r.Header.Get("X-Real-IP"), r.Header["X-Forwarded-For"], r.Header, true)
 | 
						|
		ol.Tf(ctx, "Proxy addr header %v", r.Header)
 | 
						|
 | 
						|
		r.URL.Scheme = proxyUrl.Scheme
 | 
						|
		r.URL.Host = proxyUrl.Host
 | 
						|
 | 
						|
		// Trim the prefix path.
 | 
						|
		if trimPrefix := proxyUrlQuery.Get("trimPrefix"); trimPrefix != "" {
 | 
						|
			r.URL.Path = strings.TrimPrefix(r.URL.Path, trimPrefix)
 | 
						|
		}
 | 
						|
		// Aadd the prefix to path.
 | 
						|
		if addPrefix := proxyUrlQuery.Get("addPrefix"); addPrefix != "" {
 | 
						|
			r.URL.Path = addPrefix + r.URL.Path
 | 
						|
		}
 | 
						|
 | 
						|
		// The original request.Host requested by the client.
 | 
						|
		// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
 | 
						|
		if r.Header.Get("X-Forwarded-Host") == "" {
 | 
						|
			r.Header.Set("X-Forwarded-Host", r.Host)
 | 
						|
		}
 | 
						|
 | 
						|
		// Set the Host of client request to the upstream server's, to act as client
 | 
						|
		// directly access the upstream server.
 | 
						|
		if proxyUrlQuery.Get("modifyRequestHost") != "false" {
 | 
						|
			r.Host = proxyUrl.Host
 | 
						|
		}
 | 
						|
 | 
						|
		ra, url, rip := r.RemoteAddr, r.URL.String(), r.Header.Get("X-Real-Ip")
 | 
						|
		ol.Tf(ctx, "proxy http rip=%v, addr=%v %v %v with headers %v", rip, ra, r.Method, url, r.Header)
 | 
						|
	}
 | 
						|
 | 
						|
	proxy.ModifyResponse = func(w *http.Response) error {
 | 
						|
		// We have already set the server, so remove the upstream one.
 | 
						|
		if proxyUrlQuery.Get("keepUpsreamServer") != "true" {
 | 
						|
			w.Header.Del("Server")
 | 
						|
		}
 | 
						|
 | 
						|
		// We already added this header, it will cause chrome failed when duplicated.
 | 
						|
		if w.Header.Get("Access-Control-Allow-Origin") != "" {
 | 
						|
			w.Header.Del("Access-Control-Allow-Origin")
 | 
						|
		}
 | 
						|
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	return proxy
 | 
						|
}
 | 
						|
 | 
						|
func run(ctx context.Context) error {
 | 
						|
	oh.Server = fmt.Sprintf("%v/%v", Signature(), Version())
 | 
						|
	fmt.Println(oh.Server, "HTTP/HTTPS static server with API proxy.")
 | 
						|
 | 
						|
	var httpPorts Strings
 | 
						|
	flag.Var(&httpPorts, "t", "http listen")
 | 
						|
	flag.Var(&httpPorts, "http", "http listen at. 0 to disable http.")
 | 
						|
 | 
						|
	var httpsPorts Strings
 | 
						|
	flag.Var(&httpsPorts, "s", "https listen")
 | 
						|
	flag.Var(&httpsPorts, "https", "https listen at. 0 to disable https. 443 to serve. ")
 | 
						|
 | 
						|
	var httpsDomains string
 | 
						|
	flag.StringVar(&httpsDomains, "d", "", "https the allow domains")
 | 
						|
	flag.StringVar(&httpsDomains, "domains", "", "https the allow domains, empty to allow all. for example: ossrs.net,www.ossrs.net")
 | 
						|
 | 
						|
	var html string
 | 
						|
	flag.StringVar(&html, "r", "./html", "the www web root")
 | 
						|
	flag.StringVar(&html, "root", "./html", "the www web root. support relative dir to argv[0].")
 | 
						|
 | 
						|
	var cacheFile string
 | 
						|
	flag.StringVar(&cacheFile, "e", "./letsencrypt.cache", "https the cache for letsencrypt")
 | 
						|
	flag.StringVar(&cacheFile, "cache", "./letsencrypt.cache", "https the cache for letsencrypt. support relative dir to argv[0].")
 | 
						|
 | 
						|
	var useLetsEncrypt bool
 | 
						|
	flag.BoolVar(&useLetsEncrypt, "l", false, "whether use letsencrypt CA")
 | 
						|
	flag.BoolVar(&useLetsEncrypt, "lets", false, "whether use letsencrypt CA. self sign if not.")
 | 
						|
 | 
						|
	var ssKey string
 | 
						|
	flag.StringVar(&ssKey, "k", "", "https self-sign key")
 | 
						|
	flag.StringVar(&ssKey, "ssk", "", "https self-sign key")
 | 
						|
 | 
						|
	var ssCert string
 | 
						|
	flag.StringVar(&ssCert, "c", "", `https self-sign cert`)
 | 
						|
	flag.StringVar(&ssCert, "ssc", "", `https self-sign cert`)
 | 
						|
 | 
						|
	var oproxies Strings
 | 
						|
	flag.Var(&oproxies, "p", "proxy ruler")
 | 
						|
	flag.Var(&oproxies, "proxy", "one or more proxy the matched path to backend, for example, -proxy http://127.0.0.1:8888/api/webrtc")
 | 
						|
 | 
						|
	var oprehooks Strings
 | 
						|
	flag.Var(&oprehooks, "pre-hook", "the pre-hook ruler, with request")
 | 
						|
 | 
						|
	var sdomains, skeys, scerts Strings
 | 
						|
	flag.Var(&sdomains, "sdomain", "the SSL hostname")
 | 
						|
	flag.Var(&skeys, "skey", "the SSL key for domain")
 | 
						|
	flag.Var(&scerts, "scert", "the SSL cert for domain")
 | 
						|
 | 
						|
	var trimSlashLimit int
 | 
						|
	var noRedirectIndex, trimLastSlash bool
 | 
						|
	flag.BoolVar(&noRedirectIndex, "no-redirect-index", false, "Whether serve with index.html without redirect.")
 | 
						|
	flag.BoolVar(&trimLastSlash, "trim-last-slash", false, "Whether trim last slash by HTTP redirect(302).")
 | 
						|
	flag.IntVar(&trimSlashLimit, "trim-slash-limit", 0, "Only trim last slash when got enough directories.")
 | 
						|
 | 
						|
	flag.Usage = func() {
 | 
						|
		fmt.Println(fmt.Sprintf("Usage: %v -t http -s https -d domains -r root -e cache -l lets -k ssk -c ssc -p proxy", os.Args[0]))
 | 
						|
		fmt.Println(fmt.Sprintf("	"))
 | 
						|
		fmt.Println(fmt.Sprintf("Options:"))
 | 
						|
		fmt.Println(fmt.Sprintf("	-t, -http string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Listen at port for HTTP server. Default: 0, disable HTTP."))
 | 
						|
		fmt.Println(fmt.Sprintf("	-s, -https string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Listen at port for HTTPS server. Default: 0, disable HTTPS."))
 | 
						|
		fmt.Println(fmt.Sprintf("	-r, -root string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			The www root path. Supports relative to argv[0]=%v. Default: ./html", path.Dir(os.Args[0])))
 | 
						|
		fmt.Println(fmt.Sprintf("	-no-redirect-index=bool"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Whether serve with index.html without redirect. Default: false"))
 | 
						|
		fmt.Println(fmt.Sprintf("	-p, -proxy string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Proxy path to backend. For example: http://127.0.0.1:8888/api/webrtc"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Proxy path to backend. For example: http://127.0.0.1:8888/api/webrtc?modifyRequestHost=false"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Proxy path to backend. For example: http://127.0.0.1:8888/api/webrtc?keepUpsreamServer=true"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Proxy path to backend. For example: http://127.0.0.1:8888/api/webrtc?trimPrefix=/ffmpeg"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Proxy path to backend. For example: http://127.0.0.1:8888/api/webrtc?addPrefix=/release"))
 | 
						|
		fmt.Println(fmt.Sprintf("	-pre-hook string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Pre-hook to backend, with request. For example: http://127.0.0.1:8888/api/stat"))
 | 
						|
		fmt.Println(fmt.Sprintf("Options for HTTPS(letsencrypt cert):"))
 | 
						|
		fmt.Println(fmt.Sprintf("	-l, -lets=bool"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Whether use letsencrypt CA. Default: false"))
 | 
						|
		fmt.Println(fmt.Sprintf("	-e, -cache string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			The letsencrypt cache. Default: ./letsencrypt.cache"))
 | 
						|
		fmt.Println(fmt.Sprintf("	-d, -domains string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			Set the validate HTTPS domain. For example: ossrs.net,www.ossrs.net"))
 | 
						|
		fmt.Println(fmt.Sprintf("Options for HTTPS(file-based cert):"))
 | 
						|
		fmt.Println(fmt.Sprintf("	-k, -ssk string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			The self-sign or validate file-based key file."))
 | 
						|
		fmt.Println(fmt.Sprintf("	-c, -ssc string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			The self-sign or validate file-based cert file."))
 | 
						|
		fmt.Println(fmt.Sprintf("	-sdomain string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			For multiple HTTPS site, the domain name. For example: ossrs.net"))
 | 
						|
		fmt.Println(fmt.Sprintf("	-skey string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			For multiple HTTPS site, the key file."))
 | 
						|
		fmt.Println(fmt.Sprintf("	-scert string"))
 | 
						|
		fmt.Println(fmt.Sprintf("			For multiple HTTPS site, the cert file."))
 | 
						|
		fmt.Println(fmt.Sprintf("For example:"))
 | 
						|
		fmt.Println(fmt.Sprintf("	%v -t 8080 -s 9443 -r ./html", os.Args[0]))
 | 
						|
		fmt.Println(fmt.Sprintf("	%v -t 8080 -s 9443 -r ./html -p http://ossrs.net:1985/api/v1/versions", os.Args[0]))
 | 
						|
		fmt.Println(fmt.Sprintf("Generate cert for self-sign HTTPS:"))
 | 
						|
		fmt.Println(fmt.Sprintf("	openssl genrsa -out server.key 2048"))
 | 
						|
		fmt.Println(fmt.Sprintf(`	openssl req -new -x509 -key server.key -out server.crt -days 365 -subj "/C=CN/ST=Beijing/L=Beijing/O=Me/OU=Me/CN=me.org"`))
 | 
						|
		fmt.Println(fmt.Sprintf("For example:"))
 | 
						|
		fmt.Println(fmt.Sprintf("	%v -s 9443 -r ./html -sdomain ossrs.net -skey ossrs.net.key -scert ossrs.net.pem", os.Args[0]))
 | 
						|
	}
 | 
						|
	flag.Parse()
 | 
						|
 | 
						|
	if useLetsEncrypt && len(httpsPorts) == 0 {
 | 
						|
		return oe.Errorf("for letsencrypt, https=%v must be 0(disabled) or 443(enabled)", httpsPorts)
 | 
						|
	}
 | 
						|
	if len(httpPorts) == 0 && len(httpsPorts) == 0 {
 | 
						|
		flag.Usage()
 | 
						|
		os.Exit(-1)
 | 
						|
	}
 | 
						|
 | 
						|
	// If trim last slash, we should enable no redirect index, to avoid infinitely redirect.
 | 
						|
	if trimLastSlash {
 | 
						|
		noRedirectIndex = true
 | 
						|
	}
 | 
						|
	fmt.Println(fmt.Sprintf("Config trimLastSlash=%v, trimSlashLimit=%v, noRedirectIndex=%v", trimLastSlash, trimSlashLimit, noRedirectIndex))
 | 
						|
 | 
						|
	var proxyUrls []*url.URL
 | 
						|
	proxies := make(map[string]*url.URL)
 | 
						|
	for _, oproxy := range []string(oproxies) {
 | 
						|
		if oproxy == "" {
 | 
						|
			return oe.Errorf("empty proxy in %v", oproxies)
 | 
						|
		}
 | 
						|
 | 
						|
		proxyUrl, err := url.Parse(oproxy)
 | 
						|
		if err != nil {
 | 
						|
			return oe.Wrapf(err, "parse proxy %v", oproxy)
 | 
						|
		}
 | 
						|
 | 
						|
		if _, ok := proxies[proxyUrl.Path]; ok {
 | 
						|
			return oe.Errorf("proxy %v duplicated", proxyUrl.Path)
 | 
						|
		}
 | 
						|
 | 
						|
		proxyUrls = append(proxyUrls, proxyUrl)
 | 
						|
		proxies[proxyUrl.Path] = proxyUrl
 | 
						|
		ol.Tf(ctx, "Proxy %v to %v", proxyUrl.Path, oproxy)
 | 
						|
	}
 | 
						|
 | 
						|
	var preHookUrls []*url.URL
 | 
						|
	preHooks := make(map[string]*url.URL)
 | 
						|
	for _, oprehook := range []string(oprehooks) {
 | 
						|
		if oprehook == "" {
 | 
						|
			return oe.Errorf("empty pre-hook in %v", oprehooks)
 | 
						|
		}
 | 
						|
 | 
						|
		preHookUrl, err := url.Parse(oprehook)
 | 
						|
		if err != nil {
 | 
						|
			return oe.Wrapf(err, "parse pre-hook %v", oprehook)
 | 
						|
		}
 | 
						|
 | 
						|
		if _, ok := preHooks[preHookUrl.Path]; ok {
 | 
						|
			return oe.Errorf("pre-hook %v duplicated", preHookUrl.Path)
 | 
						|
		}
 | 
						|
 | 
						|
		preHookUrls = append(preHookUrls, preHookUrl)
 | 
						|
		preHooks[preHookUrl.Path] = preHookUrl
 | 
						|
		ol.Tf(ctx, "pre-hook %v to %v", preHookUrl.Path, oprehook)
 | 
						|
	}
 | 
						|
 | 
						|
	if !path.IsAbs(cacheFile) && path.IsAbs(os.Args[0]) {
 | 
						|
		cacheFile = path.Join(path.Dir(os.Args[0]), cacheFile)
 | 
						|
	}
 | 
						|
	if !path.IsAbs(html) && path.IsAbs(os.Args[0]) {
 | 
						|
		html = path.Join(path.Dir(os.Args[0]), html)
 | 
						|
	}
 | 
						|
 | 
						|
	serveFileNoRedirect := func (w http.ResponseWriter, r *http.Request, name string) {
 | 
						|
		upath := path.Join(html, path.Clean(r.URL.Path))
 | 
						|
 | 
						|
		// Redirect without the last slash.
 | 
						|
		if trimLastSlash && r.URL.Path != "/" && strings.HasSuffix(r.URL.Path, "/") {
 | 
						|
			u := strings.TrimSuffix(r.URL.Path, "/")
 | 
						|
			if r.URL.RawQuery != "" {
 | 
						|
				u += "?" + r.URL.RawQuery
 | 
						|
			}
 | 
						|
			if strings.Count(u, "/") >= trimSlashLimit {
 | 
						|
				http.Redirect(w, r, u, http.StatusFound)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Append the index.html path if access a directory.
 | 
						|
		if noRedirectIndex && !strings.Contains(path.Base(upath), ".") {
 | 
						|
			if d, err := os.Stat(upath); err != nil {
 | 
						|
				http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
						|
				return
 | 
						|
			} else if d.IsDir() {
 | 
						|
				upath = path.Join(upath, "index.html")
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		http.ServeFile(w, r, upath)
 | 
						|
	}
 | 
						|
 | 
						|
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		oh.SetHeader(w)
 | 
						|
 | 
						|
		if o := r.Header.Get("Origin"); len(o) > 0 {
 | 
						|
			// SRS does not need cookie or credentials, so we disable CORS credentials, and use * for CORS origin,
 | 
						|
			// headers, expose headers and methods.
 | 
						|
			w.Header().Set("Access-Control-Allow-Origin", "*")
 | 
						|
			// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
 | 
						|
			w.Header().Set("Access-Control-Allow-Headers", "*")
 | 
						|
			// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
 | 
						|
			w.Header().Set("Access-Control-Allow-Methods", "*")
 | 
						|
			// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
 | 
						|
			w.Header().Set("Access-Control-Expose-Headers", "*")
 | 
						|
			// https://stackoverflow.com/a/24689738/17679565
 | 
						|
			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
 | 
						|
			w.Header().Set("Access-Control-Allow-Credentials", "false")
 | 
						|
		}
 | 
						|
 | 
						|
		// For matched OPTIONS, directly return without response.
 | 
						|
		if r.Method == "OPTIONS" {
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		if proxyUrls == nil {
 | 
						|
			if r.URL.Path == "/httpx/v1/versions" {
 | 
						|
				oh.WriteVersion(w, r, Version())
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			serveFileNoRedirect(w, r, path.Join(html, path.Clean(r.URL.Path)))
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// Find pre-hook to serve with proxy.
 | 
						|
		var preHook *url.URL
 | 
						|
		for _, preHookUrl := range preHookUrls {
 | 
						|
			if !shouldProxyURL(r.URL.Path, preHookUrl.Path) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			if p, ok := preHooks[preHookUrl.Path]; ok {
 | 
						|
				preHook = p
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Find proxy to serve it.
 | 
						|
		for _, proxyUrl := range proxyUrls {
 | 
						|
			if !shouldProxyURL(r.URL.Path, proxyUrl.Path) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			if proxy, ok := proxies[proxyUrl.Path]; ok {
 | 
						|
				p := NewComplexProxy(ctx, proxy, preHook, r)
 | 
						|
				p.ServeHTTP(w, r)
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		serveFileNoRedirect(w, r, path.Join(html, path.Clean(r.URL.Path)))
 | 
						|
	})
 | 
						|
 | 
						|
	var protos []string
 | 
						|
	if len(httpPorts) > 0 {
 | 
						|
		protos = append(protos, fmt.Sprintf("http(:%v)", strings.Join(httpPorts, ",")))
 | 
						|
	}
 | 
						|
	for _, httpsPort := range httpsPorts {
 | 
						|
		if httpsPort == "0" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		s := httpsDomains
 | 
						|
		if httpsDomains == "" {
 | 
						|
			s = "all domains"
 | 
						|
		}
 | 
						|
 | 
						|
		if useLetsEncrypt {
 | 
						|
			protos = append(protos, fmt.Sprintf("https(:%v, %v, %v)", httpsPort, s, cacheFile))
 | 
						|
		} else {
 | 
						|
			protos = append(protos, fmt.Sprintf("https(:%v)", httpsPort))
 | 
						|
		}
 | 
						|
 | 
						|
		if useLetsEncrypt {
 | 
						|
			protos = append(protos, "letsencrypt")
 | 
						|
		} else if ssKey != "" {
 | 
						|
			protos = append(protos, fmt.Sprintf("self-sign(%v, %v)", ssKey, ssCert))
 | 
						|
		} else if len(sdomains) == 0 {
 | 
						|
			return oe.New("no ssl config")
 | 
						|
		}
 | 
						|
 | 
						|
		for i := 0; i < len(sdomains); i++ {
 | 
						|
			sdomain, skey, scert := sdomains[i], skeys[i], scerts[i]
 | 
						|
			if f, err := os.Open(scert); err != nil {
 | 
						|
				return oe.Wrapf(err, "open cert %v for %v err %+v", scert, sdomain, err)
 | 
						|
			} else {
 | 
						|
				f.Close()
 | 
						|
			}
 | 
						|
 | 
						|
			if f, err := os.Open(skey); err != nil {
 | 
						|
				return oe.Wrapf(err, "open key %v for %v err %+v", skey, sdomain, err)
 | 
						|
			} else {
 | 
						|
				f.Close()
 | 
						|
			}
 | 
						|
			protos = append(protos, fmt.Sprintf("ssl(%v,%v,%v)", sdomain, skey, scert))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	ol.Tf(ctx, "%v html root at %v", strings.Join(protos, ", "), string(html))
 | 
						|
 | 
						|
	if len(httpsPorts) > 0 && !useLetsEncrypt && ssKey != "" {
 | 
						|
		if f, err := os.Open(ssCert); err != nil {
 | 
						|
			return oe.Wrapf(err, "open cert %v err %+v", ssCert, err)
 | 
						|
		} else {
 | 
						|
			f.Close()
 | 
						|
		}
 | 
						|
 | 
						|
		if f, err := os.Open(ssKey); err != nil {
 | 
						|
			return oe.Wrapf(err, "open key %v err %+v", ssKey, err)
 | 
						|
		} else {
 | 
						|
			f.Close()
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	wg := sync.WaitGroup{}
 | 
						|
	ctx, cancel := context.WithCancel(ctx)
 | 
						|
	defer cancel()
 | 
						|
 | 
						|
	var httpServers []*http.Server
 | 
						|
 | 
						|
	for _, v := range httpPorts {
 | 
						|
		httpPort, err := strconv.ParseInt(v, 10, 64)
 | 
						|
		if err != nil {
 | 
						|
			return oe.Wrapf(err, "parse %v", v)
 | 
						|
		}
 | 
						|
 | 
						|
		wg.Add(1)
 | 
						|
		go func(httpPort int) {
 | 
						|
			defer wg.Done()
 | 
						|
 | 
						|
			ctx = ol.WithContext(ctx)
 | 
						|
			if httpPort == 0 {
 | 
						|
				ol.W(ctx, "http server disabled")
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			defer cancel()
 | 
						|
			hs := &http.Server{Addr: fmt.Sprintf(":%v", httpPort), Handler: nil}
 | 
						|
			httpServers = append(httpServers, hs)
 | 
						|
			ol.Tf(ctx, "http serve at %v", httpPort)
 | 
						|
 | 
						|
			if err := hs.ListenAndServe(); err != nil {
 | 
						|
				ol.Ef(ctx, "http serve err %+v", err)
 | 
						|
				return
 | 
						|
			}
 | 
						|
			ol.T("http server ok")
 | 
						|
		}(int(httpPort))
 | 
						|
	}
 | 
						|
 | 
						|
	for _, v := range httpsPorts {
 | 
						|
		httpsPort, err := strconv.ParseInt(v, 10, 64)
 | 
						|
		if err != nil {
 | 
						|
			return oe.Wrapf(err, "parse %v", v)
 | 
						|
		}
 | 
						|
 | 
						|
		wg.Add(1)
 | 
						|
		go func() {
 | 
						|
			defer wg.Done()
 | 
						|
 | 
						|
			ctx = ol.WithContext(ctx)
 | 
						|
			if httpsPort == 0 {
 | 
						|
				ol.W(ctx, "https server disabled")
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			defer cancel()
 | 
						|
 | 
						|
			var err error
 | 
						|
			var m https.Manager
 | 
						|
			if useLetsEncrypt {
 | 
						|
				var domains []string
 | 
						|
				if httpsDomains != "" {
 | 
						|
					domains = strings.Split(httpsDomains, ",")
 | 
						|
				}
 | 
						|
 | 
						|
				if m, err = https.NewLetsencryptManager("", domains, cacheFile); err != nil {
 | 
						|
					ol.Ef(ctx, "create letsencrypt manager err %+v", err)
 | 
						|
					return
 | 
						|
				}
 | 
						|
			} else if ssKey != "" {
 | 
						|
				if m, err = https.NewSelfSignManager(ssCert, ssKey); err != nil {
 | 
						|
					ol.Ef(ctx, "create self-sign manager err %+v", err)
 | 
						|
					return
 | 
						|
				}
 | 
						|
			} else if len(sdomains) > 0 {
 | 
						|
				if m, err = NewCertsManager(sdomains, skeys, scerts); err != nil {
 | 
						|
					ol.Ef(ctx, "create ssl managers err %+v", err)
 | 
						|
					return
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			hss := &http.Server{
 | 
						|
				Addr: fmt.Sprintf(":%v", httpsPort),
 | 
						|
				TLSConfig: &tls.Config{
 | 
						|
					GetCertificate: m.GetCertificate,
 | 
						|
				},
 | 
						|
			}
 | 
						|
			httpServers = append(httpServers, hss)
 | 
						|
			ol.Tf(ctx, "https serve at %v", httpsPort)
 | 
						|
 | 
						|
			if err = hss.ListenAndServeTLS("", ""); err != nil {
 | 
						|
				ol.Ef(ctx, "https serve err %+v", err)
 | 
						|
				return
 | 
						|
			}
 | 
						|
			ol.T("https serve ok")
 | 
						|
		}()
 | 
						|
	}
 | 
						|
 | 
						|
	select {
 | 
						|
	case <-ctx.Done():
 | 
						|
		for _, server := range httpServers {
 | 
						|
			server.Close()
 | 
						|
		}
 | 
						|
	}
 | 
						|
	wg.Wait()
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func main() {
 | 
						|
	ctx := ol.WithContext(context.Background())
 | 
						|
	if err := run(ctx); err != nil {
 | 
						|
		ol.Ef(ctx, "run err %+v", err)
 | 
						|
		os.Exit(-1)
 | 
						|
	}
 | 
						|
}
 |