@marcel91200 / caddy Mirror · read-only / commits / 0cf592fa
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

New 'php_fastcgi' directive for convenient PHP+FastCGI reverse proxy

Matthew Holt committed Sep 10, 2019 at 14:16 UTC 0cf592fa2e0d2fff8e9379095bbe17f7c8cbd4f2
2 files changed +146 -1
caddyconfig/httpcaddyfile/directives.go
+1
@@ -34,6 +34,7 @@ var defaultDirectiveOrder = []string{
34 "templates",
35 "redir",
36 "static_response", // TODO: "reply" or "respond"?
37 + "php_fastcgi",
38 "reverse_proxy",
39 "file_server",
40 }
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+145 -1
@@ -14,7 +14,21 @@
14
15 package fastcgi
16
17 -import "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
17 +import (
18 + "encoding/json"
19 +
20 + "github.com/caddyserver/caddy/v2/caddyconfig"
21 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
22 + "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
23 + "github.com/caddyserver/caddy/v2/modules/caddyhttp"
24 + "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
25 + "github.com/caddyserver/caddy/v2/modules/caddyhttp/reverseproxy"
26 + "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
27 +)
28 +
29 +func init() {
30 + httpcaddyfile.RegisterDirective("php_fastcgi", parsePHPFastCGI)
31 +}
32
33 // UnmarshalCaddyfile deserializes Caddyfile tokens into h.
34 //
@@ -55,3 +69,133 @@ func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
69 }
70 return nil
71 }
72 +
73 +// parsePHPFastCGI parses the php_fastcgi directive, which has the same syntax
74 +// as the reverse_proxy directive (in fact, the reverse_proxy's directive
75 +// Unmarshaler is invoked by this function) but the resulting proxy is specially
76 +// configured for most™️ PHP apps over FastCGI. A line such as this:
77 +//
78 +// php_fastcgi localhost:7777
79 +//
80 +// is equivalent to:
81 +//
82 +// matcher indexFiles {
83 +// file {
84 +// try_files {path} index.php
85 +// }
86 +// }
87 +// rewrite match:indexFiles {http.matchers.file.relative}
88 +//
89 +// matcher phpFiles {
90 +// path *.php
91 +// }
92 +// reverse_proxy match:phpFiles localhost:7777 {
93 +// transport fastcgi {
94 +// split .php
95 +// }
96 +// }
97 +//
98 +// Thus, this directive produces multiple routes, each with a different
99 +// matcher because multiple consecutive routes are necessary to support
100 +// the common PHP use case. If this "common" config is not compatible
101 +// with a user's PHP requirements, they can use the manual approach as
102 +// above to configure it precisely as they need.
103 +//
104 +// If a matcher is specified by the user, for example:
105 +//
106 +// php_fastcgi /subpath localhost:7777
107 +//
108 +// then the resulting routes are wrapped in a subroute that uses the
109 +// user's matcher as a prerequisite to enter the subroute.
110 +func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
111 + if !h.Next() {
112 + return nil, h.ArgErr()
113 + }
114 +
115 + // route to rewrite to PHP index file
116 + rewriteMatcherSet := map[string]json.RawMessage{
117 + "file": h.JSON(fileserver.MatchFile{
118 + TryFiles: []string{"{http.request.uri.path}", "index.php"},
119 + }, nil),
120 + }
121 + rewriteHandler := rewrite.Rewrite{
122 + URI: "{http.matchers.file.relative}{http.request.uri.query_string}",
123 + Rehandle: true,
124 + }
125 + rewriteRoute := caddyhttp.Route{
126 + MatcherSetsRaw: []map[string]json.RawMessage{rewriteMatcherSet},
127 + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)},
128 + }
129 +
130 + // route to actually reverse proxy requests to PHP files;
131 + // match only requests that are for PHP files
132 + rpMatcherSet := map[string]json.RawMessage{
133 + "path": h.JSON([]string{"*.php"}, nil),
134 + }
135 +
136 + // if the user specified a matcher token, use that
137 + // matcher in a route that wraps both of our routes;
138 + // either way, strip the matcher token and pass
139 + // the remaining tokens to the unmarshaler so that
140 + // we can gain the rest of the reverse_proxy syntax
141 + userMatcherSet, hasUserMatcher, err := h.MatcherToken()
142 + if err != nil {
143 + return nil, err
144 + }
145 + if hasUserMatcher {
146 + h.Dispenser.Delete() // strip matcher token
147 + }
148 + h.Dispenser.Reset() // pretend this lookahead never happened
149 +
150 + // set up the transport for FastCGI, and specifically PHP
151 + fcgiTransport := Transport{SplitPath: ".php"}
152 +
153 + // create the reverse proxy handler which uses our FastCGI transport
154 + rpHandler := &reverseproxy.Handler{
155 + TransportRaw: caddyconfig.JSONModuleObject(fcgiTransport, "protocol", "fastcgi", nil),
156 + }
157 +
158 + // the rest of the config is specified by the user
159 + // using the reverse_proxy directive syntax
160 + err = rpHandler.UnmarshalCaddyfile(h.Dispenser)
161 + if err != nil {
162 + return nil, err
163 + }
164 +
165 + // create the final reverse proxy route which is
166 + // conditional on matching PHP files
167 + rpRoute := caddyhttp.Route{
168 + MatcherSetsRaw: []map[string]json.RawMessage{rpMatcherSet},
169 + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rpHandler, "handler", "reverse_proxy", nil)},
170 + }
171 +
172 + // the user's matcher is a prerequisite for ours, so
173 + // wrap ours in a subroute and return that
174 + if hasUserMatcher {
175 + subroute := caddyhttp.Subroute{
176 + Routes: caddyhttp.RouteList{rewriteRoute, rpRoute},
177 + }
178 + return []httpcaddyfile.ConfigValue{
179 + {
180 + Class: "route",
181 + Value: caddyhttp.Route{
182 + MatcherSetsRaw: []map[string]json.RawMessage{userMatcherSet},
183 + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)},
184 + },
185 + },
186 + }, nil
187 + }
188 +
189 + // if the user did not specify a matcher, then
190 + // we can just use our own matchers
191 + return []httpcaddyfile.ConfigValue{
192 + {
193 + Class: "route",
194 + Value: rewriteRoute,
195 + },
196 + {
197 + Class: "route",
198 + Value: rpRoute,
199 + },
200 + }, nil
201 +}