@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 224 lines 6.93 KB
Raw
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 fileserver
16
17 import (
18 "encoding/json"
19 "fmt"
20 "io"
21 "log"
22 "os"
23 "strconv"
24 "time"
25
26 "github.com/caddyserver/certmagic"
27 "github.com/spf13/cobra"
28 "go.uber.org/zap"
29
30 caddycmd "github.com/caddyserver/caddy/v2/cmd"
31
32 "github.com/caddyserver/caddy/v2"
33 "github.com/caddyserver/caddy/v2/caddyconfig"
34 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
35 "github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
36 caddytpl "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
37 )
38
39 func init() {
40 caddycmd.RegisterCommand(caddycmd.Command{
41 Name: "file-server",
42 Usage: "[--domain <example.com>] [--root <path>] [--listen <addr>] [--browse] [--reveal-symlinks] [--access-log] [--precompressed]",
43 Short: "Spins up a production-ready file server",
44 Long: `
45 A simple but production-ready file server. Useful for quick deployments,
46 demos, and development.
47
48 The listener's socket address can be customized with the --listen flag.
49
50 If a domain name is specified with --domain, the default listener address
51 will be changed to the HTTPS port and the server will use HTTPS. If using
52 a public domain, ensure A/AAAA records are properly configured before
53 using this option.
54
55 By default, Zstandard and Gzip compression are enabled. Use --no-compress
56 to disable compression.
57
58 If --browse is enabled, requests for folders without an index file will
59 respond with a file listing.`,
60 CobraFunc: func(cmd *cobra.Command) {
61 cmd.Flags().StringP("domain", "d", "", "Domain name at which to serve the files")
62 cmd.Flags().StringP("root", "r", "", "The path to the root of the site")
63 cmd.Flags().StringP("listen", "l", "", "The address to which to bind the listener")
64 cmd.Flags().BoolP("browse", "b", false, "Enable directory browsing")
65 cmd.Flags().BoolP("reveal-symlinks", "", false, "Show symlink paths when browse is enabled.")
66 cmd.Flags().BoolP("templates", "t", false, "Enable template rendering")
67 cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
68 cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
69 cmd.Flags().IntP("file-limit", "f", defaultDirEntryLimit, "Max directories to read")
70 cmd.Flags().BoolP("no-compress", "", false, "Disable Zstandard and Gzip compression")
71 cmd.Flags().StringSliceP("precompressed", "p", []string{}, "Specify precompression file extensions. Compression preference implied from flag order.")
72 cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdFileServer)
73 cmd.AddCommand(&cobra.Command{
74 Use: "export-template",
75 Short: "Exports the default file browser template",
76 Example: "caddy file-server export-template > browse.html",
77 RunE: func(cmd *cobra.Command, args []string) error {
78 _, err := io.WriteString(os.Stdout, BrowseTemplate)
79 return err
80 },
81 })
82 },
83 })
84 }
85
86 func cmdFileServer(fs caddycmd.Flags) (int, error) {
87 caddy.TrapSignals()
88
89 domain := fs.String("domain")
90 root := fs.String("root")
91 listen := fs.String("listen")
92 browse := fs.Bool("browse")
93 templates := fs.Bool("templates")
94 accessLog := fs.Bool("access-log")
95 fileLimit := fs.Int("file-limit")
96 debug := fs.Bool("debug")
97 revealSymlinks := fs.Bool("reveal-symlinks")
98 compress := !fs.Bool("no-compress")
99 precompressed, err := fs.GetStringSlice("precompressed")
100 if err != nil {
101 return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid precompressed flag: %v", err)
102 }
103 var handlers []json.RawMessage
104
105 if compress {
106 zstd, err := caddy.GetModule("http.encoders.zstd")
107 if err != nil {
108 return caddy.ExitCodeFailedStartup, err
109 }
110
111 gzip, err := caddy.GetModule("http.encoders.gzip")
112 if err != nil {
113 return caddy.ExitCodeFailedStartup, err
114 }
115
116 handlers = append(handlers, caddyconfig.JSONModuleObject(encode.Encode{
117 EncodingsRaw: caddy.ModuleMap{
118 "zstd": caddyconfig.JSON(zstd.New(), nil),
119 "gzip": caddyconfig.JSON(gzip.New(), nil),
120 },
121 Prefer: []string{"zstd", "gzip"},
122 }, "handler", "encode", nil))
123 }
124
125 if templates {
126 handler := caddytpl.Templates{FileRoot: root}
127 handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "templates", nil))
128 }
129
130 handler := FileServer{Root: root}
131
132 if len(precompressed) != 0 {
133 // logic mirrors modules/caddyhttp/fileserver/caddyfile.go case "precompressed"
134 var order []string
135 for _, compression := range precompressed {
136 modID := "http.precompressed." + compression
137 mod, err := caddy.GetModule(modID)
138 if err != nil {
139 return caddy.ExitCodeFailedStartup, fmt.Errorf("getting module named '%s': %v", modID, err)
140 }
141 inst := mod.New()
142 precompress, ok := inst.(encode.Precompressed)
143 if !ok {
144 return caddy.ExitCodeFailedStartup, fmt.Errorf("module %s is not a precompressor; is %T", modID, inst)
145 }
146 if handler.PrecompressedRaw == nil {
147 handler.PrecompressedRaw = make(caddy.ModuleMap)
148 }
149 handler.PrecompressedRaw[compression] = caddyconfig.JSON(precompress, nil)
150 order = append(order, compression)
151 }
152 handler.PrecompressedOrder = order
153 }
154
155 if browse {
156 handler.Browse = &Browse{RevealSymlinks: revealSymlinks, FileLimit: fileLimit}
157 }
158
159 handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))
160
161 route := caddyhttp.Route{HandlersRaw: handlers}
162
163 if domain != "" {
164 route.MatcherSetsRaw = []caddy.ModuleMap{
165 {
166 "host": caddyconfig.JSON(caddyhttp.MatchHost{domain}, nil),
167 },
168 }
169 }
170
171 server := &caddyhttp.Server{
172 ReadHeaderTimeout: caddy.Duration(10 * time.Second),
173 IdleTimeout: caddy.Duration(30 * time.Second),
174 MaxHeaderBytes: 1024 * 10,
175 Routes: caddyhttp.RouteList{route},
176 }
177 if listen == "" {
178 if domain == "" {
179 listen = ":80"
180 } else {
181 listen = ":" + strconv.Itoa(certmagic.HTTPSPort)
182 }
183 }
184 server.Listen = []string{listen}
185 if accessLog {
186 server.Logs = &caddyhttp.ServerLogConfig{}
187 }
188
189 httpApp := caddyhttp.App{
190 Servers: map[string]*caddyhttp.Server{"static": server},
191 }
192
193 var false bool
194 cfg := &caddy.Config{
195 Admin: &caddy.AdminConfig{
196 Disabled: true,
197 Config: &caddy.ConfigSettings{
198 Persist: &false,
199 },
200 },
201 AppsRaw: caddy.ModuleMap{
202 "http": caddyconfig.JSON(httpApp, nil),
203 },
204 }
205
206 if debug {
207 cfg.Logging = &caddy.Logging{
208 Logs: map[string]*caddy.CustomLog{
209 "default": {
210 BaseLog: caddy.BaseLog{Level: zap.DebugLevel.CapitalString()},
211 },
212 },
213 }
214 }
215
216 err = caddy.Run(cfg)
217 if err != nil {
218 return caddy.ExitCodeFailedStartup, err
219 }
220
221 log.Printf("Caddy serving static files on %s", listen)
222
223 select {}
224 }