@marcel91200 / caddy Mirror · read-only / commits / 2f684e42
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

reverse_proxy/headers: Expose header replacement ability in Caddyfile

Adds header_up and header_down subdirectives to reverse_proxy

Matthew Holt committed Sep 20, 2019 at 13:13 UTC 2f684e42d5b8b78fe688b2f7e31d54d2333c0701
2 files changed +107 -36
modules/caddyhttp/headers/caddyfile.go
+62 -36
@@ -30,26 +30,40 @@ func init() {
30 // parseCaddyfile sets up the handler for response headers from
31 // Caddyfile tokens. Syntax:
32 //
33 -// headers [<matcher>] [[+|-]<field> <value>] {
34 -// [+][<field>] [<value>]
35 -// [-<field>]
33 +// headers [<matcher>] [[+|-]<field> [<value|regexp>] [<replacement>]] {
34 +// [+]<field> [<value|regexp> [<replacement>]]
35 +// -<field>
36 // }
37 //
38 // Either a block can be opened or a single header field can be configured
39 // in the first line, but not both in the same directive.
40 func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
41 hdr := new(Handler)
42 +
43 + makeResponseOps := func() {
44 + if hdr.Response == nil {
45 + hdr.Response = &RespHeaderOps{
46 + HeaderOps: new(HeaderOps),
47 + Deferred: true,
48 + }
49 + }
50 + }
51 +
52 for h.Next() {
53 // first see if headers are in the initial line
54 var hasArgs bool
55 if h.NextArg() {
56 hasArgs = true
57 field := h.Val()
48 - var value string
58 + var value, replacement string
59 if h.NextArg() {
60 value = h.Val()
61 }
52 - processCaddyfileLineRespHdr(hdr, field, value)
62 + if h.NextArg() {
63 + replacement = h.Val()
64 + }
65 + makeResponseOps()
66 + CaddyfileHeaderOp(hdr.Response.HeaderOps, field, value, replacement)
67 }
68
69 // if not, they should be in a block
@@ -58,20 +72,25 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
72 return nil, h.Err("cannot specify headers in both arguments and block")
73 }
74 field := h.Val()
61 - var value string
75 + var value, replacement string
76 if h.NextArg() {
77 value = h.Val()
78 }
65 - processCaddyfileLineRespHdr(hdr, field, value)
79 + if h.NextArg() {
80 + replacement = h.Val()
81 + }
82 + makeResponseOps()
83 + CaddyfileHeaderOp(hdr.Response.HeaderOps, field, value, replacement)
84 }
85 }
86 +
87 return hdr, nil
88 }
89
90 // parseReqHdrCaddyfile sets up the handler for request headers
91 // from Caddyfile tokens. Syntax:
92 //
74 -// request_header [<matcher>] [[+|-]<field> <value>]
93 +// request_header [<matcher>] [[+|-]<field> [<value|regexp>] [<replacement>]]
94 //
95 func parseReqHdrCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
96 hdr := new(Handler)
@@ -80,27 +99,18 @@ func parseReqHdrCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler,
99 return nil, h.ArgErr()
100 }
101 field := h.Val()
83 - var value string
102 + var value, replacement string
103 if h.NextArg() {
104 value = h.Val()
105 }
106 + if h.NextArg() {
107 + replacement = h.Val()
108 + }
109
110 if hdr.Request == nil {
111 hdr.Request = new(HeaderOps)
112 }
91 - if strings.HasPrefix(field, "+") {
92 - if hdr.Request.Add == nil {
93 - hdr.Request.Add = make(http.Header)
94 - }
95 - hdr.Request.Add.Set(field[1:], value)
96 - } else if strings.HasPrefix(field, "-") {
97 - hdr.Request.Delete = append(hdr.Request.Delete, field[1:])
98 - } else {
99 - if hdr.Request.Set == nil {
100 - hdr.Request.Set = make(http.Header)
101 - }
102 - hdr.Request.Set.Set(field, value)
103 - }
113 + CaddyfileHeaderOp(hdr.Request, field, value, replacement)
114
115 if h.NextArg() {
116 return nil, h.ArgErr()
@@ -109,24 +119,40 @@ func parseReqHdrCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler,
119 return hdr, nil
120 }
121
112 -func processCaddyfileLineRespHdr(hdr *Handler, field, value string) {
113 - if hdr.Response == nil {
114 - hdr.Response = &RespHeaderOps{
115 - HeaderOps: new(HeaderOps),
116 - Deferred: true,
117 - }
118 - }
122 +// CaddyfileHeaderOp applies a new header operation according to
123 +// field, value, and replacement. The field can be prefixed with
124 +// "+" or "-" to specify adding or removing; otherwise, the value
125 +// will be set (overriding any previous value). If replacement is
126 +// non-empty, value will be treated as a regular expression which
127 +// will be used to search and then replacement will be used to
128 +// complete the substring replacement; in that case, any + or -
129 +// prefix to field will be ignored.
130 +func CaddyfileHeaderOp(ops *HeaderOps, field, value, replacement string) {
131 if strings.HasPrefix(field, "+") {
120 - if hdr.Response.Add == nil {
121 - hdr.Response.Add = make(http.Header)
132 + if ops.Add == nil {
133 + ops.Add = make(http.Header)
134 }
123 - hdr.Response.Add.Set(field[1:], value)
135 + ops.Add.Set(field[1:], value)
136 } else if strings.HasPrefix(field, "-") {
125 - hdr.Response.Delete = append(hdr.Response.Delete, field[1:])
137 + ops.Delete = append(ops.Delete, field[1:])
138 } else {
127 - if hdr.Response.Set == nil {
128 - hdr.Response.Set = make(http.Header)
139 + if replacement == "" {
140 + if ops.Set == nil {
141 + ops.Set = make(http.Header)
142 + }
143 + ops.Set.Set(field, value)
144 + } else {
145 + if ops.Replace == nil {
146 + ops.Replace = make(map[string][]Replacement)
147 + }
148 + field = strings.TrimLeft(field, "+-")
149 + ops.Replace[field] = append(
150 + ops.Replace[field],
151 + Replacement{
152 + SearchRegexp: value,
153 + Replace: replacement,
154 + },
155 + )
156 }
130 - hdr.Response.Set.Set(field, value)
157 }
158 }
modules/caddyhttp/reverseproxy/caddyfile.go
+45
@@ -25,6 +25,7 @@ import (
25 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
26 "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
27 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
28 + "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
29 "github.com/dustin/go-humanize"
30 )
31
@@ -67,6 +68,10 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
68 // unhealthy_status <status>
69 // unhealthy_latency <duration>
70 //
71 +// # header manipulation
72 +// header_up [+|-]<field> [<value|regexp> [<replacement>]]
73 +// header_down [+|-]<field> [<value|regexp> [<replacement>]]
74 +//
75 // # round trip
76 // transport <name> {
77 // ...
@@ -327,6 +332,46 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
332 }
333 h.HealthChecks.Passive.UnhealthyLatency = caddy.Duration(dur)
334
335 + case "header_up":
336 + if h.Headers == nil {
337 + h.Headers = new(headers.Handler)
338 + }
339 + if h.Headers.Request == nil {
340 + h.Headers.Request = new(headers.HeaderOps)
341 + }
342 + args := d.RemainingArgs()
343 + switch len(args) {
344 + case 1:
345 + headers.CaddyfileHeaderOp(h.Headers.Request, args[0], "", "")
346 + case 2:
347 + headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], "")
348 + case 3:
349 + headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], args[2])
350 + default:
351 + return d.ArgErr()
352 + }
353 +
354 + case "header_down":
355 + if h.Headers == nil {
356 + h.Headers = new(headers.Handler)
357 + }
358 + if h.Headers.Response == nil {
359 + h.Headers.Response = &headers.RespHeaderOps{
360 + HeaderOps: new(headers.HeaderOps),
361 + }
362 + }
363 + args := d.RemainingArgs()
364 + switch len(args) {
365 + case 1:
366 + headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], "", "")
367 + case 2:
368 + headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], args[1], "")
369 + case 3:
370 + headers.CaddyfileHeaderOp(h.Headers.Response.HeaderOps, args[0], args[1], args[2])
371 + default:
372 + return d.ArgErr()
373 + }
374 +
375 case "transport":
376 if !d.NextArg() {
377 return d.ArgErr()