forked from cortesi/devd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
route.go
147 lines (128 loc) · 3.21 KB
/
route.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package devd
import (
"crypto/tls"
"errors"
"fmt"
"html/template"
"net/http"
"net/url"
"strings"
"github.com/cortesi/devd/fileserver"
"github.com/cortesi/devd/httpctx"
"github.com/cortesi/devd/inject"
"github.com/cortesi/devd/reverseproxy"
)
const defaultDomain = "devd.io"
func isURL(s string) bool {
return strings.HasPrefix(s, "http://") ||
strings.HasPrefix(s, "https://")
}
// Endpoint is the destination of a Route - either on the filesystem or
// forwarding to another URL
type endpoint interface {
Handler(templates *template.Template, ci inject.CopyInject) httpctx.Handler
String() string
}
// An endpoint that forwards to an upstream URL
type forwardEndpoint url.URL
func (ep forwardEndpoint) Handler(templates *template.Template, ci inject.CopyInject) httpctx.Handler {
u := url.URL(ep)
rp := reverseproxy.NewSingleHostReverseProxy(&u, ci)
rp.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
return rp
}
func newForwardEndpoint(path string) (*forwardEndpoint, error) {
url, err := url.Parse(path)
if err != nil {
return nil, fmt.Errorf("Could not parse route URL: %s", err)
}
f := forwardEndpoint(*url)
return &f, nil
}
func (ep forwardEndpoint) String() string {
return ep.String()
}
// An enpoint that serves a filesystem location
type filesystemEndpoint string
func newFilesystemEndpoint(path string) (*filesystemEndpoint, error) {
f := filesystemEndpoint(path)
return &f, nil
}
func (ep filesystemEndpoint) Handler(templates *template.Template, ci inject.CopyInject) httpctx.Handler {
return &fileserver.FileServer{
Root: http.Dir(ep),
Inject: ci,
Templates: templates,
}
}
func (ep filesystemEndpoint) String() string {
return string(ep)
}
// Route is a mapping from a (host, path) tuple to an endpoint.
type Route struct {
Host string
Path string
Endpoint endpoint
}
// Constructs a new route from a string specifcation. Specifcations are of the
// form ANCHOR=VALUE.
func newRoute(s string) (*Route, error) {
seq := strings.SplitN(s, "=", 2)
var path, value string
if len(seq) == 1 {
path = "/"
value = seq[0]
} else {
path = seq[0]
value = seq[1]
}
if path == "" || value == "" {
return nil, errors.New("Invalid specification")
}
host := ""
if path[0] != '/' {
seq := strings.SplitN(path, "/", 2)
host = seq[0] + "." + defaultDomain
switch len(seq) {
case 1:
path = "/"
case 2:
path = "/" + seq[1]
}
}
var ep endpoint
var err error
if isURL(value) {
ep, err = newForwardEndpoint(value)
} else {
ep, err = newFilesystemEndpoint(value)
}
if err != nil {
return nil, err
}
return &Route{host, path, ep}, nil
}
// MuxMatch produces a match clause suitable for passing to a Mux
func (f Route) MuxMatch() string {
// Path is guaranteed to start with /
return f.Host + f.Path
}
// RouteCollection is a collection of routes
type RouteCollection map[string]Route
func (f *RouteCollection) String() string {
return fmt.Sprintf("%v", *f)
}
// Add a route to the collection
func (f RouteCollection) Add(value string) error {
s, err := newRoute(value)
if err != nil {
return err
}
if _, exists := f[s.MuxMatch()]; exists {
return errors.New("Route already exists.")
}
f[s.MuxMatch()] = *s
return nil
}