Browse Source
			
			
			
			
				
		* inital map implementation * resolve the value during middleware execution * use regex instead * pr feedback * renamed mmap to maphandler * refactored GetString implementation * fixed mispelling * additional feedbackmaster
							committed by
							
								 GitHub
								GitHub
							
						
					
				
				 6 changed files with 328 additions and 0 deletions
			
			
		| @ -0,0 +1,143 @@ | |||||
|  | package integration | ||||
|  | 
 | ||||
|  | import ( | ||||
|  | 	"bytes" | ||||
|  | 	"testing" | ||||
|  | 
 | ||||
|  | 	"github.com/caddyserver/caddy/v2/caddytest" | ||||
|  | ) | ||||
|  | 
 | ||||
|  | func TestMap(t *testing.T) { | ||||
|  | 
 | ||||
|  | 	// arrange
 | ||||
|  | 	tester := caddytest.NewTester(t) | ||||
|  | 	tester.InitServer(`{ | ||||
|  | 		http_port     9080 | ||||
|  | 		https_port    9443 | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	localhost:9080 { | ||||
|  | 
 | ||||
|  | 		map http.request.method dest-name { | ||||
|  | 			default unknown | ||||
|  | 			G.T get-called | ||||
|  | 			POST post-called | ||||
|  | 		} | ||||
|  | 
 | ||||
|  | 		respond /version 200 { | ||||
|  | 			body "hello from localhost {dest-name}" | ||||
|  | 		}	 | ||||
|  | 	} | ||||
|  | 	`, "caddyfile") | ||||
|  | 
 | ||||
|  | 	// act and assert
 | ||||
|  | 	tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") | ||||
|  | 	tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called") | ||||
|  | } | ||||
|  | 
 | ||||
|  | func TestMapRespondWithDefault(t *testing.T) { | ||||
|  | 
 | ||||
|  | 	// arrange
 | ||||
|  | 	tester := caddytest.NewTester(t) | ||||
|  | 	tester.InitServer(`{ | ||||
|  | 		http_port     9080 | ||||
|  | 		https_port    9443 | ||||
|  | 		} | ||||
|  | 		 | ||||
|  | 		localhost:9080 { | ||||
|  | 	 | ||||
|  | 			map http.request.method dest-name { | ||||
|  | 				default unknown | ||||
|  | 				GET get-called | ||||
|  | 			} | ||||
|  | 		 | ||||
|  | 			respond /version 200 { | ||||
|  | 				body "hello from localhost {dest-name}" | ||||
|  | 			}	 | ||||
|  | 		} | ||||
|  | 	`, "caddyfile") | ||||
|  | 
 | ||||
|  | 	// act and assert
 | ||||
|  | 	tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") | ||||
|  | 	tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown") | ||||
|  | } | ||||
|  | 
 | ||||
|  | func TestMapAsJson(t *testing.T) { | ||||
|  | 
 | ||||
|  | 	// arrange
 | ||||
|  | 	tester := caddytest.NewTester(t) | ||||
|  | 	tester.InitServer(`{ | ||||
|  | 		"apps": { | ||||
|  | 			"http": { | ||||
|  | 			"http_port": 9080, | ||||
|  | 			"https_port": 9443, | ||||
|  | 			"servers": { | ||||
|  | 				"srv0": { | ||||
|  | 				"listen": [ | ||||
|  | 					":9080" | ||||
|  | 				], | ||||
|  | 				"routes": [ | ||||
|  | 					{ | ||||
|  | 					"handle": [ | ||||
|  | 						{ | ||||
|  | 						"handler": "subroute", | ||||
|  | 						"routes": [ | ||||
|  | 							{ | ||||
|  | 							"handle": [ | ||||
|  | 								{ | ||||
|  | 								"handler": "map", | ||||
|  | 								"source": "http.request.method", | ||||
|  | 								"destination": "dest-name", | ||||
|  | 								"default": "unknown", | ||||
|  | 								"items": [ | ||||
|  | 									{ | ||||
|  | 									"expression": "GET", | ||||
|  | 									"value": "get-called" | ||||
|  | 									}, | ||||
|  | 									{ | ||||
|  | 									"expression": "POST", | ||||
|  | 									"value": "post-called" | ||||
|  | 									} | ||||
|  | 								] | ||||
|  | 								} | ||||
|  | 							] | ||||
|  | 							}, | ||||
|  | 							{ | ||||
|  | 							"handle": [ | ||||
|  | 								{ | ||||
|  | 								"body": "hello from localhost {dest-name}", | ||||
|  | 								"handler": "static_response", | ||||
|  | 								"status_code": 200 | ||||
|  | 								} | ||||
|  | 							], | ||||
|  | 							"match": [ | ||||
|  | 								{ | ||||
|  | 								"path": [ | ||||
|  | 									"/version" | ||||
|  | 								] | ||||
|  | 								} | ||||
|  | 							] | ||||
|  | 							} | ||||
|  | 						] | ||||
|  | 						} | ||||
|  | 					], | ||||
|  | 					"match": [ | ||||
|  | 						{ | ||||
|  | 						"host": [ | ||||
|  | 							"localhost" | ||||
|  | 						] | ||||
|  | 						} | ||||
|  | 					], | ||||
|  | 					"terminal": true | ||||
|  | 					} | ||||
|  | 				] | ||||
|  | 				} | ||||
|  | 			} | ||||
|  | 			} | ||||
|  | 		} | ||||
|  | 		} | ||||
|  | 		`, "json") | ||||
|  | 
 | ||||
|  | 	tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called") | ||||
|  | 	tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called") | ||||
|  | } | ||||
| @ -0,0 +1,71 @@ | |||||
|  | // Copyright 2015 Matthew Holt and The Caddy Authors
 | ||||
