Skip to content

Commit cffe3b0

Browse files
Fix google#257: Return http.Handler instead of SetupRouter in server/restapi/web (google#259)
* Add NewHandler() function to return http.Handler instead of requiring mux.Router - Created NewHandler() that returns standard http.Handler for better decoupling - Marked SetupRouter() as deprecated for backward compatibility - Allows users to use ADK REST API with any HTTP server/router, not just gorilla/mux * Update api launcher to use NewHandler() instead of SetupRouter() - Refactored SetupSubrouters to use web.NewHandler() - Wraps handler with CORS middleware before registration - Uses http.StripPrefix for proper path handling - No breaking changes to web launcher interface * Add example demonstrating NewHandler() usage with standard net/http - Shows how to use web.NewHandler() with net/http.ServeMux - Demonstrates framework-agnostic approach - Includes health check endpoint to show composability - Example works with any HTTP server/router, not tied to gorilla/mux * Resolve PR feedback: remove SetupRouter and update example to use launcher.Config * Clean up comments in NewHandler and setupRouter Removed comments about the preferred integration method and the public SetupRouter helper. * Fix linter issues: handle w.Write error and format with goimports * Fix missing newline at end of api.go Add a newline at the end of the file. * Fix goimports formatting: remove trailing blank line and handle w.Write error --------- Co-authored-by: Dmitry Pasiukevich <dpasiukevich@google.com>
1 parent 4336819 commit cffe3b0

File tree

3 files changed

+116
-11
lines changed

3 files changed

+116
-11
lines changed

‎cmd/launcher/web/api/api.go‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,16 @@ func (a *apiLauncher) UserMessage(webURL string, printer func(v ...any)) {
6767

6868
// SetupSubrouters adds the API router to the parent router.
6969
func (a *apiLauncher) SetupSubrouters(router *mux.Router, config *launcher.Config) error {
70-
rAPI := router.Methods("GET", "POST", "DELETE", "OPTIONS").PathPrefix("/api/").Subrouter()
71-
restapiweb.SetupRouter(rAPI, config)
72-
rAPI.Use(corsWithArgs(a.config.frontendAddress))
70+
// Create the ADK REST API handler
71+
apiHandler := restapiweb.NewHandler(config)
72+
73+
// Wrap it with CORS middleware
74+
corsHandler := corsWithArgs(a.config.frontendAddress)(apiHandler)
75+
76+
// Register it at the /api/ path
77+
router.Methods("GET", "POST", "DELETE", "OPTIONS").PathPrefix("/api/").Handler(
78+
http.StripPrefix("/api", corsHandler),
79+
)
7380
return nil
7481
}
7582

‎examples/restapi/main.go‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// This example demonstrates how to use the ADK REST API handler directly
16+
// with the standard net/http package, without relying on any specific router.
17+
package main
18+
19+
import (
20+
"context"
21+
"log"
22+
"net/http"
23+
"os"
24+
25+
"google.golang.org/adk/agent/llmagent"
26+
"google.golang.org/adk/cmd/launcher"
27+
"google.golang.org/adk/model/gemini"
28+
"google.golang.org/adk/server/restapi/services"
29+
"google.golang.org/adk/server/restapi/web"
30+
"google.golang.org/adk/session"
31+
"google.golang.org/adk/tool"
32+
"google.golang.org/adk/tool/geminitool"
33+
"google.golang.org/genai"
34+
)
35+
36+
func main() {
37+
ctx := context.Background()
38+
39+
// Create a Gemini model
40+
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
41+
APIKey: os.Getenv("GOOGLE_API_KEY"),
42+
})
43+
if err != nil {
44+
log.Fatalf("Failed to create model: %v", err)
45+
}
46+
47+
// Create an agent
48+
agent, err := llmagent.New(llmagent.Config{
49+
Name: "weather_time_agent",
50+
Model: model,
51+
Description: "Agent to answer questions about the time and weather in a city.",
52+
Instruction: "I can answer your questions about the time and weather in a city.",
53+
Tools: []tool.Tool{
54+
geminitool.GoogleSearch{},
55+
},
56+
})
57+
if err != nil {
58+
log.Fatalf("Failed to create agent: %v", err)
59+
}
60+
61+
// Configure the ADK REST API
62+
config := &launcher.Config{
63+
AgentLoader: services.NewSingleAgentLoader(agent),
64+
SessionService: session.InMemoryService(),
65+
}
66+
67+
// Create the REST API handler - this returns a standard http.Handler
68+
apiHandler := web.NewHandler(config)
69+
70+
// Create a standard net/http ServeMux
71+
mux := http.NewServeMux()
72+
73+
// Register the API handler at the /api/ path
74+
// You can use any HTTP server or router here - not tied to gorilla/mux
75+
mux.Handle("/api/", http.StripPrefix("/api", apiHandler))
76+
77+
// Add a simple health check endpoint
78+
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
79+
w.WriteHeader(http.StatusOK)
80+
if _, err := w.Write([]byte("OK")); err != nil {
81+
log.Printf("Failed to write response: %v", err)
82+
}
83+
})
84+
85+
// Start the server
86+
log.Println("Starting server on :8080")
87+
log.Println("API available at http://localhost:8080/api/")
88+
log.Println("Health check at http://localhost:8080/health")
89+
90+
if err := http.ListenAndServe(":8080", mux); err != nil {
91+
log.Fatalf("Server failed: %v", err)
92+
}
93+
}

