ronantakizawa/codereview-bench huggingface dataset:
Repository: caddyserver/caddy
File: listen.go (Go)

Code before:
// 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.

//go:build !unix

package caddy

import (
	"context"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"go.uber.org/zap"
)

func reuseUnixSocket(network, addr string) (any, error) {
	return nil, nil
}

func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
	switch network {
	case "udp", "udp4", "udp6", "unixgram":
		sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
			pc, err := config.ListenPacket(ctx, network, address)
			if err != nil {
				return nil, err
			}
			return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
		})
		if err != nil {
			return nil, err
		}
		spc := sharedPc.(*sharedPacketConn)
		return &fakeClosePacketConn{spc: spc, UDPConn: spc.PacketConn.(*net.UDPConn)}, nil

	default:
		sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
			ln, err := config.Listen(ctx, network, address)
			if err != nil {
				return nil, err
			}
			return &sharedListener{Listener: ln, key: lnKey}, nil
		})
		if err != nil {
			return nil, err
		}
		return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil

Review comment (bug):
When network is `unixgram`, the resulting packet conn is `net.UnixConn`. Type assertion will fail as originally that code path only includes `net.UDPConn`.

Code after:
// 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.

//go:build !unix

package caddy

import (
	"context"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"go.uber.org/zap"
)

func reuseUnixSocket(network, addr string) (any, error) {
	return nil, nil
}

func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
	switch network {
	case "udp", "udp4", "udp6", "unixgram":
		sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
			pc, err := config.ListenPacket(ctx, network, address)
			if err != nil {
				return nil, err
			}
			return &sharedPacketConn{PacketConn: pc, key: lnKey}, nil
		})
		if err != nil {
			return nil, err
		}
		return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil

	default:
		sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
			ln, err := config.Listen(ctx, network, address)
			if err != nil {
				return nil, err
			}
			return &sharedListener{Listener: ln, key: lnKey}, nil
		})
		if err != nil {
			return nil, err
		}
		return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
	}
Repository: caddyserver/caddy
File: modules/logging/filters.go (Go)

Code before:
			case "delete":
				if !d.NextArg() {
					return d.ArgErr()
				}

				qfa.Type = deleteAction
				qfa.Parameter = d.Val()

			default:
				return d.Errf("unrecognized subdirective %s", d.Val())
			}

			m.Actions = append(m.Actions, qfa)
		}
	}
	return nil
}

// Filter filters the input field.
func (m QueryFilter) Filter(in zapcore.Field) zapcore.Field {
	if array, ok := in.Interface.(caddyhttp.LoggableStringArray); ok {
	newArray := slices.Clone(array)
		for i, s := range newArray {
			newArray[i] = m.processQueryString(s)
		}
		in.Interface = newArray	} else {
		in.String = m.processQueryString(in.String)
	}

	return in
}

func (m QueryFilter) processQueryString(s string) string {
	u, err := url.Parse(s)
	if err != nil {
		return s
	}

	q := u.Query()
	for _, a := range m.Actions {
		switch a.Type {
		case replaceAction:
			for i := range q[a.Parameter] {
				q[a.Parameter][i] = a.Value
			}

		case hashAction:
			for i := range q[a.Parameter] {
				q[a.Parameter][i] = hash(a.Value)
			}


Review comment (suggestion):
```suggestion
		newArray := make(caddyhttp.LoggableStringArray, len(array))
		for i, s := range array {
			newArray[i] = m.processQueryString(s)
		}
		in.Interface = newArray
	} else {
```

Code after:
				if !d.NextArg() {
					return d.ArgErr()
				}

				qfa.Type = deleteAction
				qfa.Parameter = d.Val()

			default:
				return d.Errf("unrecognized subdirective %s", d.Val())
			}

			m.Actions = append(m.Actions, qfa)
		}
	}
	return nil
}

// Filter filters the input field.
func (m QueryFilter) Filter(in zapcore.Field) zapcore.Field {
	if array, ok := in.Interface.(caddyhttp.LoggableStringArray); ok {
		newArray := make(caddyhttp.LoggableStringArray, len(array))
		for i, s := range array {
			newArray[i] = m.processQueryString(s)
		}
		in.Interface = newArray
	} else {
		in.String = m.processQueryString(in.String)
	}

	return in
}

