// Package gateway provides an HTTP gateway for micro run package gateway import ( "context" "encoding/json" "fmt" "io" "net/http" "strings" "sync" "time" "go-micro.dev/v5/client" "go-micro.dev/v5/codec/bytes" "go-micro.dev/v5/health" "go-micro.dev/v5/registry" ) // Gateway provides HTTP access to micro services type Gateway struct { addr string server *http.Server services []ServiceInfo mu sync.RWMutex } // ServiceInfo holds information about a running service type ServiceInfo struct { Name string `json:"name"` Address string `json:"address"` Port int `json:"port,omitempty"` } // New creates a new gateway func New(addr string) *Gateway { return &Gateway{ addr: addr, } } // SetServices updates the list of known services func (g *Gateway) SetServices(services []ServiceInfo) { g.mu.Lock() g.services = services g.mu.Unlock() } // Start starts the gateway HTTP server func (g *Gateway) Start() error { mux := http.NewServeMux() // Health endpoint - aggregates all service health mux.HandleFunc("/health", g.healthHandler) mux.HandleFunc("/health/live", g.liveHandler) mux.HandleFunc("/health/ready", g.readyHandler) // API endpoint - HTTP to RPC proxy mux.HandleFunc("/api/", g.apiHandler) // Services list mux.HandleFunc("/services", g.servicesHandler) // Home page mux.HandleFunc("/", g.homeHandler) g.server = &http.Server{ Addr: g.addr, Handler: mux, } go func() { if err := g.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { fmt.Printf("Gateway error: %v\n", err) } }() return nil } // Stop stops the gateway func (g *Gateway) Stop() { if g.server != nil { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() g.server.Shutdown(ctx) } } // Addr returns the gateway address func (g *Gateway) Addr() string { return g.addr } func (g *Gateway) homeHandler(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } g.mu.RLock() services := g.services g.mu.RUnlock() // Get services from registry regServices, _ := registry.ListServices() w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprintf(w, ` Micro

Micro

Services are running

Services (%d)

`, len(regServices)) if len(regServices) > 0 { for _, svc := range regServices { fmt.Fprintf(w, `
%s
`, svc.Name) // Get endpoints for this service if details, err := registry.GetService(svc.Name); err == nil && len(details) > 0 { if len(details[0].Endpoints) > 0 { fmt.Fprintf(w, `
`) for _, ep := range details[0].Endpoints { fmt.Fprintf(w, ` POST /api/%s/%s\n`, svc.Name, ep.Name, svc.Name, ep.Name) } fmt.Fprintf(w, `
`) } } } } else if len(services) > 0 { for _, svc := range services { fmt.Fprintf(w, `
%s %s
`, svc.Name, svc.Address) } } else { fmt.Fprintf(w, `

No services registered yet...

`) } fmt.Fprintf(w, `

Quick Links

Try it

curl -X POST http://localhost%s/api/{service}/{Endpoint} -d '{}'
`, g.addr) } func (g *Gateway) servicesHandler(w http.ResponseWriter, r *http.Request) { services, err := registry.ListServices() if err != nil { http.Error(w, err.Error(), 500) return } var result []map[string]interface{} for _, svc := range services { details, _ := registry.GetService(svc.Name) var endpoints []string if len(details) > 0 { for _, ep := range details[0].Endpoints { endpoints = append(endpoints, ep.Name) } } result = append(result, map[string]interface{}{ "name": svc.Name, "endpoints": endpoints, }) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } func (g *Gateway) healthHandler(w http.ResponseWriter, r *http.Request) { resp := health.Run(r.Context()) w.Header().Set("Content-Type", "application/json") if resp.Status == health.StatusUp { w.WriteHeader(http.StatusOK) } else { w.WriteHeader(http.StatusServiceUnavailable) } json.NewEncoder(w).Encode(resp) } func (g *Gateway) liveHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"status":"up"}`)) } func (g *Gateway) readyHandler(w http.ResponseWriter, r *http.Request) { g.healthHandler(w, r) } func (g *Gateway) apiHandler(w http.ResponseWriter, r *http.Request) { // Parse path: /api/{service}/{endpoint} path := strings.TrimPrefix(r.URL.Path, "/api/") parts := strings.SplitN(path, "/", 2) if len(parts) < 2 { http.Error(w, `{"error": "usage: /api/{service}/{endpoint}"}`, http.StatusBadRequest) return } service := parts[0] endpoint := parts[1] // Read request body body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, fmt.Sprintf(`{"error": "%s"}`, err.Error()), http.StatusBadRequest) return } if len(body) == 0 { body = []byte("{}") } // Create RPC request req := client.NewRequest(service, endpoint, &bytes.Frame{Data: body}) var rsp bytes.Frame if err := client.Call(r.Context(), req, &rsp); err != nil { http.Error(w, fmt.Sprintf(`{"error": "%s"}`, err.Error()), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(rsp.Data) }