Skip to content

Commit c9b915f

Browse files
committed
feat: initial HTTP API. Several refactors. New structure for entities
1 parent 6019b33 commit c9b915f

File tree

8 files changed

+314
-68
lines changed

8 files changed

+314
-68
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# sandbox [![Build Status](https://travis-ci.org/vinxi/sandbox.png)](https://travis-ci.org/vinxi/sandbox) [![GoDoc](https://godoc.org/github.com/vinxi/sandbox?status.svg)](https://godoc.org/github.com/vinxi/sandbox) [![Coverage Status](https://coveralls.io/repos/github/vinxi/sandbox/badge.svg?branch=master)](https://coveralls.io/github/vinxi/sandbox?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/vinxi/sandbox)](https://goreportcard.com/report/github.com/vinxi/sandbox)
1+
# sandbox [![Build Status](https://travis-ci.org/vinxi/sandbox.png)](https://travis-ci.org/vinxi/sandbox) [![GoDoc](https://godoc.org/github.com/vinxi/sandbox?status.svg)](https://godoc.org/github.com/vinxi/sandbox) [![Coverage Status](https://coveralls.io/repos/github/vinxi/sandbox/badge.svg?branch=master)](https://coveralls.io/github/vinxi/sandbox?branch=master)
22

33
Sandbox is a vinxi based full-featured, high-level, remotely configurable proxy solution.
44

_examples/default/default.go

+18-5
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,37 @@ package main
22

33
import (
44
"fmt"
5+
"os"
6+
57
"gopkg.in/vinxi/sandbox.v0"
8+
"gopkg.in/vinxi/sandbox.v0/plugins/static"
9+
"gopkg.in/vinxi/sandbox.v0/rules"
610
"gopkg.in/vinxi/vinxi.v0"
711
)
812

913
const port = 3100
1014