func (m QueryFilter) processQueryString(s string) string {
	u, err := url.Parse(s)
	if err != nil {
		return s
	}

	q := u.Query()
	for _, a := range m.Actions {
		switch a.Type {
		case replaceAction:
			for i := range q[a.Parameter] {
				q[a.Parameter][i] = a.Value
			}

		case hashAction:
			for i := range q[a.Parameter] {
				q[a.Parameter][i] = hash(a.Value)
			}
Repository: caddyserver/caddy
File: caddyconfig/httpcaddyfile/httptype.go (Go)

Code before:

	gc := counter{new(int)}
	state := make(map[string]any)

	// copy the server blocks so we can
	// splice out the named route ones
	filtered := append([]serverBlock{}, serverBlocks...)
	index := -1

	for _, sb := range serverBlocks {
		index++
		if !sb.block.IsNamedRoute {
			continue
		}

		// splice out this block, because we know it's not a real server
		filtered = append(filtered[:index], filtered[index+1:]...)
		index--

		if len(sb.block.Segments) == 0 {
			continue
		}

		wholeSegment := caddyfile.Segment{}
		for _, segment := range sb.block.Segments {
			// Replace user-defined placeholder shorthands in extracted named routes
			replacer.ReplaceSegmentsWithFullPlaceholder(&segment)

			// zip up all the segments since ParseSegmentAsSubroute
			// was designed to take a directive+
			wholeSegment = append(wholeSegment, segment...)
		}

		h := Helper{
			Dispenser:    caddyfile.NewDispenser(wholeSegment),
			options:      options,
			warnings:     warnings,
			matcherDefs:  nil,
			parentBlock:  sb.block,
			groupCounter: gc,
			State:        state,
		}

		handler, err := ParseSegmentAsSubroute(h)
		if err != nil {
			return nil, err
		}
		subroute := handler.(*caddyhttp.Subroute)
		route := caddyhttp.Route{}

		if len(subroute.Routes) == 1 && len(subroute.Routes[0].MatcherSetsRaw) == 0 {

Review comment (refactor):
Just a consistency thing

```suggestion
			// replace user-defined placeholder shorthands in extracted named routes
```


Code after:

	gc := counter{new(int)}
	state := make(map[string]any)

	// copy the server blocks so we can
	// splice out the named route ones
	filtered := append([]serverBlock{}, serverBlocks...)
	index := -1

	for _, sb := range serverBlocks {
		index++
		if !sb.block.IsNamedRoute {
			continue
		}

		// splice out this block, because we know it's not a real server
		filtered = append(filtered[:index], filtered[index+1:]...)
		index--

		if len(sb.block.Segments) == 0 {
			continue
		}

		wholeSegment := caddyfile.Segment{}
		for i := range sb.block.Segments {
			// replace user-defined placeholder shorthands in extracted named routes
			replacer.ApplyToSegment(&sb.block.Segments[i])

			// zip up all the segments since ParseSegmentAsSubroute
			// was designed to take a directive+
			wholeSegment = append(wholeSegment, sb.block.Segments[i]...)
		}

		h := Helper{
			Dispenser:    caddyfile.NewDispenser(wholeSegment),
			options:      options,
			warnings:     warnings,
			matcherDefs:  nil,
			parentBlock:  sb.block,
			groupCounter: gc,
			State:        state,
		}

		handler, err := ParseSegmentAsSubroute(h)
		if err != nil {
			return nil, err
		}
		subroute := handler.(*caddyhttp.Subroute)
		route := caddyhttp.Route{}

		if len(subroute.Routes) == 1 && len(subroute.Routes[0].MatcherSetsRaw) == 0 {
Repository: caddyserver/caddy
File: modules/caddyhttp/reverseproxy/streaming.go (Go)

Code before:
}

func (m *maxLatencyWriter) delayedFlush() {
	m.mu.Lock()
	defer m.mu.Unlock()
	if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
		m.logger.Debug("delayed flush is not pending")
		return
	}
	//nolint:errcheck
	m.logger.Debug("delayed flush")
	m.flush()
	m.flushPending = false
}

func (m *maxLatencyWriter) stop() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.flushPending = false
	if m.t != nil {
		m.t.Stop()
	}
}

// switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks.
type switchProtocolCopier struct {
	user, backend io.ReadWriteCloser
}

func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
	_, err := io.Copy(c.user, c.backend)
	errc <- err
}

func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
	_, err := io.Copy(c.backend, c.user)
	errc <- err
}

var streamingBufPool = sync.Pool{
	New: func() any {
		// The Pool's New function should generally only return pointer
		// types, since a pointer can be put into the return interface
		// value without an allocation
		// - (from the package docs)
		b := make([]byte, defaultBufferSize)
		return &b
	},
}


Review comment (bug):
```suggestion
	m.logger.Debug("delayed flush")
	//nolint:errcheck
	m.flush()
```

Code after:
		return
	}
	if m.flushPending {
		m.logger.Debug("delayed flush already pending")
		return
	}
	if m.t == nil {
		m.t = time.AfterFunc(m.latency, m.delayedFlush)
	} else {
		m.t.Reset(m.latency)
	}
	m.logger.Debug("timer set for delayed flush", zap.Duration("duration", m.latency))
	m.flushPending = true
	return
}

