mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-10 16:08:16 -05:00
feat: add basic http server (#6)
This commit is contained in:
20
cmd/root.go
20
cmd/root.go
@@ -15,10 +15,13 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -55,6 +58,7 @@ func Execute() {
|
||||
// Command represents an invocation of the CLI.
|
||||
type Command struct {
|
||||
*cobra.Command
|
||||
cfg server.Config
|
||||
}
|
||||
|
||||
// NewCommand returns a Command object representing an invocation of the CLI.
|
||||
@@ -62,17 +66,27 @@ func NewCommand() *Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "toolbox",
|
||||
Version: versionString,
|
||||
RunE: root,
|
||||
}
|
||||
|
||||
c := &Command{
|
||||
Command: rootCmd,
|
||||
cfg: server.Config{},
|
||||
}
|
||||
// wrap RunE command so that we have access to original Command object
|
||||
rootCmd.RunE = func(*cobra.Command, []string) error { return run(c) }
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func root(cmd *cobra.Command, args []string) error {
|
||||
cmd.Printf("Toolbox running v%s!", versionString)
|
||||
func run(cmd *Command) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// run server
|
||||
s := server.NewServer(cmd.cfg)
|
||||
err := s.ListenAndServe(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while serving: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,5 +52,4 @@ func TestVersion(t *testing.T) {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("cli did not return correct version: want %q, got %q", want, got)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
5
go.mod
5
go.mod
@@ -2,7 +2,10 @@ module github.com/googleapis/genai-toolbox
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require github.com/spf13/cobra v1.8.1
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,4 +1,6 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
|
||||
29
internal/server/config.go
Normal file
29
internal/server/config.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package server
|
||||
|
||||
type Config struct {
|
||||
// Address is the address of the interface the server will listen on.
|
||||
Address string
|
||||
// Port is the port the server will listen on.
|
||||
Port string
|
||||
}
|
||||
|
||||
func NewConfig() Config {
|
||||
c := Config{
|
||||
Address: "127.0.0.1",
|
||||
Port: "5000",
|
||||
}
|
||||
return c
|
||||
}
|
||||
63
internal/server/server.go
Normal file
63
internal/server/server.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
// Server contains info for running an instance of Toolbox. Should be instantiated with NewServer().
|
||||
type Server struct {
|
||||
conf Config
|
||||
router chi.Router
|
||||
}
|
||||
|
||||
// NewServer returns a Server object based on provided Config.
|
||||
func NewServer(conf Config) *Server {
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("🧰 Hello world! 🧰"))
|
||||
})
|
||||
|
||||
s := &Server{
|
||||
conf: conf,
|
||||
router: r,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ListenAndServe starts an HTTP server for the given Server instance.
|
||||
func (s *Server) ListenAndServe(ctx context.Context) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
addr := net.JoinHostPort(s.conf.Address, s.conf.Port)
|
||||
lc := net.ListenConfig{KeepAlive: 30 * time.Second}
|
||||
l, err := lc.Listen(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open listener for %q: %w", addr, err)
|
||||
}
|
||||
|
||||
return http.Serve(l, s.router)
|
||||
}
|
||||
62
internal/server/server_test.go
Normal file
62
internal/server/server_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
)
|
||||
|
||||
// tryDial is a utility function that dials an address up to 'attempts' number of times.
|
||||
func tryDial(addr string, attempts int) bool {
|
||||
for i := 0; i < attempts; i++ {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
_ = conn.Close()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestListenAndServe(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
host, port := "127.0.0.1", "5000"
|
||||
cfg := server.NewConfig()
|
||||
s := server.NewServer(cfg)
|
||||
|
||||
// start server in background
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
err := s.ListenAndServe(ctx)
|
||||
defer close(errCh)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
}()
|
||||
|
||||
if !tryDial(net.JoinHostPort(host, port), 10) {
|
||||
t.Fatalf("Unable to dial server!")
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user