From ea691f1cf95b6bd223638afa986e136a273aab6b Mon Sep 17 00:00:00 2001 From: Asim Aslam Date: Wed, 15 Dec 2021 11:21:48 +0000 Subject: [PATCH] add proxy code --- app/proxy/proxy.go | 148 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 app/proxy/proxy.go diff --git a/app/proxy/proxy.go b/app/proxy/proxy.go new file mode 100644 index 0000000..0708043 --- /dev/null +++ b/app/proxy/proxy.go @@ -0,0 +1,148 @@ +package proxy + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "net/http/httputil" + "net/url" + "os" + "strings" + "sync" + "time" +) + +var ( + // API Key + APIKey = os.Getenv("MICRO_API_KEY") + + // API Url + APIHost = "https://api.m3o.com" + + // host to proxy for Apps + AppHost = "m3o.app" +) + +var ( + mtx sync.RWMutex + + // local cache + appMap = map[string]*backend{} +) + +type backend struct { + url *url.URL + created time.Time +} + +func Handler(w http.ResponseWriter, r *http.Request) { + // no subdomain + if r.Host == AppHost { + return + } + + // lookup the app map + mtx.RLock() + bk, ok := appMap[r.Host] + mtx.RUnlock() + + // check the url map + if ok && time.Since(bk.created) < time.Minute { + r.Host = bk.url.Host + r.Header.Set("Host", r.Host) + httputil.NewSingleHostReverseProxy(bk.url).ServeHTTP(w, r) + return + } + + subdomain := strings.TrimSuffix(r.Host, "."+AppHost) + + // only process one part for now + parts := strings.Split(subdomain, ".") + if len(parts) > 1 { + log.Print("[app/proxy] more parts than expected", parts) + return + } + + // currently service id is the subdomain + id := subdomain + + log.Printf("[app/proxy] resolving host %s to id %s\n", r.Host, id) + + apiURL := APIHost + "/app/resolve" + + // use /v1/ + if len(APIKey) > 0 { + apiURL = APIHost + "/v1/app/resolve" + } + + // make new request + log.Printf("[app/proxy] Calling: %v", apiURL+"?id="+id) + req, err := http.NewRequest("GET", apiURL+"?id="+id, nil) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + if req.Header == nil { + req.Header = make(http.Header) + } + + // set the api key after we're given the header + if len(APIKey) > 0 { + req.Header.Set("Authorization", "Bearer "+APIKey) + } + + // call the backend for the url + rsp, err := http.DefaultClient.Do(req) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + defer rsp.Body.Close() + + b, err := ioutil.ReadAll(rsp.Body) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + + if rsp.StatusCode != 200 { + log.Printf("[app/proxy] Error calling api: status: %v %v", rsp.StatusCode, string(b)) + http.Error(w, "unexpected error", 500) + return + } + + result := map[string]interface{}{} + + if err := json.Unmarshal(b, &result); err != nil { + log.Print("[app/proxy] failed to unmarshal response") + http.Error(w, err.Error(), 500) + return + } + + // get the destination url + u, _ := result["url"].(string) + if len(u) == 0 { + log.Print("[app/proxy] no response url") + return + } + + uri, err := url.Parse(u) + if err != nil { + log.Print("[app/proxy] failed to parse url", err.Error()) + return + } + + mtx.Lock() + appMap[r.Host] = &backend{ + url: uri, + created: time.Now(), + } + mtx.Unlock() + + r.Host = uri.Host + r.Header.Set("Host", r.Host) + + httputil.NewSingleHostReverseProxy(uri).ServeHTTP(w, r) +}