func (m *maxLatencyWriter) delayedFlush() {
	m.mu.Lock()
	defer m.mu.Unlock()
	if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
		m.logger.Debug("delayed flush is not pending")
		return
	}
	m.logger.Debug("delayed flush")
	//nolint:errcheck
	m.flush()
	m.flushPending = false
}

func (m *maxLatencyWriter) stop() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.flushPending = false
	if m.t != nil {
		m.t.Stop()
	}
}

// switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks.
type switchProtocolCopier struct {
	user, backend io.ReadWriteCloser
}

func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
	_, err := io.Copy(c.user, c.backend)
	errc <- err
}

func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
	_, err := io.Copy(c.backend, c.user)
Repository: caddyserver/caddy
File: modules/caddyhttp/reverseproxy/streaming.go (Go)

Code before:
	logger       *zap.Logger
}

func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	n, err = m.dst.Write(p)
	m.logger.Debug("wrote bytes", zap.Int("n", n), zap.Error(err))
	if m.latency < 0 {
		//nolint:errcheck
		m.logger.Debug("flushing immediately")
		m.flush()
		return
	}
	if m.flushPending {
		m.logger.Debug("delayed flush already pending")
		return
	}
	if m.t == nil {
		m.t = time.AfterFunc(m.latency, m.delayedFlush)
	} else {
		m.t.Reset(m.latency)
	}
	m.logger.Debug("timer set for delayed flush", zap.Duration("duration", m.latency))
	m.flushPending = true
	return
}

func (m *maxLatencyWriter) delayedFlush() {
	m.mu.Lock()
	defer m.mu.Unlock()
	if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
		m.logger.Debug("delayed flush is not pending")
		return
	}
	//nolint:errcheck
	m.logger.Debug("delayed flush")
	m.flush()
	m.flushPending = false
}

func (m *maxLatencyWriter) stop() {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.flushPending = false
	if m.t != nil {
		m.t.Stop()
	}
}

// switchProtocolCopier exists so goroutines proxying data back and

Review comment (bug):
```suggestion
		m.logger.Debug("flushing immediately")
		//nolint:errcheck
		m.flush()
```

Code after:
// an optional function for graceful close.
type openConnection struct {
	conn          io.ReadWriteCloser
	gracefulClose func() error
}

type maxLatencyWriter struct {
	dst     io.Writer
	flush   func() error
	latency time.Duration // non-zero; negative means to flush immediately

	mu           sync.Mutex // protects t, flushPending, and dst.Flush
	t            *time.Timer
	flushPending bool
	logger       *zap.Logger
}

func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	n, err = m.dst.Write(p)
	m.logger.Debug("wrote bytes", zap.Int("n", n), zap.Error(err))
	if m.latency < 0 {
		m.logger.Debug("flushing immediately")
		//nolint:errcheck
		m.flush()
		return
	}
	if m.flushPending {
		m.logger.Debug("delayed flush already pending")
		return
	}
	if m.t == nil {
		m.t = time.AfterFunc(m.latency, m.delayedFlush)
	} else {
		m.t.Reset(m.latency)
	}
	m.logger.Debug("timer set for delayed flush", zap.Duration("duration", m.latency))
	m.flushPending = true
	return
}

func (m *maxLatencyWriter) delayedFlush() {
	m.mu.Lock()
	defer m.mu.Unlock()
	if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
		m.logger.Debug("delayed flush is not pending")
		return
	}
	m.logger.Debug("delayed flush")
	//nolint:errcheck
Repository: caddyserver/caddy
File: modules/caddyhttp/requestbody/requestbody.go (Go)

Code before:
}

func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	if rb.Replace != "" {
		r.Body = io.NopCloser(strings.NewReader(rb.Replace))
		r.ContentLength = int64(len(rb.Replace))
	}
	if r.Body == nil {
		return next.ServeHTTP(w, r)
	}
	if rb.MaxSize > 0 {
		r.Body = errorWrapper{http.MaxBytesReader(w, r.Body, rb.MaxSize)}
	}
	return next.ServeHTTP(w, r)
}

// errorWrapper wraps errors that are returned from Read()
// so that they can be associated with a proper status code.
type errorWrapper struct {
	io.ReadCloser
}

