Begin implementing HTTP replacer and static responder

This commit is contained in:
Matthew Holt 2019-05-04 13:21:20 -06:00
parent 1136e2cfee
commit 2eb3593327
6 changed files with 161 additions and 30 deletions

View file

@ -9,7 +9,7 @@ func BenchmarkLoad(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
r := strings.NewReader(`{ r := strings.NewReader(`{
"testval": "Yippee!", "testval": "Yippee!",
"modules": { "apps": {
"http": { "http": {
"servers": { "servers": {
"myserver": { "myserver": {

View file

@ -1,28 +1,18 @@
package main package main
import ( import (
"log" caddycmd "bitbucket.org/lightcodelabs/caddy2/cmd"
_ "net/http/pprof"
"bitbucket.org/lightcodelabs/caddy2"
// this is where modules get plugged in // this is where modules get plugged in
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/reverseproxy"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticresp"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
_ "bitbucket.org/lightcodelabs/dynamicconfig"
_ "bitbucket.org/lightcodelabs/proxy"
) )
func main() { func main() {
err := caddy2.StartAdmin("127.0.0.1:1234") caddycmd.Main()
if err != nil {
log.Fatal(err)
}
defer caddy2.StopAdmin()
select {}
} }

View file

@ -175,6 +175,7 @@ type httpServerConfig struct {
Errors httpErrorConfig `json:"errors"` Errors httpErrorConfig `json:"errors"`
TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"` TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
DisableAutoHTTPS bool `json:"disable_auto_https"` DisableAutoHTTPS bool `json:"disable_auto_https"`
DisableAutoHTTPSRedir bool `json:"disable_auto_https_redir"`
tlsApp *caddytls.TLS tlsApp *caddytls.TLS
} }
@ -190,6 +191,12 @@ func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
// set up the replacer
repl := &Replacer{req: r, resp: w, custom: make(map[string]string)}
ctx := context.WithValue(r.Context(), ReplacerCtxKey, repl)
r = r.WithContext(ctx)
// build and execute the main middleware chain
stack := s.Routes.buildMiddlewareChain(w, r) stack := s.Routes.buildMiddlewareChain(w, r)
err := executeMiddlewareChain(w, r, stack) err := executeMiddlewareChain(w, r, stack)
if err != nil { if err != nil {
@ -329,5 +336,7 @@ func (mrw middlewareResponseWriter) Write(b []byte) (int, error) {
return mrw.ResponseWriterWrapper.Write(b) return mrw.ResponseWriterWrapper.Write(b)
} }
const ReplacerCtxKey caddy2.CtxKey = "replacer"
// Interface guards // Interface guards
var _ HTTPInterfaces = middlewareResponseWriter{} var _ HTTPInterfaces = middlewareResponseWriter{}

View file

@ -139,11 +139,11 @@ func (m matchHeader) Match(r *http.Request) bool {
// Interface guards // Interface guards
var ( var (
_ RouteMatcher = matchHost{} _ RouteMatcher = (*matchHost)(nil)
_ RouteMatcher = matchPath{} _ RouteMatcher = (*matchPath)(nil)
_ RouteMatcher = matchMethod{} _ RouteMatcher = (*matchMethod)(nil)
_ RouteMatcher = matchQuery{} _ RouteMatcher = (*matchQuery)(nil)
_ RouteMatcher = matchHeader{} _ RouteMatcher = (*matchHeader)(nil)
_ RouteMatcher = new(matchProtocol) _ RouteMatcher = (*matchProtocol)(nil)
_ RouteMatcher = new(matchScript) _ RouteMatcher = (*matchScript)(nil)
) )

View file

@ -0,0 +1,75 @@
package caddyhttp
import (
"net/http"
"strings"
)
type Replacer struct {
req *http.Request
resp http.ResponseWriter
custom map[string]string
}
// Map sets a custom variable mapping to a value.
func (r *Replacer) Map(variable, value string) {
r.custom[variable] = value
}
// Replace replaces placeholders in input with the value. If
// the value is empty string, the placeholder is substituted
// with the value empty.
func (r *Replacer) Replace(input, empty string) string {
if !strings.Contains(input, phOpen) {
return input
}
input = r.replaceAll(input, empty, r.defaults())
input = r.replaceAll(input, empty, r.custom)
return input
}
func (r *Replacer) replaceAll(input, empty string, mapping map[string]string) string {
for key, val := range mapping {
if val == "" {
val = empty
}
input = strings.ReplaceAll(input, phOpen+key+phClose, val)
}
return input
}
func (r *Replacer) defaults() map[string]string {
m := map[string]string{
"host": r.req.Host,
"method": r.req.Method,
"scheme": func() string {
if r.req.TLS != nil {
return "https"
}
return "http"
}(),
"uri": r.req.URL.RequestURI(),
}
for field, vals := range r.req.Header {
m[">"+strings.ToLower(field)] = strings.Join(vals, ",")
}
for field, vals := range r.resp.Header() {
m["<"+strings.ToLower(field)] = strings.Join(vals, ",")
}
for _, cookie := range r.req.Cookies() {
m["~"+cookie.Name] = cookie.Value
}
for param, vals := range r.req.URL.Query() {
m["?"+param] = strings.Join(vals, ",")
}
return m
}
const phOpen, phClose = "{", "}"

View file

@ -0,0 +1,57 @@
package staticresp
import (
"fmt"
"net/http"
"bitbucket.org/lightcodelabs/caddy2"
"bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
)
func init() {
caddy2.RegisterModule(caddy2.Module{
Name: "http.responders.static",
New: func() (interface{}, error) { return new(Static), nil },
})
}
// Static implements a simple responder for static responses.
type Static struct {
StatusCode int `json:"status_code"`
Headers map[string][]string `json:"headers"`
Body string `json:"body"`
Close bool `json:"close"`
}
func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
repl := r.Context().Value(caddyhttp.ReplacerCtxKey).(*caddyhttp.Replacer)
// close the connection
r.Close = s.Close
// set all headers, with replacements
for field, vals := range s.Headers {
field = repl.Replace(field, "")
for i := range vals {
vals[i] = repl.Replace(vals[i], "")
}
w.Header()[field] = vals
}
// write the headers with a status code
statusCode := s.StatusCode
if statusCode == 0 {
statusCode = http.StatusOK
}
w.WriteHeader(statusCode)
// write the response body, with replacements
if s.Body != "" {
fmt.Fprint(w, repl.Replace(s.Body, ""))
}
return nil
}
// Interface guard
var _ caddyhttp.Handler = (*Static)(nil)