Skip to content

Commit 934b46d

Browse files
committed
Updates to server
1 parent 7aa1784 commit 934b46d

File tree

14 files changed

+438
-86
lines changed

14 files changed

+438
-86
lines changed

pkg/httprequest/indent.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package httprequest
2+
3+
import "net/http"
4+
5+
////////////////////////////////////////////////////////////////////////////////
6+
// GLOBALS
7+
8+
const (
9+
defaultIndent = 2
10+
)
11+
12+
////////////////////////////////////////////////////////////////////////////////
13+
// PUBLIC METHODS
14+
15+
func Indent(r *http.Request) int {
16+
return defaultIndent
17+
}

pkg/httprouter/httprouter.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,16 @@ func (r *router) HandleFunc(ctx context.Context, prefix string, fn http.HandlerF
6565
}
6666

6767
// Apply middleware, set context
68+
provider.Log(ctx).Print(ctx, "Register route: ", types.JoinPath(r.prefix, prefix))
6869
r.ServeMux.HandleFunc(types.JoinPath(r.prefix, prefix), func(w http.ResponseWriter, r *http.Request) {
69-
fn(w, r.WithContext(
70+
fn(w, r)
71+
/* TODO fn(w, r.WithContext(
7072
provider.WithLog(
7173
provider.WithName(
7274
r.Context(), provider.Name(ctx),
7375
), provider.Log(ctx),
7476
),
75-
))
77+
))*/
7678
})
7779
}
7880

pkg/provider/context.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
6+
// Packages
7+
server "github.com/mutablelogic/go-server"
8+
)
9+
10+
////////////////////////////////////////////////////////////////////////////////
11+
// TYPES
12+
13+
type ctx int
14+
15+
const (
16+
ctxProvider ctx = iota
17+
ctxPath
18+
)
19+
20+
////////////////////////////////////////////////////////////////////////////////
21+
// PUBLIC METHODS
22+
23+
func Provider(ctx context.Context) server.Provider {
24+
if value := ctx.Value(ctxProvider); value == nil {
25+
return nil
26+
} else {
27+
return value.(server.Provider)
28+
}
29+
}
30+
31+
func Path(ctx context.Context) []string {
32+
if value, ok := ctx.Value(ctxPath).([]string); !ok {
33+
return nil
34+
} else {
35+
return value
36+
}
37+
}
38+
39+
func Log(ctx context.Context) server.Logger {
40+
if value := ctx.Value(ctxProvider); value == nil {
41+
return nil
42+
} else {
43+
return value.(server.Logger)
44+
}
45+
}
46+
47+
////////////////////////////////////////////////////////////////////////////////
48+
// PRIVATE METHODS
49+
50+
// Set the provider in the context
51+
func withProvider(parent context.Context, provider server.Provider) context.Context {
52+
return context.WithValue(parent, ctxProvider, provider)
53+
}
54+
55+
// Append a path to the context
56+
func withPath(parent context.Context, path ...string) context.Context {
57+
return context.WithValue(parent, ctxPath, append(Path(parent), path...))
58+
}

pkg/provider/logger.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"log"
6+
)
7+
8+
/////////////////////////////////////////////////////////////////////
9+
// PUBLIC METHODS
10+
11+
// Emit an informational message
12+
func (provider *provider) Print(ctx context.Context, a ...any) {
13+
log.Print(a...)
14+
}