func (ew errorWrapper) Read(p []byte) (n int, err error) {
	n, err = ew.ReadCloser.Read(p)
	if err != nil && err.Error() == "http: request body too large" {
		err = caddyhttp.Error(http.StatusRequestEntityTooLarge, err)
	}
	return
}

// Interface guard
var _ caddyhttp.MiddlewareHandler = (*RequestBody)(nil)


Review comment (suggestion):
If we're completely replacing the request body, we miiiight want to call `r.Body.Close()` before re-assigning a new body. Yes the std lib will close it for us when the request is over, but would closing it early (and releasing those resources) cause any problems?

Also, I wonder if we should add placeholder support and expand those when doing the body replacement.

Code after:
	// EXPERIMENTAL. Subject to change/removal.
	WriteTimeout time.Duration `json:"write_timeout,omitempty"`

	// This field permit to replace body on the fly
	// EXPERIMENTAL. Subject to change/removal.
	Set string `json:"set,omitempty"`

	logger *zap.Logger
}

// CaddyModule returns the Caddy module information.
func (RequestBody) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "http.handlers.request_body",
		New: func() caddy.Module { return new(RequestBody) },
	}
}

func (rb *RequestBody) Provision(ctx caddy.Context) error {
	rb.logger = ctx.Logger()
	return nil
}