|  | //
 | ||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
|  | // you may not use this file except in compliance with the License.
 | ||||
|  | // You may obtain a copy of the License at
 | ||||
|  | //
 | ||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  | //
 | ||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
|  | // See the License for the specific language governing permissions and
 | ||||
|  | // limitations under the License.
 | ||||
|  | 
 | ||||
|  | package maphandler | ||||
|  | 
 | ||||
|  | import ( | ||||
|  | 	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" | ||||
|  | 	"github.com/caddyserver/caddy/v2/modules/caddyhttp" | ||||
|  | ) | ||||
|  | 
 | ||||
|  | func init() { | ||||
|  | 	httpcaddyfile.RegisterHandlerDirective("map", parseCaddyfile) | ||||
|  | } | ||||
|  | 
 | ||||
|  | // parseCaddyfile sets up the handler for a map from Caddyfile tokens. Syntax:
 | ||||
|  | //
 | ||||
|  | //     map <source> <dest> {
 | ||||
|  | //         [default <default>] - used if not match is found
 | ||||
|  | //         [<regexp> <replacement>] - regular expression to match against the source find and the matching replacement value
 | ||||
|  | //         ...
 | ||||
|  | //     }
 | ||||
|  | //
 | ||||
|  | // The map takes a source variable and maps it into the dest variable. The mapping process
 | ||||
|  | // will check the source variable for the first successful match against a list of regular expressions.
 | ||||
|  | // If a successful match is found the dest variable will contain the replacement value.
 | ||||
|  | // If no successful match is found and the default is specified then the dest will contain the default value.
 | ||||
|  | //
 | ||||