pkg/provider/provider.go

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"sync"
7+
8+
// Packages
9+
server "github.com/mutablelogic/go-server"
10+
httpresponse "github.com/mutablelogic/go-server/pkg/httpresponse"
11+
types "github.com/mutablelogic/go-server/pkg/types"
12+
)
13+
14+
////////////////////////////////////////////////////////////////////////////////
15+
// TYPES
16+
17+
type provider struct {
18+
// Map labels to plugins
19+
plugin map[string]server.Plugin
20+
21+
// Map labels to tasks
22+
task map[string]*state
23+
24+
// Order that the tasks were created
25+
order []string
26+
27+
// Function to resolve plugin members
28+
resolver ResolverFunc
29+
}
30+
31+
type state struct {
32+
server.Task
33+
context.Context
34+
context.CancelFunc
35+
sync.WaitGroup
36+
}
37+
38+
// ResolverFunc is a function that resolves a plugin from label and plugin
39+
type ResolverFunc func(context.Context, string, server.Plugin) (server.Plugin, error)
40+
41+
////////////////////////////////////////////////////////////////////////////////
42+
// LIFECYCLE
43+
44+
func New(resolver ResolverFunc, plugins ...server.Plugin) (*provider, error) {
45+
self := new(provider)
46+
self.plugin = make(map[string]server.Plugin, len(plugins))
47+
self.task = make(map[string]*state, len(plugins))
48+
self.order = make([]string, 0, len(plugins))
49+
self.resolver = resolver
50+
51+
// Add the plugins
52+
for _, plugin := range plugins {
53+
// Check plugin
54+
if plugin == nil {
55+
return nil, httpresponse.ErrInternalError.With("Plugin is nil")
56+
} else if !types.IsIdentifier(plugin.Name()) {
57+
return nil, httpresponse.ErrInternalError.Withf("Plugin name %q is not valid", plugin.Name())
58+
}
59+
60+
// TODO: Don't use names, use labels!
61+
label := plugin.Name()
62+
if _, exists := self.plugin[label]; exists {
63+
return nil, httpresponse.ErrInternalError.Withf("Plugin %q already exists", plugin.Name())
64+
} else {
65+
self.plugin[label] = plugin
66+
}
67+
}
68+
69+
// Return success
70+
return self, nil
71+
}
72+
73+
////////////////////////////////////////////////////////////////////////////////
74+
// STRINGIFY
75+
76+
func (provider *provider) String() string {
77+
type jtask struct {
78+
Name string `json:"name"`
79+
Description string `json:"description,omitempty"`
80+
Label string `json:"label,omitempty"`
81+
Plugin server.Plugin `json:"plugin,omitempty"`
82+
Task server.Task `json:"task,omitempty"`
83+
}
84+
result := make([]jtask, 0, len(provider.task))
85+
for _, label := range provider.order {
86+
plugin := provider.plugin[label]
87+
result = append(result, jtask{
88+
Name: plugin.Name(),
89+
Description: plugin.Description(),
90+
Label: label,
91+
Plugin: plugin,
92+
Task: provider.task[label].Task,
93+
})
94+
}
95+
data, err := json.MarshalIndent(result, "", " ")
96+
if err != nil {
97+
return err.Error()
98+
}
99+
return string(data)
100+
}
101+
102+
////////////////////////////////////////////////////////////////////////////////
103+
// PUBLIC METHODS
104+
105+
// Return a task from a label
106+
func (provider *provider) Task(ctx context.Context, label string) server.Task {
107+
provider.Print(ctx, "Called Task for ", label)
108+
109+
// If the task is already created, then return it
110+
if task, exists := provider.task[label]; exists {
111+
return task.Task
112+
}
113+
114+
// If the plugin doesn't exist, return nil
115+
plugin, exists := provider.plugin[label]
116+
if !exists {
117+
return nil
118+
}
119+
120+
// Resolve the plugin
121+
if provider.resolver != nil {
122+
var err error
123+
plugin, err = provider.resolver(withProvider(ctx, provider), label, plugin)
124+
if err != nil {
125+
provider.Print(ctx, "Error: ", label, ": ", err)
126+
return nil
127+
}
128+
}
129+
130+
// Create the task
131+
task, err := plugin.New(withPath(ctx, label))
132+
if err != nil {
133+
provider.Print(ctx, "Error: ", label, ": ", err)
134+
return nil
135+
} else if task == nil {
136+
provider.Print(ctx, "Error: ", label, ": ", httpresponse.ErrInternalError.With("Task is nil"))
137+
return nil
138+
}
139+
140+
// Set the task and order
141+
provider.task[label] = &state{Task: task}
142+
provider.order = append(provider.order, label)
143+
144+
// Return the task
145+
return task
146+
}
147+
148+
////////////////////////////////////////////////////////////////////////////////
149+
// PRIVATE METHODS
150+
151+
// Make all tasks
152+
func (provider *provider) constructor(ctx context.Context) error {
153+
for label := range provider.plugin {
154+
if task := provider.Task(ctx, label); task == nil {
155+
return httpresponse.ErrConflict.Withf("Failed to create task %q", label)
156+
}
157+
}
158+
return nil
159+
}

