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

caddyconfig: register adapters as Caddy modules (#3132)

* admin: Refactor /load endpoint out of caddy package This eliminates the caddy package's dependency on the caddyconfig package, which helps prevent import cycles. * v2: adapter: register config adapters as Caddy modules * v2: adapter: simplify adapter registration as adapters and modules * v2: adapter: let RegisterAdapter be in charge of registering adapters as modules * v2: adapter: remove underscrores placeholders * v2: adapter: explicitly ignore the error of writing response of writing warnings back to client * Implicitly wrap config adapters as modules Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>

Mohammed Al Sahaf committed Mar 22, 2020 at 01:49 UTC 37093befd59107316d422fcd3cc011b2481cd2cf
3 files changed +173 -84
admin.go
-83
@@ -21,7 +21,6 @@ import (
21 "expvar"
22 "fmt"
23 "io"
24 - "mime"
24 "net/http"
25 "net/http/pprof"
26 "net/url"
@@ -33,7 +32,6 @@ import (
32 "sync"
33 "time"
34
36 - "github.com/caddyserver/caddy/v2/caddyconfig"
35 "go.uber.org/zap"
36 )
37
@@ -115,7 +113,6 @@ func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
113 }
114
115 // register standard config control endpoints
118 - addRoute("/load", AdminHandlerFunc(handleLoad))
116 addRoute("/"+rawConfigKey+"/", AdminHandlerFunc(handleConfig))
117 addRoute("/id/", AdminHandlerFunc(handleConfigID))
118 addRoute("/stop", AdminHandlerFunc(handleStop))
@@ -407,86 +404,6 @@ func (h adminHandler) originAllowed(origin string) bool {
404 return false
405 }
406
410 -func handleLoad(w http.ResponseWriter, r *http.Request) error {
411 - if r.Method != http.MethodPost {
412 - return APIError{
413 - Code: http.StatusMethodNotAllowed,
414 - Err: fmt.Errorf("method not allowed"),
415 - }
416 - }
417 -
418 - buf := bufPool.Get().(*bytes.Buffer)
419 - buf.Reset()
420 - defer bufPool.Put(buf)
421 -
422 - _, err := io.Copy(buf, r.Body)
423 - if err != nil {
424 - return APIError{
425 - Code: http.StatusBadRequest,
426 - Err: fmt.Errorf("reading request body: %v", err),
427 - }
428 - }
429 - body := buf.Bytes()
430 -
431 - // if the config is formatted other than Caddy's native
432 - // JSON, we need to adapt it before loading it
433 - if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" {
434 - ct, _, err := mime.ParseMediaType(ctHeader)
435 - if err != nil {
436 - return APIError{
437 - Code: http.StatusBadRequest,
438 - Err: fmt.Errorf("invalid Content-Type: %v", err),
439 - }
440 - }
441 - if !strings.HasSuffix(ct, "/json") {
442 - slashIdx := strings.Index(ct, "/")
443 - if slashIdx < 0 {
444 - return APIError{
445 - Code: http.StatusBadRequest,
446 - Err: fmt.Errorf("malformed Content-Type"),
447 - }
448 - }
449 - adapterName := ct[slashIdx+1:]
450 - cfgAdapter := caddyconfig.GetAdapter(adapterName)
451 - if cfgAdapter == nil {
452 - return APIError{
453 - Code: http.StatusBadRequest,
454 - Err: fmt.Errorf("unrecognized config adapter '%s'", adapterName),
455 - }
456 - }
457 - result, warnings, err := cfgAdapter.Adapt(body, nil)
458 - if err != nil {
459 - return APIError{
460 - Code: http.StatusBadRequest,
461 - Err: fmt.Errorf("adapting config using %s adapter: %v", adapterName, err),
462 - }
463 - }
464 - if len(warnings) > 0 {
465 - respBody, err := json.Marshal(warnings)
466 - if err != nil {
467 - Log().Named("admin.api.load").Error(err.Error())
468 - }
469 - w.Write(respBody)
470 - }
471 - body = result
472 - }
473 - }
474 -
475 - forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
476 -
477 - err = Load(body, forceReload)
478 - if err != nil {
479 - return APIError{
480 - Code: http.StatusBadRequest,
481 - Err: fmt.Errorf("loading config: %v", err),
482 - }
483 - }
484 -
485 - Log().Named("admin.api").Info("load complete")
486 -
487 - return nil
488 -}
489 -
407 func handleConfig(w http.ResponseWriter, r *http.Request) error {
408 switch r.Method {
409 case http.MethodGet:
caddyconfig/configadapters.go
+20 -1
@@ -17,6 +17,8 @@ package caddyconfig
17 import (
18 "encoding/json"
19 "fmt"
20 +
21 + "github.com/caddyserver/caddy/v2"
22 )
23
24 // Adapter is a type which can adapt a configuration to Caddy JSON.
@@ -105,7 +107,7 @@ func RegisterAdapter(name string, adapter Adapter) error {
107 return fmt.Errorf("%s: already registered", name)
108 }
109 configAdapters[name] = adapter
108 - return nil
110 + return caddy.RegisterModule(adapterModule{name, adapter})
111 }
112
113 // GetAdapter returns the adapter with the given name,
@@ -114,4 +116,21 @@ func GetAdapter(name string) Adapter {
116 return configAdapters[name]
117 }
118
119 +// adapterModule is a wrapper type that can turn any config
120 +// adapter into a Caddy module, which has the benefit of being
121 +// counted with other modules, even though they do not
122 +// technically extend the Caddy configuration structure.
123 +// See caddyserver/caddy#3132.
124 +type adapterModule struct {
125 + name string
126 + Adapter
127 +}
128 +
129 +func (am adapterModule) CaddyModule() caddy.ModuleInfo {
130 + return caddy.ModuleInfo{
131 + ID: caddy.ModuleID("caddy.adapters." + am.name),
132 + New: func() caddy.Module { return am },
133 + }
134 +}
135 +
136 var configAdapters = make(map[string]Adapter)
caddyconfig/load.go new
+153
@@ -0,0 +1,153 @@
1 +// Copyright 2015 Matthew Holt and The Caddy Authors
2 +//
3 +// Licensed under the Apache License, Version 2.0 (the "License");
4 +// you may not use this file except in compliance with the License.
5 +// You may obtain a copy of the License at
6 +//
7 +// http://www.apache.org/licenses/LICENSE-2.0
8 +//
9 +// Unless required by applicable law or agreed to in writing, software
10 +// distributed under the License is distributed on an "AS IS" BASIS,
11 +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +// See the License for the specific language governing permissions and
13 +// limitations under the License.
14 +
15 +package caddyconfig
16 +
17 +import (
18 + "bytes"
19 + "encoding/json"
20 + "fmt"
21 + "io"
22 + "mime"
23 + "net/http"
24 + "strings"
25 + "sync"
26 +
27 + "github.com/caddyserver/caddy/v2"
28 +)
29 +
30 +func init() {
31 + caddy.RegisterModule(adminLoad{})
32 +}
33 +
34 +// adminLoad is a module that provides the /load endpoint
35 +// for the Caddy admin API. The only reason it's not baked
36 +// into the caddy package directly is because of the import
37 +// of the caddyconfig package for its GetAdapter function.
38 +// If the caddy package depends on the caddyconfig package,
39 +// then the caddyconfig package will not be able to import
40 +// the caddy package, and it can more easily cause backward
41 +// edges in the dependency tree (i.e. import cycle).
42 +// Fortunately, the admin API has first-class support for
43 +// adding endpoints from modules.
44 +type adminLoad struct{}
45 +
46 +// CaddyModule returns the Caddy module information.
47 +func (adminLoad) CaddyModule() caddy.ModuleInfo {
48 + return caddy.ModuleInfo{
49 + ID: "admin.api.load",
50 + New: func() caddy.Module { return new(adminLoad) },
51 + }
52 +}
53 +
54 +// Routes returns a route for the /load endpoint.
55 +func (al adminLoad) Routes() []caddy.AdminRoute {
56 + return []caddy.AdminRoute{
57 + {
58 + Pattern: "/load",
59 + Handler: caddy.AdminHandlerFunc(al.handleLoad),
60 + },
61 + }
62 +}
63 +
64 +// handleLoad replaces the entire current configuration with
65 +// a new one provided in the response body. It supports config
66 +// adapters through the use of the Content-Type header. A
67 +// config that is identical to the currently-running config
68 +// will be a no-op unless Cache-Control: must-revalidate is set.
69 +func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
70 + if r.Method != http.MethodPost {
71 + return caddy.APIError{
72 + Code: http.StatusMethodNotAllowed,
73 + Err: fmt.Errorf("method not allowed"),
74 + }
75 + }
76 +
77 + buf := bufPool.Get().(*bytes.Buffer)
78 + buf.Reset()
79 + defer bufPool.Put(buf)
80 +
81 + _, err := io.Copy(buf, r.Body)
82 + if err != nil {
83 + return caddy.APIError{
84 + Code: http.StatusBadRequest,
85 + Err: fmt.Errorf("reading request body: %v", err),
86 + }
87 + }
88 + body := buf.Bytes()
89 +
90 + // if the config is formatted other than Caddy's native
91 + // JSON, we need to adapt it before loading it
92 + if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" {
93 + ct, _, err := mime.ParseMediaType(ctHeader)
94 + if err != nil {
95 + return caddy.APIError{
96 + Code: http.StatusBadRequest,
97 + Err: fmt.Errorf("invalid Content-Type: %v", err),
98 + }
99 + }
100 + if !strings.HasSuffix(ct, "/json") {
101 + slashIdx := strings.Index(ct, "/")
102 + if slashIdx < 0 {
103 + return caddy.APIError{
104 + Code: http.StatusBadRequest,
105 + Err: fmt.Errorf("malformed Content-Type"),
106 + }
107 + }
108 + adapterName := ct[slashIdx+1:]
109 + cfgAdapter := GetAdapter(adapterName)
110 + if cfgAdapter == nil {
111 + return caddy.APIError{
112 + Code: http.StatusBadRequest,
113 + Err: fmt.Errorf("unrecognized config adapter '%s'", adapterName),
114 + }
115 + }
116 + result, warnings, err := cfgAdapter.Adapt(body, nil)
117 + if err != nil {
118 + return caddy.APIError{
119 + Code: http.StatusBadRequest,
120 + Err: fmt.Errorf("adapting config using %s adapter: %v", adapterName, err),
121 + }
122 + }
123 + if len(warnings) > 0 {
124 + respBody, err := json.Marshal(warnings)
125 + if err != nil {
126 + caddy.Log().Named("admin.api.load").Error(err.Error())
127 + }
128 + _, _ = w.Write(respBody)
129 + }
130 + body = result
131 + }
132 + }
133 +
134 + forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
135 +
136 + err = caddy.Load(body, forceReload)
137 + if err != nil {
138 + return caddy.APIError{
139 + Code: http.StatusBadRequest,
140 + Err: fmt.Errorf("loading config: %v", err),
141 + }
142 + }
143 +
144 + caddy.Log().Named("admin.api").Info("load complete")
145 +
146 + return nil
147 +}
148 +
149 +var bufPool = sync.Pool{
150 + New: func() interface{} {
151 + return new(bytes.Buffer)
152 + },
153 +}