‎server/restapi/web/server.go‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package web
1717

1818
import (
19+
"net/http"
20+
1921
"github.com/gorilla/mux"
2022
"google.golang.org/adk/cmd/launcher"
2123
"google.golang.org/adk/internal/telemetry"
@@ -26,18 +28,21 @@ import (
2628
sdktrace "go.opentelemetry.io/otel/sdk/trace"
2729
)
2830

29-
// SetupRouter initiates mux.Router with ADK REST API routers
30-
func SetupRouter(router *mux.Router, routerConfig *launcher.Config) *mux.Router {
31+
// NewHandler creates and returns an http.Handler for the ADK REST API.
32+
func NewHandler(config *launcher.Config) http.Handler {
3133
adkExporter := services.NewAPIServerSpanExporter()
3234
telemetry.AddSpanProcessor(sdktrace.NewSimpleSpanProcessor(adkExporter))
33-
return setupRouter(router,
34-
routers.NewSessionsAPIRouter(handlers.NewSessionsAPIController(routerConfig.SessionService)),
35-
routers.NewRuntimeAPIRouter(handlers.NewRuntimeAPIRouter(routerConfig.SessionService, routerConfig.AgentLoader, routerConfig.ArtifactService)),
36-
routers.NewAppsAPIRouter(handlers.NewAppsAPIController(routerConfig.AgentLoader)),
37-
routers.NewDebugAPIRouter(handlers.NewDebugAPIController(routerConfig.SessionService, routerConfig.AgentLoader, adkExporter)),
38-
routers.NewArtifactsAPIRouter(handlers.NewArtifactsAPIController(routerConfig.ArtifactService)),
35+
36+
router := mux.NewRouter().StrictSlash(true)
37+
setupRouter(router,
38+
routers.NewSessionsAPIRouter(handlers.NewSessionsAPIController(config.SessionService)),
39+
routers.NewRuntimeAPIRouter(handlers.NewRuntimeAPIRouter(config.SessionService, config.AgentLoader, config.ArtifactService)),
40+
routers.NewAppsAPIRouter(handlers.NewAppsAPIController(config.AgentLoader)),
41+
routers.NewDebugAPIRouter(handlers.NewDebugAPIController(config.SessionService, config.AgentLoader, adkExporter)),
42+
routers.NewArtifactsAPIRouter(handlers.NewArtifactsAPIController(config.ArtifactService)),
3943
&routers.EvalAPIRouter{},
4044
)
45+
return router
4146
}
4247

4348
func setupRouter(router *mux.Router, subrouters ...routers.Router) *mux.Router {

0 commit comments

Comments
 (0)