func (rb RequestBody) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	if rb.Set != "" {
		if r.Body != nil {
			err := r.Body.Close()
			if err != nil {
				return err
			}
		}
		repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
		replacedBody := repl.ReplaceAll(rb.Set, "")
		r.Body = io.NopCloser(strings.NewReader(replacedBody))
		r.ContentLength = int64(len(rb.Set))
	}
	if r.Body == nil {
		return next.ServeHTTP(w, r)
	}
	if rb.MaxSize > 0 {
		r.Body = errorWrapper{http.MaxBytesReader(w, r.Body, rb.MaxSize)}
	}
	if rb.ReadTimeout > 0 || rb.WriteTimeout > 0 {
		//nolint:bodyclose
		rc := http.NewResponseController(w)
		if rb.ReadTimeout > 0 {
			if err := rc.SetReadDeadline(time.Now().Add(rb.ReadTimeout)); err != nil {
				if c := rb.logger.Check(zapcore.ErrorLevel, "could not set read deadline"); c != nil {
					c.Write(zap.Error(err))
				}
			}
Repository: caddyserver/caddy
File: modules/caddyhttp/server.go (Go)

Code before:
}

// lengthReader is an io.ReadCloser that keeps track of the
// number of bytes read from the request body.
type lengthReader struct {
	Source io.ReadCloser
	Length int
}

var lengthReaderPool = sync.Pool{
	New: func() interface{} {
		return &lengthReader{}
	},
}

func getLengthReader(source io.ReadCloser) *lengthReader {
	reader := lengthReaderPool.Get().(*lengthReader)
	reader.Source = source
	reader.Length = 0
	return reader
}

func putLengthReader(reader *lengthReader) {
	reader.Source = nil
	lengthReaderPool.Put(reader)
}

func (r *lengthReader) Read(b []byte) (int, error) {
	n, err := r.Source.Read(b)
	r.Length += n
	return n, err
}

func (r *lengthReader) Close() error {
	return r.Source.Close()
}

// Context keys for HTTP request context values.
const (
	// For referencing the server instance
	ServerCtxKey caddy.CtxKey = "server"

	// For the request's variable table
	VarsCtxKey caddy.CtxKey = "vars"

	// For a partial copy of the unmodified request that
	// originally came into the server's entry handler
	OriginalRequestCtxKey caddy.CtxKey = "original_request"

	// For referencing underlying net.Conn
	ConnCtxKey caddy.CtxKey = "conn"

Review comment (suggestion):
I think the length should be reset when put back in the pool rather than when getting it. Numerical types already default to zero in Go. This makes it more symmetrical, i.e. `put` doing all the cleanup.

```suggestion
func getLengthReader(source io.ReadCloser) *lengthReader {
	reader := lengthReaderPool.Get().(*lengthReader)
	reader.Source = source
	return reader
}

func putLengthReader(reader *lengthReader) {
	reader.Source = nil
	reader.Length = 0
	lengthReaderPool.Put(reader)
}
```

Code after:
}

// lengthReader is an io.ReadCloser that keeps track of the
// number of bytes read from the request body.
type lengthReader struct {
	Source io.ReadCloser
	Length int
}

var lengthReaderPool = sync.Pool{
	New: func() interface{} {
		return &lengthReader{}
	},
}

func getLengthReader(source io.ReadCloser) *lengthReader {
	reader := lengthReaderPool.Get().(*lengthReader)
	reader.Source = source
	return reader
}

func putLengthReader(reader *lengthReader) {
	reader.Source = nil
	reader.Length = 0
	lengthReaderPool.Put(reader)
}

func (r *lengthReader) Read(b []byte) (int, error) {
	n, err := r.Source.Read(b)
	r.Length += n
	return n, err
}

func (r *lengthReader) Close() error {
	return r.Source.Close()
}

// Context keys for HTTP request context values.
const (
	// For referencing the server instance
	ServerCtxKey caddy.CtxKey = "server"

	// For the request's variable table
	VarsCtxKey caddy.CtxKey = "vars"

	// For a partial copy of the unmodified request that
	// originally came into the server's entry handler
	OriginalRequestCtxKey caddy.CtxKey = "original_request"

	// For referencing underlying net.Conn
	ConnCtxKey caddy.CtxKey = "conn"
Repository: caddyserver/caddy
File: modules/caddyhttp/fileserver/command.go (Go)

Code before:
	listen := fs.String("listen")
	browse := fs.Bool("browse")
	templates := fs.Bool("templates")
	accessLog := fs.Bool("access-log")
	debug := fs.Bool("debug")
	compress := !fs.Bool("no-compress")

	var handlers []json.RawMessage

	if compress {
		encodings := make(caddy.ModuleMap, 2)
		prefer := make([]string, 0, 2)

		gzip, err := caddy.GetModule("http.encoders.gzip")
		if err != nil {
			return 0, err
		}
		encodings["gzip"] = caddyconfig.JSON(gzip.New(), nil)
		prefer = append(prefer, "gzip")

		zstd, err := caddy.GetModule("http.encoders.zstd")
		if err != nil {
			return 0, err
		}
		encodings["zstd"] = caddyconfig.JSON(zstd.New(), nil)
		prefer = append(prefer, "zstd")

		handlers = append(handlers, caddyconfig.JSONModuleObject(encode.Encode{
			EncodingsRaw: encodings,
			Prefer:       prefer,
		}, "handler", "encode", nil))
	}

	if templates {
		handler := caddytpl.Templates{FileRoot: root}
		handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "templates", nil))
	}

	handler := FileServer{Root: root}
	if browse {
		handler.Browse = new(Browse)
	}

	handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "file_server", nil))

	route := caddyhttp.Route{HandlersRaw: handlers}

	if domain != "" {
		route.MatcherSetsRaw = []caddy.ModuleMap{
			{
				"host": caddyconfig.JSON(caddyhttp.MatchHost{domain}, nil),

Review comment (suggestion):
I think we usually put zstd before gzip because it _should_ perform better, but most clients don't support it (yet) so gzip is the fallback.

Code after:
func cmdFileServer(fs caddycmd.Flags) (int, error) {
	caddy.TrapSignals()

	domain := fs.String("domain")
	root := fs.String("root")
	listen := fs.String("listen")
	browse := fs.Bool("browse")
	templates := fs.Bool("templates")
	accessLog := fs.Bool("access-log")
	debug := fs.Bool("debug")
	compress := !fs.Bool("no-compress")
	precompressed, err := fs.GetStringSlice("precompressed")
	if err != nil {
		return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid precompressed flag: %v", err)
	}

	var handlers []json.RawMessage

	if compress {
		zstd, err := caddy.GetModule("http.encoders.zstd")
		if err != nil {
			return caddy.ExitCodeFailedStartup, err
		}

		gzip, err := caddy.GetModule("http.encoders.gzip")
		if err != nil {
			return caddy.ExitCodeFailedStartup, err
		}

		handlers = append(handlers, caddyconfig.JSONModuleObject(encode.Encode{
			EncodingsRaw: caddy.ModuleMap{
				"zstd": caddyconfig.JSON(zstd.New(), nil),
				"gzip": caddyconfig.JSON(gzip.New(), nil),
			},
			Prefer: []string{"zstd", "gzip"},
		}, "handler", "encode", nil))
	}

	if templates {
		handler := caddytpl.Templates{FileRoot: root}
		handlers = append(handlers, caddyconfig.JSONModuleObject(handler, "handler", "templates", nil))
	}

	handler := FileServer{Root: root}

	if len(precompressed) != 0 {
		// logic mirrors modules/caddyhttp/fileserver/caddyfile.go case "precompressed"
		var order []string
		for _, compression := range precompressed {
			modID := "http.precompressed." + compression
			mod, err := caddy.GetModule(modID)