@@ -253,24 +253,16 @@ func (collec *ServiceCollection) postService(w http.ResponseWriter, r *http.Requ
253253
254254 for pred , output := range service .Configuration .Spec .OutputMapping {
255255
256- if output .Distribution == nil || output .Distribution .Access == nil {
257- continue // abstract output → no endpoint
258- }
259-
260256 externalPath := output .Distribution .Access .ExternalPath
261-
262- // Validate (now required)
263257 if externalPath == "" {
264258 errMsg := fmt .Sprintf ("externalPath is required for output %s" , pred )
265259 logrus .Error (errMsg )
266260 http .Error (w , errMsg , http .StatusInternalServerError )
267261 return
268262 }
269263
270- // Normalize and join
271264 path := util .JoinPaths (aggPath , externalPath )
272265
273- // Prevent duplicates
274266 if seen [path ] {
275267 errMsg := fmt .Sprintf ("duplicate externalPath resolved to %s" , path )
276268 logrus .Error (errMsg )
@@ -279,15 +271,37 @@ func (collec *ServiceCollection) postService(w http.ResponseWriter, r *http.Requ
279271 }
280272 seen [path ] = true
281273
282- // Register handler
283- err = collec .HandleFunc (path , collec .HandleServiceOutput , []model.Scope {model .Read , model .Write })
274+ // Build forward URL at registration time, nil if abstract
275+ var forwardURL string
276+ if output .Distribution != nil && output .Distribution .Access != nil {
277+ access := output .Distribution .Access
278+ internalPath := access .InternalPath
279+ if internalPath == "" {
280+ internalPath = "/"
281+ }
282+ if ! strings .HasPrefix (internalPath , "/" ) {
283+ internalPath = "/" + internalPath
284+ }
285+ forwardURL = fmt .Sprintf (
286+ "%s://%s.%s.svc.cluster.local:%d%s" ,
287+ access .Protocol ,
288+ service .NamespaceID ,
289+ model .Namespace ,
290+ access .ServicePort ,
291+ internalPath ,
292+ )
293+ }
294+
295+ err = collec .HandleFunc (path , func (w http.ResponseWriter , r * http.Request ) {
296+ collec .HandleServiceOutput (w , r , forwardURL )
297+ }, []model.Scope {model .Read , model .Write })
284298 if err != nil {
285299 logrus .WithError (err ).Errorf ("Error registering handler for output %s" , pred )
286300 http .Error (w , "Failed to create service from request" , http .StatusInternalServerError )
287301 return
288302 }
289303
290- logrus .Infof ("Registered endpoint: %s → %s" , path , pred )
304+ logrus .Infof ("Registered endpoint: %s → %s" , pred , path )
291305 }
292306
293307 // Return service information
@@ -328,99 +342,40 @@ func (collec *ServiceCollection) deleteService(w http.ResponseWriter, _ *http.Re
328342}
329343
330344// Handles all incoming service requests <service path>/<output>
331- func (collec * ServiceCollection ) HandleServiceOutput (w http.ResponseWriter , r * http.Request ) {
332- // Trim leading/trailing slashes and split path
333- parts := strings .Split (strings .Trim (r .URL .Path , "/" ), "/" )
334- if len (parts ) < 2 {
335- http .Error (w , "Invalid path, must be /<service-path>/<output>" , http .StatusBadRequest )
336- return
337- }
338-
339- // Last segment is the output
340- pred := parts [len (parts )- 1 ]
341-
342- // Everything else is the service path
343- servicePath := parts [:len (parts )- 1 ]
344- serviceID := strings .Join (servicePath , "-" )
345- service , ok := collec .services [serviceID ]
346- if ! ok {
347- http .Error (w , "Service not found" , http .StatusNotFound )
348- return
349- }
350-
351- // Lookup output mapping from ServiceConfiguration
352- output , exists := service .Configuration .Spec .OutputMapping [pred ]
353- if ! exists {
354- logrus .Errorf ("No output mapping found for %s" , pred )
355- http .Error (w , fmt .Sprintf ("Requested service has no output %s" , pred ), http .StatusNotFound )
356- return
357- }
358-
359- if output .Distribution == nil || output .Distribution .Access == nil {
360- logrus .Errorf ("Output %s has no distribution access defined" , pred )
361- http .Error (w , fmt .Sprintf ("Output %s is not accessible" , pred ), http .StatusNotFound )
345+ func (collec * ServiceCollection ) HandleServiceOutput (w http.ResponseWriter , r * http.Request , forwardURL string ) {
346+ if forwardURL == "" {
347+ http .Error (w , "Output has no distribution" , http .StatusNotFound )
362348 return
363349 }
364350
365- access := output .Distribution .Access
366-
367- // Extract servicePort
368- port := access .ServicePort
369-
370- // Normalize internalPath (default "/")
371- internalPath := access .InternalPath
372- if internalPath == "" {
373- internalPath = "/"
374- }
375-
376- // ensure it starts with "/"
377- if ! strings .HasPrefix (internalPath , "/" ) {
378- internalPath = "/" + internalPath
379- }
380-
381- // Build forwarding URL
382- forwardURL := fmt .Sprintf (
383- "%s://%s.%s.svc.cluster.local:%d%s" ,
384- access .Protocol ,
385- service .NamespaceID ,
386- model .Namespace ,
387- port ,
388- internalPath ,
389- )
390-
391351 req , err := http .NewRequest (r .Method , forwardURL , r .Body )
392352 if err != nil {
393353 logrus .WithError (err ).Error ("Failed to create forward request" )
394354 http .Error (w , "Failed to reach requested service" , http .StatusInternalServerError )
395355 return
396356 }
397357
398- // Copy headers from original request
399358 for k , vv := range r .Header {
400359 for _ , v := range vv {
401360 req .Header .Add (k , v )
402361 }
403362 }
404363
405- // Send request using proxy client
406364 resp , err := model .HttpClient .Do (req )
407365 if err != nil {
408366 http .Error (w , fmt .Sprintf ("Failed to forward request: %v" , err ), http .StatusBadGateway )
409367 return
410368 }
411369 defer resp .Body .Close ()
412370
413- // Copy response headers
414371 for k , vv := range resp .Header {
415372 for _ , v := range vv {
416373 w .Header ().Add (k , v )
417374 }
418375 }
419376
420- // Write status code
421377 w .WriteHeader (resp .StatusCode )
422378
423- // Copy response body
424379 if _ , err := io .Copy (w , resp .Body ); err != nil {
425380 logrus .WithError (err ).Error ("Failed to copy response body" )
426381 }
0 commit comments