package server import ( "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path" "reflect" "runtime/debug" "strings" "repositories.action2quare.com/ayo/gocommon/flagx" "repositories.action2quare.com/ayo/gocommon/logger" ) const ( defaultMaxMemory = 32 << 10 // 32 KB ) type HoustonServerWithHandler interface { HoustonServer RegisterHandlers(serveMux *http.ServeMux, prefix string) error } type houstonHandler struct { HoustonServer methods map[string]reflect.Method deployPath string downloadPath string } func NewHoustonHandler() HoustonServerWithHandler { var tmp *houstonHandler methods := make(map[string]reflect.Method) tp := reflect.TypeOf(tmp) for i := 0; i < tp.NumMethod(); i++ { method := tp.Method(i) methods[strings.ToLower(method.Name)] = method } return &houstonHandler{ HoustonServer: NewServer(), methods: methods, } } func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error { config := loadServerConfig() storagePath := config.StorageRoot h.deployPath = path.Join(storagePath, sub_folder_name_deploys) h.downloadPath = path.Join(storagePath, sub_folder_name_downloads) if err := os.MkdirAll(h.deployPath, 0775); err != nil { return err } if err := os.MkdirAll(h.downloadPath, 0775); err != nil { return err } logger.Printf("houstonHandler registed. deployPath : %s, downloadPath : %s", h.deployPath, h.downloadPath) if len(prefix) > 0 { prefix = "/" + prefix } serveMux.Handle(prefix, h) fsx := http.FileServer(http.Dir(h.deployPath)) serveMux.Handle(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), http.StripPrefix(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), fsx)) ufsx := http.FileServer(http.Dir(h.downloadPath)) serveMux.Handle(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_downloads), http.StripPrefix(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_downloads), ufsx)) serveMux.HandleFunc(fmt.Sprintf("%s/upload", prefix), func(w http.ResponseWriter, r *http.Request) { defer func() { s := recover() if s != nil { logger.Println(s) debug.PrintStack() } io.Copy(io.Discard, r.Body) r.Body.Close() }() name := r.Header.Get("Houston-Service-Name") version := r.Header.Get("Houston-Service-Version") filename := r.Header.Get("Houston-Service-Filename") dir := path.Join(h.downloadPath, name, version) if err := os.MkdirAll(dir, 0775); err == nil { file, _ := os.Create(path.Join(dir, filename)) if file != nil { defer file.Close() if _, err = io.Copy(file, r.Body); err != nil { w.WriteHeader(http.StatusInternalServerError) } } else { w.WriteHeader(http.StatusInternalServerError) } } else { w.WriteHeader(http.StatusInternalServerError) } }) return nil } var noauth = flagx.Bool("noauth", false, "") type respWriteTracker struct { inner http.ResponseWriter reqUrlValues url.Values body []byte } func (rt *respWriteTracker) Header() http.Header { return rt.inner.Header() } func (rt *respWriteTracker) Write(bt []byte) (int, error) { rt.body = append(rt.body, bt...) return rt.inner.Write(bt) } func (rt *respWriteTracker) WriteHeader(statusCode int) { if statusCode != http.StatusOK { logger.Println() } } var logApiCallFlag = flagx.Bool("logapicall", false, "") func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer func() { s := recover() if s != nil { logger.Println(s) debug.PrintStack() } }() defer func() { io.Copy(io.Discard, r.Body) r.Body.Close() }() var userinfo map[string]any if !*noauth { authheader := r.Header.Get("Authorization") if len(authheader) == 0 { logger.Println("Authorization header is not valid :", authheader) w.WriteHeader(http.StatusBadRequest) return } req, _ := http.NewRequest("GET", "https://graph.microsoft.com/oidc/userinfo", nil) req.Header.Add("Authorization", authheader) client := &http.Client{} resp, err := client.Do(req) if err != nil { logger.Println("graph microsoft api call failed :", err) w.WriteHeader(http.StatusBadRequest) return } defer resp.Body.Close() raw, _ := io.ReadAll(resp.Body) if err = json.Unmarshal(raw, &userinfo); err != nil { return } if _, expired := userinfo["error"]; expired { w.WriteHeader(http.StatusUnauthorized) return } } var operation string if r.Method == "POST" { operation = r.FormValue("operation") } else { operation = r.URL.Query().Get("operation") } if len(operation) == 0 { w.WriteHeader(http.StatusBadRequest) return } method, ok := h.methods[strings.ToLower(operation)] if !ok { // 없는 operation logger.Println("fail to call api. operation is not valid :", operation) w.WriteHeader(http.StatusBadRequest) return } if *logApiCallFlag { var urlvalues url.Values if r.Method == "POST" { urlvalues = r.Form } else { urlvalues = r.URL.Query() } tracker := &respWriteTracker{ inner: w, reqUrlValues: urlvalues, } defer func() { logger.Println("api called :", userinfo["email"], urlvalues) logger.Println("-->", string(tracker.body)) }() w = http.ResponseWriter(tracker) } args := []reflect.Value{ reflect.ValueOf(h), reflect.ValueOf(w), reflect.ValueOf(r), } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") method.Func.Call(args) }