1115
func main() {
16+
cwd, _ := os.Getwd()
17+
1218
// Create a new vinxi proxy
13-
vs := vinxi.NewServer(vinxi.ServerOptions{Port: port})
19+
v := vinxi.New()
20+
21+
// Manage current vinxi instance
22+
manager := sandbox.Manage(v)
23+
scope := manager.NewScope(rules.NewPath("/foo"))
24+
scope.UsePlugin(static.New(cwd))
1425

15-
// Attach the apachelog middleware
16-
vs.Use(apachelog.Default)
26+
go func() {
27+
fmt.Printf("Admin server listening on port: %d\n", port+100)
28+
manager.ServeAndListen(sandbox.ServerOptions{Port: port + 100})
29+
}()
1730

1831
// Target server to forward
19-
vs.Forward("http://httpbin.org")
32+
v.Forward("http://httpbin.org")
2033

2134
fmt.Printf("Server listening on port: %d\n", port)
22-
err := vs.Listen()
35+
_, err := v.ServeAndListen(vinxi.ServerOptions{Port: port})
2336
if err != nil {
2437
fmt.Errorf("Error: %s\n", err)
2538
}

interface.go

-24
This file was deleted.

manager.go

+139-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package sandbox
22

33
import (
4+
"bytes"
5+
"encoding/json"
6+
"io"
47
"net/http"
58

9+
"github.com/bmizerany/pat"
10+
"github.com/dchest/uniuri"
611
"gopkg.in/vinxi/layer.v0"
712
"gopkg.in/vinxi/vinxi.v0"
813
)
@@ -16,9 +21,10 @@ type Options struct {
1621
}
1722

1823
type Rule interface {
24+
ID() string
1925
Name() string
2026
Description() string
21-
Options() Options
27+
// Options() Options
2228
JSONConfig() string
2329
Match(*http.Request) bool
2430
}
@@ -28,12 +34,17 @@ type Scope struct {
2834
rules []Rule
2935
plugins *PluginLayer
3036

37+
ID string
3138
Name string
3239
Description string
3340
}
3441

35-
func NewScope() *Scope {
36-
return &Scope{Plugins: NewPluginLayer()}
42+
func NewScope(rules ...Rule) *Scope {
43+
return &Scope{ID: uniuri.New(), Name: "default", plugins: NewPluginLayer(), rules: rules}
44+
}
45+
46+
func (s *Scope) UsePlugin(plugin Plugin) {
47+
s.plugins.Use(plugin)
3748
}
3849

3950
func (s *Scope) AddRule(rules ...Rule) {
@@ -52,22 +63,23 @@ func (s *Scope) Enable() {
5263
s.disabled = false
5364
}
5465

55-
func (s *Scope) HandleHTTP(h http.Handler) func(w http.ResponseWriter, req *http.Request) {
56-
return func(w http.ResponseWriter, req *http.Request) {
66+
func (s *Scope) HandleHTTP(h http.Handler) func(http.ResponseWriter, *http.Request) {
67+
return func(w http.ResponseWriter, r *http.Request) {
5768
if s.disabled {
5869
h.ServeHTTP(w, r)
5970
return
6071
}
6172

62-
w.WriteHeader(502)
63-
w.Write([]byte("bad request"))
64-
}
65-
}
73+
for _, rule := range s.rules {
74+
if !rule.Match(r) {
75+
// Continue
76+
h.ServeHTTP(w, r)
77+
return
78+
}
79+
}
6680

67-
func NewInstance(vinxi *vinxi.Vinxi) *Instance {
68-
plugins := NewPluginLayer()
69-
plugins.Register(vinxi)
70-
return &Instance{plugins: plugins, vinxi: vinxi}
81+
s.plugins.Run(w, r, h)
82+
}
7183
}
7284

7385
type Manager struct {
@@ -82,17 +94,127 @@ func Manage(instance *vinxi.Vinxi) *Manager {
8294
return m
8395
}
8496

85-
func (a *Manager) HandleHTTP(w http.ResponseWriter, req *http.Request, h http.Handler) {
97+
func (m *Manager) NewScope(rules ...Rule) *Scope {
98+
scope := NewScope(rules...)
99+
m.scopes = append(m.scopes, scope)
100+
return scope
101+
}
102+
103+
func (a *Manager) HandleHTTP(w http.ResponseWriter, r *http.Request, h http.Handler) {
86104
next := h
87105

88-
for _, scope := range scopes {
106+
for _, scope := range a.scopes {
89107
next = http.HandlerFunc(scope.HandleHTTP(next))
90108
}
91109

92110
next.ServeHTTP(w, r)
93111
}
94112

95-
func (a *Manager) Listen(opts ServerOptions) error {
113+
type JSONRule struct {
114+
ID string `json:"id"`
115+
Name string `json:"name,omitempty"`
116+
Description string `json:"description,omitempty"`
117+
Config string `json:"config,omitempty"`
118+
}
119+
120+
type JSONPlugin struct {
121+
ID string `json:"id"`
122+
Name string `json:"name,omitempty"`
123+
Description string `json:"description,omitempty"`
124+
Enabled bool `json:"enabled,omitempty"`
125+
}
126+
127+
type JSONScope struct {
128+
ID string `json:"id"`
129+
Name string `json:"name,omitempty"`
130+
Rules []JSONRule `json:"rules,omitempty"`
131+
Plugins []JSONPlugin `json:"plugins,omitempty"`
132+
}
133+
134+
func (a *Manager) ServeAndListen(opts ServerOptions) (*http.Server, error) {
96135
a.Server = NewServer(opts)
97-
return server.ListenAndServe()
136+
137+
m := pat.New()
138+
a.Server.Handler = m
139+
140+
// Define route handlers
141+
m.Get("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
142+
io.WriteString(w, "vinxi HTTP API manager "+Version)
143+
}))
144+
145+
m.Get("/scopes", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
146+
buf := &bytes.Buffer{}
147+
scopes := createScopes(a.scopes)
148+
149+
err := json.NewEncoder(buf).Encode(scopes)
150+
if err != nil {
151+
w.WriteHeader(500)
152+
w.Write([]byte(err.Error()))
153+
return
154+
}
155+
156+
w.Write(buf.Bytes())
157+
}))
158+
159+
m.Get("/scopes/:scope", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
160+
id := req.URL.Query().Get(":scope")
161+
162+
// Find scope by ID
163+
for _, scope := range a.scopes {
164+
if scope.ID == id {
165+
data, err := encodeJSON(createScope(scope))
166+
if err != nil {
167+
w.WriteHeader(500)
168+
w.Write([]byte(err.Error()))
169+
return
170+
}
171+
w.Write(data)
172+
return
173+
}
174+
}
175+
176+
w.WriteHeader(404)
177+
w.Write([]byte("not found"))
178+
}))
179+
180+
return a.Server, Listen(a.Server, opts)
181+
}
182+
183+
func encodeJSON(data interface{}) ([]byte, error) {
184+
buf := &bytes.Buffer{}
185+
err := json.NewEncoder(buf).Encode(data)
186+
return buf.Bytes(), err
187+
}
188+
189+
func createScope(scope *Scope) JSONScope {
190+
return JSONScope{
191+
ID: scope.ID,
192+
Name: scope.Name,
193+
Rules: createRules(scope),
194+
Plugins: createPlugins(scope),
195+
}
196+
}
197+
198+
func createScopes(scopes []*Scope) []JSONScope {
199+
buf := make([]JSONScope, len(scopes))
200+
for i, scope := range scopes {
201+
buf[i] = createScope(scope)
202+
}
203+
return buf
204+
}
205+
206+
func createRules(scope *Scope) []JSONRule {
207+
rules := make([]JSONRule, len(scope.rules))
208+
for i, rule := range scope.rules {
209+
rules[i] = JSONRule{ID: rule.ID(), Name: rule.Name(), Description: rule.Description(), Config: rule.JSONConfig()}
210+
}
211+
return rules
212+
}
213+
214+
func createPlugins(scope *Scope) []JSONPlugin {
215+
plugins := make([]JSONPlugin, scope.plugins.Len())
216+
for i, plugin := range scope.plugins.pool {
217+
plugins[i] = JSONPlugin{ID: plugin.ID(), Name: plugin.Name(), Description: plugin.Description(), Enabled: plugin.IsEnabled()}
218+
}
219+
return plugins
98220
}

plugin.go

+84-11
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,76 @@ package sandbox
33
import (
44
"net/http"
55

6+
"github.com/dchest/uniuri"
67
"gopkg.in/vinxi/layer.v0"
78
)
89

9-
type Plugin struct {
10-
Name string
11-
Description string
10+
type Handler func(http.Handler) http.Handler
11+
12+
// Plugin represents the required interface implemented by plugins.
13+
type Plugin interface {
14+
// ID is used to retrieve the plugin unique identifier.
15+
ID() string
16+
// Name is used to retrieve the plugin name identifier.
17+
Name() string
18+
// Description is used to retrieve a human friendly
19+
// description of what the plugin does.
20+
Description() string
21+
// Enable is used to enable the current plugin.
22+
// If the plugin has been already enabled, the call is no-op.
23+
Enable()
24+
// Disable is used to disable the current plugin.
25+
Disable()
26+
// Remove is used to disable and remove a plugin.
27+
// Remove()
28+
// IsEnabled is used to check if a plugin is enabled or not.
29+
IsEnabled() bool
30+
// HandleHTTP is used to run the plugin task.
31+
// Note: add erro reporting layer
32+
HandleHTTP(http.Handler) http.Handler
33+
}
34+
35+
type plugin struct {
36+
disabled bool
37+
id string
38+
name string
39+
description string
40+
handler Handler
41+
}
42+
43+
func NewPlugin(name, description string, handler Handler) Plugin {
44+
return &plugin{id: uniuri.New(), name: name, description: description, handler: handler}
45+
}
46+
47+
func (p *plugin) ID() string {
48+
return p.id
49+
}
50+
51+
func (p *plugin) Name() string {
52+
return p.name
53+
}
54+
55+
func (p *plugin) Description() string {
56+
return p.description
57+
}
58+
59+
func (p *plugin) Disable() {
60+
p.disabled = true
61+
}
62+
63+
func (p *plugin) Enable() {
64+
p.disabled = false
65+
}
66+
67+
func (p *plugin) IsEnabled() bool {
68+
return p.disabled == false
69+
}
70+
71+
func (p *plugin) HandleHTTP(h http.Handler) http.Handler {
72+
next := p.handler(h)
73+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
74+
next.ServeHTTP(w, r)
75+
})
1276
}
1377

1478
// PluginLayer represents a plugins layer designed to intrument
@@ -24,15 +88,24 @@ func NewPluginLayer() *PluginLayer {
2488
return &PluginLayer{}
2589
}
2690

91+
func (l *PluginLayer) Use(plugin Plugin) {
92+
l.pool = append(l.pool, plugin)
93+
}
94+
95+
func (l *PluginLayer) Len() int {
96+
return len(l.pool)
97+
}
98+
2799
// Register implements the middleware Register method.
28-
func (l *PluginLayer) Register(mw *layer.Middleware) {
29-
mw.Use("error", l.run)
30-
mw.Use("request", l.run)
100+
func (l *PluginLayer) Register(mw *layer.Layer) {
101+
mw.Use("error", l.Run)
102+
mw.Use("request", l.Run)
31103
}
32104

33-
func (l *PluginLayer) run(h http.Handler) http.Handler {
34-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35-
// no-op for now
36-
h.ServeHTTP(w, r)
37-
})
105+
func (l *PluginLayer) Run(w http.ResponseWriter, r *http.Request, h http.Handler) {
106+
next := h
107+
for _, plugin := range l.pool {
108+
next = plugin.HandleHTTP(next)
109+
}
110+
next.ServeHTTP(w, r)
38111
}

0 commit comments

Comments
 (0)