|  | func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { | ||||
|  | 	m := new(Handler) | ||||
|  | 
 | ||||
|  | 	for h.Next() { | ||||
|  | 		// first see if source and dest are configured
 | ||||
|  | 		if h.NextArg() { | ||||
|  | 			m.Source = h.Val() | ||||
|  | 			if h.NextArg() { | ||||
|  | 				m.Destination = h.Val() | ||||
|  | 			} | ||||
|  | 		} | ||||
|  | 
 | ||||
|  | 		// load the rules
 | ||||
|  | 		for h.NextBlock(0) { | ||||
|  | 			expression := h.Val() | ||||
|  | 			if expression == "default" { | ||||
|  | 				args := h.RemainingArgs() | ||||
|  | 				if len(args) != 1 { | ||||
|  | 					return m, h.ArgErr() | ||||
|  | 				} | ||||
|  | 				m.Default = args[0] | ||||
|  | 			} else { | ||||
|  | 				args := h.RemainingArgs() | ||||
|  | 				if len(args) != 1 { | ||||
|  | 					return m, h.ArgErr() | ||||
|  | 				} | ||||
|  | 				m.Items = append(m.Items, Item{Expression: expression, Value: args[0]}) | ||||
|  | 			} | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 
 | ||||
|  | 	return m, nil | ||||
|  | } | ||||
| @ -0,0 +1,105 @@ | |||||
|  | // Copyright 2015 Matthew Holt and The Caddy Authors
 | ||||
|  | //
 | ||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
|  | // you may not use this file except in compliance with the License.
 | ||||
|  | // You may obtain a copy of the License at
 | ||||
|  | //
 | ||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  | //
 | ||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
|  | // See the License for the specific language governing permissions and
 | ||||
|  | // limitations under the License.
 | ||||
|  | 
 | ||||
|  | package maphandler | ||||
|  | 
 | ||||
|  | import ( | ||||
|  | 	"net/http" | ||||
|  | 	"regexp" | ||||
|  | 
 | ||||
|  | 	"github.com/caddyserver/caddy/v2" | ||||
|  | 	"github.com/caddyserver/caddy/v2/modules/caddyhttp" | ||||
|  | ) | ||||
|  | 
 | ||||
|  | func init() { | ||||
|  | 	caddy.RegisterModule(Handler{}) | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Handler is a middleware that maps a source placeholder to a destination
 | ||||
|  | // placeholder.
 | ||||
|  | //
 | ||||
|  | // The mapping process happens early in the request handling lifecycle so that
 | ||||
|  | // the Destination placeholder is calculated and available for substitution.
 | ||||
|  | // The Items array contains pairs of regex expressions and values, the
 | ||||
|  | // Source is matched against the expression, if they match then the destination
 | ||||
|  | // placeholder is set to the value.
 | ||||
|  | //
 | ||||
|  | // The Default is optional, if no Item expression is matched then the value of
 | ||||
|  | // the Default will be used.
 | ||||
|  | //
 | ||||
|  | type Handler struct { | ||||
|  | 	// Source is a placeholder
 | ||||
|  | 	Source string `json:"source,omitempty"` | ||||
|  | 	// Destination is a new placeholder
 | ||||
|  | 	Destination string `json:"destination,omitempty"` | ||||
|  | 	// Default is an optional value to use if no other was found
 | ||||
|  | 	Default string `json:"default,omitempty"` | ||||
|  | 	// Items is an array of regex expressions and values
 | ||||
|  | 	Items []Item `json:"items,omitempty"` | ||||
|  | } | ||||
|  | 
 | ||||
|  | // CaddyModule returns the Caddy module information.
 | ||||
|  | func (Handler) CaddyModule() caddy.ModuleInfo { | ||||
|  | 	return caddy.ModuleInfo{ | ||||
|  | 		ID:  "http.handlers.map", | ||||
|  | 		New: func() caddy.Module { return new(Handler) }, | ||||
|  | 	} | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Provision will compile all regular expressions
 | ||||
|  | func (h *Handler) Provision(_ caddy.Context) error { | ||||
|  | 	for i := 0; i < len(h.Items); i++ { | ||||
|  | 		h.Items[i].compiled = regexp.MustCompile(h.Items[i].Expression) | ||||
|  | 	} | ||||
|  | 	return nil | ||||
|  | } | ||||
|  | 
 | ||||
|  | func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { | ||||
|  | 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) | ||||
|  | 
 | ||||
|  | 	// get the source value, if the source value was not found do no
 | ||||
|  | 	// replacement.
 | ||||
|  | 	val, ok := repl.GetString(h.Source) | ||||
|  | 	if ok { | ||||
|  | 		found := false | ||||
|  | 		for i := 0; i < len(h.Items); i++ { | ||||
|  | 			if h.Items[i].compiled.MatchString(val) { | ||||
|  | 				found = true | ||||
|  | 				repl.Set(h.Destination, h.Items[i].Value) | ||||
|  | 				break | ||||
|  | 			} | ||||
|  | 		} | ||||
|  | 
 | ||||
|  | 		if !found && h.Default != "" { | ||||
|  | 			repl.Set(h.Destination, h.Default) | ||||
|  | 		} | ||||
|  | 	} | ||||
|  | 	return next.ServeHTTP(w, r) | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Item defines each entry in the map
 | ||||
|  | type Item struct { | ||||
|  | 	// Expression is the regular expression searched for
 | ||||
|  | 	Expression string `json:"expression,omitempty"` | ||||
|  | 	// Value to use once the expression has been found
 | ||||
|  | 	Value string `json:"value,omitempty"` | ||||
|  | 	// compiled expression, internal use
 | ||||
|  | 	compiled *regexp.Regexp | ||||
|  | } | ||||
|  | 
 | ||||
|  | // Interface guards
 | ||||
|  | var ( | ||||
|  | 	_ caddy.Provisioner           = (*Handler)(nil) | ||||
|  | 	_ caddyhttp.MiddlewareHandler = (*Handler)(nil) | ||||
|  | ) | ||||
					Loading…
					
					
				
		Reference in new issue