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/http"
20
+ "strconv"
21
+
22
+ "github.com/caddyserver/caddy/v2"
23
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
24
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
25
+)
26
+
27
+func init() {
28
+ caddy.RegisterModule(CopyResponseHandler{})
29
+ caddy.RegisterModule(CopyResponseHeadersHandler{})
30
+}
31
+
32
+// CopyResponseHandler is a special HTTP handler which may
33
+// only be used within reverse_proxy's handle_response routes,
34
+// to copy the proxy response. EXPERIMENTAL, subject to change.
35
+type CopyResponseHandler struct {
36
+ // To write the upstream response's body but with a different
37
+ // status code, set this field to the desired status code.
38
+ StatusCode caddyhttp.WeakString `json:"status_code,omitempty"`
39
+
40
+ ctx caddy.Context
41
+}
42
+
43
+// CaddyModule returns the Caddy module information.
44
+func (CopyResponseHandler) CaddyModule() caddy.ModuleInfo {
45
+ return caddy.ModuleInfo{
46
+ ID: "http.handlers.copy_response",
47
+ New: func() caddy.Module { return new(CopyResponseHandler) },
48
+ }
49
+}
50
+
51
+// Provision ensures that h is set up properly before use.
52
+func (h *CopyResponseHandler) Provision(ctx caddy.Context) error {
53
+ h.ctx = ctx
54
+ return nil
55
+}
56
+
57
+// ServeHTTP implements the Handler interface.
58
+func (h CopyResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, _ caddyhttp.Handler) error {
59
+ repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
60
+ hrc, ok := req.Context().Value(proxyHandleResponseContextCtxKey).(*handleResponseContext)
61
+
62
+ // don't allow this to be used outside of handle_response routes
63
+ if !ok {
64
+ return caddyhttp.Error(http.StatusInternalServerError,
65
+ fmt.Errorf("cannot use 'copy_response' outside of reverse_proxy's handle_response routes"))
66
+ }
67
+
68
+ // allow a custom status code to be written; otherwise the
69
+ // status code from the upstream resposne is written
70
+ if codeStr := h.StatusCode.String(); codeStr != "" {
71
+ intVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, ""))
72
+ if err != nil {
73
+ return caddyhttp.Error(http.StatusInternalServerError, err)
74
+ }
75
+ hrc.response.StatusCode = intVal
76
+ }
77
+
78
+ // make sure the reverse_proxy handler doesn't try to call
79
+ // finalizeResponse again after we've already done it here.
80
+ hrc.isFinalized = true
81
+
82
+ // write the response
83
+ return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger, false)
84
+}
85
+
86
+// CopyResponseHeadersHandler is a special HTTP handler which may
87
+// only be used within reverse_proxy's handle_response routes,
88
+// to copy headers from the proxy response. EXPERIMENTAL;
89
+// subject to change.
90
+type CopyResponseHeadersHandler struct {
91
+ // A list of header fields to copy from the response.
92
+ // Cannot be defined at the same time as Exclude.
93
+ Include []string `json:"include,omitempty"`
94
+
95
+ // A list of header fields to skip copying from the response.
96
+ // Cannot be defined at the same time as Include.
97
+ Exclude []string `json:"exclude,omitempty"`
98
+
99
+ includeMap map[string]struct{}
100
+ excludeMap map[string]struct{}
101
+ ctx caddy.Context
102
+}
103
+
104
+// CaddyModule returns the Caddy module information.
105
+func (CopyResponseHeadersHandler) CaddyModule() caddy.ModuleInfo {
106
+ return caddy.ModuleInfo{
107
+ ID: "http.handlers.copy_response_headers",
108
+ New: func() caddy.Module { return new(CopyResponseHeadersHandler) },
109
+ }
110
+}
111
+
112
+// Validate ensures the h's configuration is valid.
113
+func (h *CopyResponseHeadersHandler) Validate() error {
114
+ if len(h.Exclude) > 0 && len(h.Include) > 0 {
115
+ return fmt.Errorf("cannot define both 'exclude' and 'include' lists at the same time")
116
+ }
117
+
118
+ return nil
119
+}
120
+
121
+// Provision ensures that h is set up properly before use.
122
+func (h *CopyResponseHeadersHandler) Provision(ctx caddy.Context) error {
123
+ h.ctx = ctx
124
+
125
+ // Optimize the include list by converting it to a map
126
+ if len(h.Include) > 0 {
127
+ h.includeMap = map[string]struct{}{}
128
+ }
129
+ for _, field := range h.Include {
130
+ h.includeMap[http.CanonicalHeaderKey(field)] = struct{}{}
131
+ }
132
+
133
+ // Optimize the exclude list by converting it to a map
134
+ if len(h.Exclude) > 0 {
135
+ h.excludeMap = map[string]struct{}{}
136
+ }
137
+ for _, field := range h.Exclude {
138
+ h.excludeMap[http.CanonicalHeaderKey(field)] = struct{}{}
139
+ }
140
+
141
+ return nil
142
+}
143
+
144
+// ServeHTTP implements the Handler interface.
145
+func (h CopyResponseHeadersHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next caddyhttp.Handler) error {
146
+ hrc, ok := req.Context().Value(proxyHandleResponseContextCtxKey).(*handleResponseContext)
147
+
148
+ // don't allow this to be used outside of handle_response routes
149
+ if !ok {
150
+ return caddyhttp.Error(http.StatusInternalServerError,
151
+ fmt.Errorf("cannot use 'copy_response_headers' outside of reverse_proxy's handle_response routes"))
152
+ }
153
+
154
+ for field, values := range hrc.response.Header {
155
+ // Check the include list first, skip
156
+ // the header if it's _not_ in this list.
157
+ if len(h.includeMap) > 0 {
158
+ if _, ok := h.includeMap[field]; !ok {
159
+ continue
160
+ }
161
+ }
162
+
163
+ // Then, check the exclude list, skip
164
+ // the header if it _is_ in this list.
165
+ if len(h.excludeMap) > 0 {
166
+ if _, ok := h.excludeMap[field]; ok {
167
+ continue
168
+ }
169
+ }
170
+
171
+ // Copy all the values for the header.
172
+ for _, value := range values {
173
+ rw.Header().Add(field, value)
174
+ }
175
+ }
176
+
177
+ return next.ServeHTTP(rw, req)
178
+}
179
+
180
+// Interface guards
181
+var (
182
+ _ caddyhttp.MiddlewareHandler = (*CopyResponseHandler)(nil)
183
+ _ caddyfile.Unmarshaler = (*CopyResponseHandler)(nil)
184
+ _ caddy.Provisioner = (*CopyResponseHandler)(nil)
185
+
186
+ _ caddyhttp.MiddlewareHandler = (*CopyResponseHeadersHandler)(nil)
187
+ _ caddyfile.Unmarshaler = (*CopyResponseHeadersHandler)(nil)
188
+ _ caddy.Provisioner = (*CopyResponseHeadersHandler)(nil)
189
+ _ caddy.Validator = (*CopyResponseHeadersHandler)(nil)
190
+)