42
if err != nil {
43
return nil, err
44
}
45
+ err = rp.FinalizeUnmarshalCaddyfile(h)
46
+ if err != nil {
47
+ return nil, err
48
+ }
49
return rp, nil
50
}
51
90
// transport <name> {
91
// ...
92
// }
93
+//
94
+// # handle responses
95
+// @name {
96
+// status <code...>
97
+// header <field> [<value>]
98
+// }
99
+// handle_response [<matcher>] [status_code] {
100
+// <directives...>
101
+// }
102
// }
103
//
104
// Proxy upstream addresses should be network dial addresses such
105
// as `host:port`, or a URL such as `scheme://host:port`. Scheme
106
// and port may be inferred from other parts of the address/URL; if
107
// either are missing, defaults to HTTP.
108
+//
109
+// The FinalizeUnmarshalCaddyfile method should be called after this
110
+// to finalize parsing of "handle_response" blocks, if possible.
111
func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
112
// currently, all backends must use the same scheme/protocol (the
113
// underlying JSON does not yet support per-backend transports)
118
var transport http.RoundTripper
119
var transportModuleName string
120
121
+ // collect the response matchers defined as subdirectives
122
+ // prefixed with "@" for use with "handle_response" blocks
123
+ h.responseMatchers = make(map[string]caddyhttp.ResponseMatcher)
124
+
125
// TODO: the logic in this function is kind of sensitive, we need
126
// to write tests before making any more changes to it
127
upstreamDialAddress := func(upstreamAddr string) (string, error) {
247
}
248
249
for d.NextBlock(0) {
250
+ // if the subdirective has an "@" prefix then we
251
+ // parse it as a response matcher for use with "handle_response"
252
+ if strings.HasPrefix(d.Val(), matcherPrefix) {
253
+ err := caddyhttp.ParseNamedResponseMatcher(d.NewFromNextSegment(), h.responseMatchers)
254
+ if err != nil {
255
+ return err
256
+ }
257
+ continue
258
+ }
259
+
260
switch d.Val() {
261
case "to":
262
args := d.RemainingArgs()
647
}
648
transport = rt
649
650
+ case "handle_response":
651
+ // delegate the parsing of handle_response to the caller,
652
+ // since we need the httpcaddyfile.Helper to parse subroutes.
653
+ // See h.FinalizeUnmarshalCaddyfile
654
+ h.handleResponseSegments = append(h.handleResponseSegments, d.NewFromNextSegment())
655
+
656
default:
657
return d.Errf("unrecognized subdirective %s", d.Val())
658
}
695
return nil
696
}
697
698
+// FinalizeUnmarshalCaddyfile finalizes the Caddyfile parsing which
699
+// requires having an httpcaddyfile.Helper to function, to parse subroutes.
700
+func (h *Handler) FinalizeUnmarshalCaddyfile(helper httpcaddyfile.Helper) error {
701
+ for _, d := range h.handleResponseSegments {
702
+ // consume the "handle_response" token
703
+ d.Next()
704
+
705
+ var matcher *caddyhttp.ResponseMatcher
706
+ args := d.RemainingArgs()
707
+
708
+ // the first arg should be a matcher (optional)
709
+ // the second arg should be a status code (optional)
710
+ // any more than that isn't currently supported
711
+ if len(args) > 2 {
712
+ return d.Errf("too many arguments for 'handle_response': %s", args)
713
+ }
714
+
715
+ // the first arg should always be a matcher.
716
+ // it doesn't really make sense to support status code without a matcher.
717
+ if len(args) > 0 {
718
+ if !strings.HasPrefix(args[0], matcherPrefix) {
719
+ return d.Errf("must use a named response matcher, starting with '@'")
720
+ }
721
+
722
+ foundMatcher, ok := h.responseMatchers[args[0]]
723
+ if !ok {
724
+ return d.Errf("no named response matcher defined with name '%s'", args[0][1:])
725
+ }
726
+ matcher = &foundMatcher
727
+ }
728
+
729
+ // a second arg should be a status code, in which case
730
+ // we skip parsing the block for routes
731
+ if len(args) == 2 {
732
+ _, err := strconv.Atoi(args[1])
733
+ if err != nil {
734
+ return d.Errf("bad integer value '%s': %v", args[1], err)
735
+ }
736
+
737
+ // make sure there's no block, cause it doesn't make sense
738
+ if d.NextBlock(1) {
739
+ return d.Errf("cannot define routes for 'handle_response' when changing the status code")
740
+ }
741
+
742
+ h.HandleResponse = append(
743
+ h.HandleResponse,
744
+ caddyhttp.ResponseHandler{
745
+ Match: matcher,
746
+ StatusCode: caddyhttp.WeakString(args[1]),
747
+ },
748
+ )
749
+ continue
750
+ }
751
+
752
+ // parse the block as routes
753
+ handler, err := httpcaddyfile.ParseSegmentAsSubroute(helper.WithDispenser(d.NewFromNextSegment()))
754
+ if err != nil {
755
+ return err
756
+ }
757
+ subroute, ok := handler.(*caddyhttp.Subroute)
758
+ if !ok {
759
+ return helper.Errf("segment was not parsed as a subroute")
760
+ }
761
+ h.HandleResponse = append(
762
+ h.HandleResponse,
763
+ caddyhttp.ResponseHandler{
764
+ Match: matcher,
765
+ Routes: subroute.Routes,
766
+ },
767
+ )
768
+ }
769
+
770
+ // move the handle_response entries without a matcher to the end.
771
+ // we can't use sort.SliceStable because it will reorder the rest of the
772
+ // entries which may be undesirable because we don't have a good
773
+ // heuristic to use for sorting.
774
+ withoutMatchers := []caddyhttp.ResponseHandler{}
775
+ withMatchers := []caddyhttp.ResponseHandler{}
776
+ for _, hr := range h.HandleResponse {
777
+ if hr.Match == nil {
778
+ withoutMatchers = append(withoutMatchers, hr)
779
+ } else {
780
+ withMatchers = append(withMatchers, hr)
781
+ }
782
+ }
783
+ h.HandleResponse = append(withMatchers, withoutMatchers...)
784
+
785
+ // clean up the bits we only needed for adapting
786
+ h.handleResponseSegments = nil
787
+ h.responseMatchers = nil
788
+
789
+ return nil
790
+}
791
+
792
// UnmarshalCaddyfile deserializes Caddyfile tokens into h.
793
//
794
// transport http {
1022
return nil
1023
}
1024
1025
+const matcherPrefix = "@"
1026
+
1027
// Interface guards
1028
var (
1029
_ caddyfile.Unmarshaler = (*Handler)(nil)