pkg/provider/provider_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package provider_test
2+
3+
import (
4+
"testing"
5+
6+
// Packages
7+
provider "github.com/mutablelogic/go-server/pkg/provider"
8+
httpserver "github.com/mutablelogic/go-server/plugin/httpserver"
9+
assert "github.com/stretchr/testify/assert"
10+
)
11+
12+
func Test_Provider_001(t *testing.T) {
13+
assert := assert.New(t)
14+
15+
t.Run("1", func(t *testing.T) {
16+
provider, err := provider.New()
17+
if assert.NoError(err) {
18+
assert.NotNil(provider)
19+
}
20+
})
21+
22+
t.Run("2", func(t *testing.T) {
23+
provider, err := provider.New(httpserver.Config{})
24+
if assert.NoError(err) {
25+
assert.NotNil(provider)
26+
}
27+
})
28+
}

pkg/provider/run.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"sort"
8+
9+
httpresponse "github.com/mutablelogic/go-server/pkg/httpresponse"
10+
// Packages
11+
)
12+
13+
////////////////////////////////////////////////////////////////////////////////
14+
// GLOBALS
15+
16+
const (
17+
providerLabel = "root"
18+
)
19+
20+
////////////////////////////////////////////////////////////////////////////////
21+
// PUBLIC METHODS
22+
23+
// Run a task until context is cancelled and return any errors
24+
func (provider *provider) Run(parent context.Context) error {
25+
var result error
26+
27+
// Append the provider to the context
28+
parent = withProvider(parent, provider)
29+
30+
// Create a child context which will allow us to cancel all the tasks
31+
// prematurely if any of them fail
32+
ctx, prematureCancel := context.WithCancel(parent)
33+
defer prematureCancel()
34+
35+
// Put the provider at the top of the path
36+
ctx = withPath(ctx, providerLabel)
37+
38+
// Create all the tasks
39+
if err := provider.constructor(parent); err != nil {
40+
return err
41+
}
42+
43+
// Run the tasks in parallel
44+
for _, label := range provider.order {
45+
task, exists := provider.task[label]
46+
if !exists {
47+
return httpresponse.ErrNotFound.Withf("Task %q not found", label)
48+
}
49+
50+
// Create a context for each task
51+
ctx, cancel := context.WithCancel(context.Background())
52+
task.Context = withProvider(ctx, provider)
53+
task.CancelFunc = cancel
54+
task.WaitGroup.Add(1)
55+
go func(task *state) {
56+
defer task.WaitGroup.Done()
57+
defer task.CancelFunc()
58+
provider.Print(ctx, "Starting task ", label)
59+
if err := task.Run(task.Context); err != nil {
60+
result = errors.Join(result, fmt.Errorf("%q: %w", label, err))
61+
prematureCancel()
62+
}
63+
}(task)
64+
}
65+
66+
// Wait for the context to be cancelled
67+
<-ctx.Done()
68+
69+
// Cancel all the tasks in reverse order, waiting for each to complete before cancelling the next
70+
// TODO: Make sure we are cancelling in the right order
71+
sort.Sort(sort.Reverse(sort.StringSlice(provider.order)))
72+
for _, label := range provider.order {
73+
provider.Print(ctx, "Stopping task ", label)
74+
provider.task[label].CancelFunc()
75+
provider.task[label].WaitGroup.Wait()
76+
}
77+
78+
// Return any errors
79+
return result
80+
}

pkg/types/duration.go

-10
This file was deleted.

0 commit comments

Comments
 (0)