forked from cortesi/devd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
route.go
138 lines (119 loc) · 3.41 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
package devd
import (
"crypto/tls"
"errors"
"fmt"
"html/template"
"net/http"
"net/url"
"github.com/cortesi/devd/fileserver"
"github.com/cortesi/devd/httpctx"
"github.com/cortesi/devd/inject"
"github.com/cortesi/devd/reverseproxy"
"github.com/cortesi/devd/routespec"
)
// Endpoint is the destination of a Route - either on the filesystem or
// forwarding to another URL
type endpoint interface {
Handler(prefix string, 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(prefix string, 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 httpctx.StripPrefix(prefix, 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 "forward to " + ep.Scheme + "://" + ep.Host + ep.Path
}
// An enpoint that serves a filesystem location
type filesystemEndpoint struct {
Root string
notFoundRoutes []routespec.RouteSpec
}
func newFilesystemEndpoint(path string, notfound []string) (*filesystemEndpoint, error) {
rparts := []routespec.RouteSpec{}
for _, p := range notfound {
rp, err := routespec.ParseRouteSpec(p)
if err != nil {
return nil, err
}
if rp.IsURL {
return nil, fmt.Errorf("Not found over-ride target cannot be a URL.")
}
rparts = append(rparts, *rp)
}
return &filesystemEndpoint{path, rparts}, nil
}
func (ep filesystemEndpoint) Handler(prefix string, templates *template.Template, ci inject.CopyInject) httpctx.Handler {
return &fileserver.FileServer{
Version: "devd " + Version,
Root: http.Dir(ep.Root),
Inject: ci,
Templates: templates,
NotFoundRoutes: ep.notFoundRoutes,
Prefix: prefix,
}
}
func (ep filesystemEndpoint) String() string {
return "reads files from " + ep.Root
}
// 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, notfound []string) (*Route, error) {
rp, err := routespec.ParseRouteSpec(s)
if err != nil {
return nil, err
}
var ep endpoint
if rp.IsURL {
ep, err = newForwardEndpoint(rp.Value)
} else {
ep, err = newFilesystemEndpoint(rp.Value, notfound)
}
if err != nil {
return nil, err
}
return &Route{rp.Host, rp.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, notfound []string) error {
s, err := newRoute(value, notfound)
if err != nil {
return err
}
if _, exists := f[s.MuxMatch()]; exists {
return errors.New("Route already exists.")
}
f[s.MuxMatch()] = *s
return nil
}