Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 13, 21:45 UTC
reverseproxy: Refactor dial address parsing, augment command parsing (#4616)
Francis Lavoie committed
Mar 5, 2022 at 18:34 UTC
d058dee11d7cfcf0b711f0378d10c9e5cabc8982
4 files changed
+134
-119
caddyconfig/caddyfile/dispenser.go
+5
-1
@@ -350,7 +350,11 @@ func (d *Dispenser) Err(msg string) error {
350
351
// Errf is like Err, but for formatted error messages
352
func (d *Dispenser) Errf(format string, args ...interface{}) error {
353
- err := fmt.Errorf(format, args...)
353
+ return d.WrapErr(fmt.Errorf(format, args...))
354
+}
355
+
356
+// WrapErr takes an existing error and adds the Caddyfile file and line number.
357
+func (d *Dispenser) WrapErr(err error) error {
358
return fmt.Errorf("%s:%d - Error during parsing: %w", d.File(), d.Line(), err)
359
}
360
modules/caddyhttp/reverseproxy/addresses.go
new
+112
@@ -0,0 +1,112 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package reverseproxy
16
+
17
+import (
18
+ "fmt"
19
+ "net"
20
+ "net/url"
21
+ "strings"
22
+
23
+ "github.com/caddyserver/caddy/v2"
24
+)
25
+
26
+// parseUpstreamDialAddress parses configuration inputs for
27
+// the dial address, including support for a scheme in front
28
+// as a shortcut for the port number, and a network type,
29
+// for example 'unix' to dial a unix socket.
30
+//
31
+// TODO: the logic in this function is kind of sensitive, we
32
+// need to write tests before making any more changes to it
33
+func parseUpstreamDialAddress(upstreamAddr string) (string, string, error) {
34
+ var network, scheme, host, port string
35
+
36
+ if strings.Contains(upstreamAddr, "://") {
37
+ // we get a parsing error if a placeholder is specified
38
+ // so we return a more user-friendly error message instead
39
+ // to explain what to do instead
40
+ if strings.Contains(upstreamAddr, "{") {
41
+ return "", "", fmt.Errorf("due to parsing difficulties, placeholders are not allowed when an upstream address contains a scheme")
42
+ }
43
+
44
+ toURL, err := url.Parse(upstreamAddr)
45
+ if err != nil {
46
+ return "", "", fmt.Errorf("parsing upstream URL: %v", err)
47
+ }
48
+
49
+ // there is currently no way to perform a URL rewrite between choosing
50
+ // a backend and proxying to it, so we cannot allow extra components
51
+ // in backend URLs
52
+ if toURL.Path != "" || toURL.RawQuery != "" || toURL.Fragment != "" {
53
+ return "", "", fmt.Errorf("for now, URLs for proxy upstreams only support scheme, host, and port components")
54
+ }
55
+
56
+ // ensure the port and scheme aren't in conflict
57
+ urlPort := toURL.Port()
58
+ if toURL.Scheme == "http" && urlPort == "443" {
59
+ return "", "", fmt.Errorf("upstream address has conflicting scheme (http://) and port (:443, the HTTPS port)")
60
+ }
61
+ if toURL.Scheme == "https" && urlPort == "80" {
62
+ return "", "", fmt.Errorf("upstream address has conflicting scheme (https://) and port (:80, the HTTP port)")
63
+ }
64
+ if toURL.Scheme == "h2c" && urlPort == "443" {
65
+ return "", "", fmt.Errorf("upstream address has conflicting scheme (h2c://) and port (:443, the HTTPS port)")
66
+ }
67
+
68
+ // if port is missing, attempt to infer from scheme
69
+ if toURL.Port() == "" {
70
+ var toPort string
71
+ switch toURL.Scheme {
72
+ case "", "http", "h2c":
73
+ toPort = "80"
74
+ case "https":
75
+ toPort = "443"
76
+ }
77
+ toURL.Host = net.JoinHostPort(toURL.Hostname(), toPort)
78
+ }
79
+
80
+ scheme, host, port = toURL.Scheme, toURL.Hostname(), toURL.Port()
81
+ } else {
82
+ // extract network manually, since caddy.ParseNetworkAddress() will always add one
83
+ if idx := strings.Index(upstreamAddr, "/"); idx >= 0 {
84
+ network = strings.ToLower(strings.TrimSpace(upstreamAddr[:idx]))
85
+ upstreamAddr = upstreamAddr[idx+1:]
86
+ }
87
+ var err error
88
+ host, port, err = net.SplitHostPort(upstreamAddr)
89
+ if err != nil {
90
+ host = upstreamAddr
91
+ }
92
+ // we can assume a port if only a hostname is specified, but use of a
93
+ // placeholder without a port likely means a port will be filled in
94
+ if port == "" && !strings.Contains(host, "{") {
95
+ port = "80"
96
+ }
97
+ }
98
+
99
+ // for simplest possible config, we only need to include
100
+ // the network portion if the user specified one
101
+ if network != "" {
102
+ return caddy.JoinNetworkAddress(network, host, port), scheme, nil
103
+ }
104
+
105
+ // if the host is a placeholder, then we don't want to join with an empty port,
106
+ // because that would just append an extra ':' at the end of the address.
107
+ if port == "" && strings.Contains(host, "{") {
108
+ return host, scheme, nil
109
+ }
110
+
111
+ return net.JoinHostPort(host, port), scheme, nil
112
+}
modules/caddyhttp/reverseproxy/caddyfile.go
+13
-95
@@ -18,7 +18,6 @@ import (
18
"log"
19
"net"
20
"net/http"
21
- "net/url"
21
"reflect"
22
"strconv"
23
"strings"
@@ -123,98 +122,6 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
122
// prefixed with "@" for use with "handle_response" blocks
123
h.responseMatchers = make(map[string]caddyhttp.ResponseMatcher)
124
126
- // TODO: the logic in this function is kind of sensitive, we need
127
- // to write tests before making any more changes to it
128
- upstreamDialAddress := func(upstreamAddr string) (string, error) {
129
- var network, scheme, host, port string
130
-
131
- if strings.Contains(upstreamAddr, "://") {
132
- // we get a parsing error if a placeholder is specified
133
- // so we return a more user-friendly error message instead
134
- // to explain what to do instead
135
- if strings.Contains(upstreamAddr, "{") {
136
- return "", d.Err("due to parsing difficulties, placeholders are not allowed when an upstream address contains a scheme")
137
- }
138
-
139
- toURL, err := url.Parse(upstreamAddr)
140
- if err != nil {
141
- return "", d.Errf("parsing upstream URL: %v", err)
142
- }
143
-
144
- // there is currently no way to perform a URL rewrite between choosing
145
- // a backend and proxying to it, so we cannot allow extra components
146
- // in backend URLs
147
- if toURL.Path != "" || toURL.RawQuery != "" || toURL.Fragment != "" {
148
- return "", d.Err("for now, URLs for proxy upstreams only support scheme, host, and port components")
149
- }
150
-
151
- // ensure the port and scheme aren't in conflict
152
- urlPort := toURL.Port()
153
- if toURL.Scheme == "http" && urlPort == "443" {
154
- return "", d.Err("upstream address has conflicting scheme (http://) and port (:443, the HTTPS port)")
155
- }
156
- if toURL.Scheme == "https" && urlPort == "80" {
157
- return "", d.Err("upstream address has conflicting scheme (https://) and port (:80, the HTTP port)")
158
- }
159
- if toURL.Scheme == "h2c" && urlPort == "443" {
160
- return "", d.Err("upstream address has conflicting scheme (h2c://) and port (:443, the HTTPS port)")
161
- }
162
-
163
- // if port is missing, attempt to infer from scheme
164
- if toURL.Port() == "" {
165
- var toPort string
166
- switch toURL.Scheme {
167
- case "", "http", "h2c":
168
- toPort = "80"
169
- case "https":
170
- toPort = "443"
171
- }
172
- toURL.Host = net.JoinHostPort(toURL.Hostname(), toPort)
173
- }
174
-
175
- scheme, host, port = toURL.Scheme, toURL.Hostname(), toURL.Port()
176
- } else {
177
- // extract network manually, since caddy.ParseNetworkAddress() will always add one
178
- if idx := strings.Index(upstreamAddr, "/"); idx >= 0 {
179
- network = strings.ToLower(strings.TrimSpace(upstreamAddr[:idx]))
180
- upstreamAddr = upstreamAddr[idx+1:]
181
- }
182
- var err error
183
- host, port, err = net.SplitHostPort(upstreamAddr)
184
- if err != nil {
185
- host = upstreamAddr
186
- }
187
- // we can assume a port if only a hostname is specified, but use of a
188
- // placeholder without a port likely means a port will be filled in
189
- if port == "" && !strings.Contains(host, "{") {
190
- port = "80"
191
- }
192
- }
193
-
194
- // the underlying JSON does not yet support different
195
- // transports (protocols or schemes) to each backend,
196
- // so we remember the last one we see and compare them
197
- if commonScheme != "" && scheme != commonScheme {
198
- return "", d.Errf("for now, all proxy upstreams must use the same scheme (transport protocol); expecting '%s://' but got '%s://'",
199
- commonScheme, scheme)
200
- }
201
- commonScheme = scheme
202
-
203
- // for simplest possible config, we only need to include
204
- // the network portion if the user specified one
205
- if network != "" {
206
- return caddy.JoinNetworkAddress(network, host, port), nil
207
- }
208
-
209
- // if the host is a placeholder, then we don't want to join with an empty port,
210
- // because that would just append an extra ':' at the end of the address.
211
- if port == "" && strings.Contains(host, "{") {
212
- return host, nil
213
- }
214
-
215
- return net.JoinHostPort(host, port), nil
216
- }
217
-
125
// appendUpstream creates an upstream for address and adds
126
// it to the list. If the address starts with "srv+" it is
127
// treated as a SRV-based upstream, and any port will be
@@ -224,10 +131,21 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
131
if isSRV {
132
address = strings.TrimPrefix(address, "srv+")
133
}
227
- dialAddr, err := upstreamDialAddress(address)
134
+
135
+ dialAddr, scheme, err := parseUpstreamDialAddress(address)
136
if err != nil {
229
- return err
137
+ return d.WrapErr(err)
138
}
139
+
140
+ // the underlying JSON does not yet support different
141
+ // transports (protocols or schemes) to each backend,
142
+ // so we remember the last one we see and compare them
143
+ if commonScheme != "" && scheme != commonScheme {
144
+ return d.Errf("for now, all proxy upstreams must use the same scheme (transport protocol); expecting '%s://' but got '%s://'",
145
+ commonScheme, scheme)
146
+ }
147
+ commonScheme = scheme
148
+
149
if isSRV {
150
if host, _, err := net.SplitHostPort(dialAddr); err == nil {
151
dialAddr = host
modules/caddyhttp/reverseproxy/command.go
+4
-23
@@ -18,7 +18,6 @@ import (
18
"encoding/json"
19
"flag"
20
"fmt"
21
- "net"
21
"net/http"
22
"strconv"
23
@@ -104,32 +103,14 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
103
}
104
105
// set up the upstream address; assume missing information from given parts
107
- toAddr, err := httpcaddyfile.ParseAddress(to)
106
+ toAddr, toScheme, err := parseUpstreamDialAddress(to)
107
if err != nil {
108
return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid upstream address %s: %v", to, err)
109
}
111
- if toAddr.Path != "" {
112
- return caddy.ExitCodeFailedStartup, fmt.Errorf("paths are not allowed: %s", to)
113
- }
114
- if toAddr.Scheme == "" {
115
- if toAddr.Port == httpsPort {
116
- toAddr.Scheme = "https"
117
- } else {
118
- toAddr.Scheme = "http"
119
- }
120
- }
121
- if toAddr.Port == "" {
122
- if toAddr.Scheme == "http" {
123
- toAddr.Port = httpPort
124
- } else if toAddr.Scheme == "https" {
125
- toAddr.Port = httpsPort
126
- }
127
- }
110
111
// proceed to build the handler and server
130
-
112
ht := HTTPTransport{}
132
- if toAddr.Scheme == "https" {
113
+ if toScheme == "https" {
114
ht.TLS = new(TLSConfig)
115
if insecure {
116
ht.TLS.InsecureSkipVerify = true
@@ -138,7 +119,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
119
120
handler := Handler{
121
TransportRaw: caddyconfig.JSONModuleObject(ht, "protocol", "http", nil),
141
- Upstreams: UpstreamPool{{Dial: net.JoinHostPort(toAddr.Host, toAddr.Port)}},
122
+ Upstreams: UpstreamPool{{Dial: toAddr}},
123
}
124
125
if changeHost {
@@ -185,7 +166,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
166
return caddy.ExitCodeFailedStartup, err
167
}
168
188
- fmt.Printf("Caddy proxying %s -> %s\n", fromAddr.String(), toAddr.String())
169
+ fmt.Printf("Caddy proxying %s -> %s\n", fromAddr.String(), toAddr)
170
171
select {}
172
}