Skip to content

Commit

Permalink
Refactor file server implementation (#3588)
Browse files Browse the repository at this point in the history
So that it doesn't require introducing a `Replace` function.
The new implementation is more robust and properly handles cases where the file path is deep and differs from the mount path.
  • Loading branch information
raphael authored Sep 10, 2024
1 parent 9d9f578 commit beed4ab
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 130 deletions.
29 changes: 13 additions & 16 deletions http/codegen/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package codegen

import (
"fmt"
"path"
"path/filepath"
"reflect"
"strings"
Expand Down Expand Up @@ -32,16 +33,16 @@ func ServerFiles(genpkg string, root *expr.RootExpr) []*codegen.File {
func serverFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
data := HTTPServices.Get(svc.Name())
svcName := data.Service.PathName
path := filepath.Join(codegen.Gendir, "http", svcName, "server", "server.go")
fpath := filepath.Join(codegen.Gendir, "http", svcName, "server", "server.go")
title := fmt.Sprintf("%s HTTP server", svc.Name())
funcs := map[string]any{
"join": strings.Join,
"hasWebSocket": hasWebSocket,
"isWebSocketEndpoint": isWebSocketEndpoint,
"viewedServerBody": viewedServerBody,
"mustDecodeRequest": mustDecodeRequest,
"addLeadingSlash": addLeadingSlash,
"removeTrailingIndexHTML": removeTrailingIndexHTML,
"join": strings.Join,
"hasWebSocket": hasWebSocket,
"isWebSocketEndpoint": isWebSocketEndpoint,
"viewedServerBody": viewedServerBody,
"mustDecodeRequest": mustDecodeRequest,
"addLeadingSlash": addLeadingSlash,
"dir": path.Dir,
}
imports := []*codegen.ImportSpec{
{Path: "bufio"},
Expand Down Expand Up @@ -86,11 +87,14 @@ func serverFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.File {
sections = append(sections, &codegen.SectionTemplate{Name: "server-handler", Source: readTemplate("server_handler"), Data: e})
sections = append(sections, &codegen.SectionTemplate{Name: "server-handler-init", Source: readTemplate("server_handler_init"), FuncMap: funcs, Data: e})
}
if len(data.FileServers) > 0 {
sections = append(sections, &codegen.SectionTemplate{Name: "append-fs", Source: readTemplate("append_fs"), FuncMap: funcs, Data: data})
}
for _, s := range data.FileServers {
sections = append(sections, &codegen.SectionTemplate{Name: "server-files", Source: readTemplate("file_server"), FuncMap: funcs, Data: s})
}

return &codegen.File{Path: path, SectionTemplates: sections}
return &codegen.File{Path: fpath, SectionTemplates: sections}
}

// serverEncodeDecodeFile returns the file defining the HTTP server encoding and
Expand Down Expand Up @@ -252,13 +256,6 @@ func addLeadingSlash(s string) string {
return "/" + s
}

func removeTrailingIndexHTML(s string) string {
if strings.HasSuffix(s, "/index.html") {
return strings.TrimSuffix(s, "index.html")
}
return s
}

func mapQueryDecodeData(dt expr.DataType, varName string, inc int) map[string]any {
return map[string]any{
"Type": dt,
Expand Down
18 changes: 18 additions & 0 deletions http/codegen/templates/append_fs.go.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// appendFS is a custom implementation of fs.FS that appends a specified prefix
// to the file paths before delegating the Open call to the underlying fs.FS.
type appendFS struct {
prefix string
fs http.FileSystem
}

// Open opens the named file, appending the prefix to the file path before
// passing it to the underlying fs.FS.
func (s appendFS) Open(name string) (http.File, error) {
return s.fs.Open(path.Join(s.prefix, name))
}

// appendPrefix returns a new fs.FS that appends the specified prefix to file paths
// before delegating to the provided embed.FS.
func appendPrefix(fsys http.FileSystem, prefix string) http.FileSystem {
return appendFS{prefix: prefix, fs: fsys}
}
9 changes: 8 additions & 1 deletion http/codegen/templates/server_init.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ func {{ .ServerInit }}(
if {{ .ArgName }} == nil {
{{ .ArgName }} = http.Dir(".")
}
{{- $prefix := addLeadingSlash .FilePath }}
{{- if not .IsDir }}
{{- $prefix = dir $prefix }}
{{- end }}
{{- if ne $prefix "/" }}
{{ .ArgName }} = appendPrefix({{ .ArgName }}, "{{ $prefix }}")
{{- end }}
{{- end }}
return &{{ .ServerStruct }}{
Mounts: []*{{ .MountPointStruct }}{
Expand All @@ -39,7 +46,7 @@ func {{ .ServerInit }}(
{{- range .FileServers }}
{{- $filepath := .FilePath }}
{{- range .RequestPaths }}
{"{{ $filepath }}", "GET", "{{ . }}"},
{"Serve {{ $filepath }}", "GET", "{{ . }}"},
{{- end }}
{{- end }}
},
Expand Down
19 changes: 14 additions & 5 deletions http/codegen/templates/server_mount.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@ func {{ .MountServer }}(mux goahttp.Muxer, h *{{ .ServerStruct }}) {
{{ .MountHandler }}(mux, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "{{ .Redirect.URL }}", {{ .Redirect.StatusCode }})
}))
{{- else if .IsDir }}
{{- $filepath := addLeadingSlash (removeTrailingIndexHTML .FilePath) }}
{{ .MountHandler }}(mux, {{ range .RequestPaths }}{{if ne . $filepath }}goahttp.Replace("{{ . }}", "{{ $filepath }}", {{ end }}{{ end }}h.{{ .VarName }}){{ range .RequestPaths }}{{ if ne . $filepath }}){{ end}}{{ end }}
{{- else }}
{{- $filepath := addLeadingSlash (removeTrailingIndexHTML .FilePath) }}
{{ .MountHandler }}(mux, {{ range .RequestPaths }}{{if ne . $filepath }}goahttp.Replace("", "{{ $filepath }}", {{ end }}{{ end }}h.{{ .VarName }}){{ range .RequestPaths }}{{ if ne . $filepath }}){{ end}}{{ end }}
{{- $mountHandler := .MountHandler }}
{{- $varName := .VarName }}
{{- $isDir := .IsDir }}
{{- range .RequestPaths }}
{{- $stripped := addLeadingSlash . }}
{{- if not $isDir }}
{{- $stripped = (dir $stripped) }}
{{- end }}
{{- if eq $stripped "/" }}
{{ $mountHandler }}(mux, h.{{ $varName }})
{{- else }}
{{ $mountHandler }}(mux, http.StripPrefix("{{ $stripped }}", h.{{ $varName }}))
{{- end }}
{{- end }}
{{- end }}
{{- end }}
}
Expand Down
33 changes: 19 additions & 14 deletions http/codegen/testdata/server_init_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,20 @@ func New(
if fileSystemPathToFile1JSON == nil {
fileSystemPathToFile1JSON = http.Dir(".")
}
fileSystemPathToFile1JSON = appendPrefix(fileSystemPathToFile1JSON, "/path/to")
if fileSystemPathToFile2JSON == nil {
fileSystemPathToFile2JSON = http.Dir(".")
}
fileSystemPathToFile2JSON = appendPrefix(fileSystemPathToFile2JSON, "/path/to")
if fileSystemPathToFile3JSON == nil {
fileSystemPathToFile3JSON = http.Dir(".")
}
fileSystemPathToFile3JSON = appendPrefix(fileSystemPathToFile3JSON, "/path/to")
return &Server{
Mounts: []*MountPoint{
{"/path/to/file1.json", "GET", "/server_file_server/file1.json"},
{"/path/to/file2.json", "GET", "/server_file_server/file2.json"},
{"/path/to/file3.json", "GET", "/server_file_server/file3.json"},
{"Serve /path/to/file1.json", "GET", "/server_file_server/file1.json"},
{"Serve /path/to/file2.json", "GET", "/server_file_server/file2.json"},
{"Serve /path/to/file3.json", "GET", "/server_file_server/file3.json"},
},
PathToFile1JSON: http.FileServer(fileSystemPathToFile1JSON),
PathToFile2JSON: http.FileServer(fileSystemPathToFile2JSON),
Expand Down Expand Up @@ -107,15 +110,17 @@ func New(
if fileSystemPathToFile1JSON == nil {
fileSystemPathToFile1JSON = http.Dir(".")
}
fileSystemPathToFile1JSON = appendPrefix(fileSystemPathToFile1JSON, "/path/to")
if fileSystemPathToFile2JSON == nil {
fileSystemPathToFile2JSON = http.Dir(".")
}
fileSystemPathToFile2JSON = appendPrefix(fileSystemPathToFile2JSON, "/path/to")
return &Server{
Mounts: []*MountPoint{
{"MethodMixed1", "GET", "/resources1/{id}"},
{"MethodMixed2", "GET", "/resources2/{id}"},
{"/path/to/file1.json", "GET", "/file1.json"},
{"/path/to/file2.json", "GET", "/file2.json"},
{"Serve /path/to/file1.json", "GET", "/file1.json"},
{"Serve /path/to/file2.json", "GET", "/file2.json"},
},
MethodMixed1: NewMethodMixed1Handler(e.MethodMixed1, mux, decoder, encoder, errhandler, formatter),
MethodMixed2: NewMethodMixed2Handler(e.MethodMixed2, mux, decoder, encoder, errhandler, formatter),
Expand Down Expand Up @@ -179,10 +184,10 @@ func New(

var ServerMultipleFilesConstructorCode = `// Mount configures the mux to serve the ServiceFileServer endpoints.
func Mount(mux goahttp.Muxer, h *Server) {
MountPathToFileJSON(mux, goahttp.Replace("", "/path/to/file.json", h.PathToFileJSON))
MountPathToFileJSON2(mux, goahttp.Replace("", "/path/to/file.json", h.PathToFileJSON2))
MountPathToFileJSON(mux, h.PathToFileJSON)
MountPathToFileJSON2(mux, h.PathToFileJSON2)
MountFileJSON(mux, h.FileJSON)
MountPathToFolder(mux, goahttp.Replace("/", "/path/to/folder", h.PathToFolder))
MountPathToFolder(mux, h.PathToFolder)
}
// Mount configures the mux to serve the ServiceFileServer endpoints.
Expand All @@ -193,10 +198,10 @@ func (s *Server) Mount(mux goahttp.Muxer) {

var ServerMultipleFilesWithPrefixPathConstructorCode = `// Mount configures the mux to serve the ServiceFileServer endpoints.
func Mount(mux goahttp.Muxer, h *Server) {
MountPathToFileJSON(mux, goahttp.Replace("", "/path/to/file.json", h.PathToFileJSON))
MountPathToFileJSON2(mux, goahttp.Replace("", "/path/to/file.json", h.PathToFileJSON2))
MountFileJSON(mux, goahttp.Replace("", "/file.json", h.FileJSON))
MountPathToFolder(mux, goahttp.Replace("/server_file_server", "/path/to/folder", h.PathToFolder))
MountPathToFileJSON(mux, http.StripPrefix("/server_file_server", h.PathToFileJSON))
MountPathToFileJSON2(mux, h.PathToFileJSON2)
MountFileJSON(mux, http.StripPrefix("/server_file_server", h.FileJSON))
MountPathToFolder(mux, http.StripPrefix("/server_file_server", h.PathToFolder))
}
// Mount configures the mux to serve the ServiceFileServer endpoints.
Expand All @@ -210,9 +215,9 @@ func Mount(mux goahttp.Muxer, h *Server) {
MountPathToFileJSON(mux, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/redirect/dest", http.StatusMovedPermanently)
}))
MountPathToFileJSON2(mux, goahttp.Replace("", "/path/to/file.json", h.PathToFileJSON2))
MountPathToFileJSON2(mux, h.PathToFileJSON2)
MountFileJSON(mux, h.FileJSON)
MountPathToFolder(mux, goahttp.Replace("/", "/path/to/folder", h.PathToFolder))
MountPathToFolder(mux, h.PathToFolder)
}
// Mount configures the mux to serve the ServiceFileServer endpoints.
Expand Down
31 changes: 0 additions & 31 deletions http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package http

import (
"net/http"
"net/url"
"strings"
)

type (
Expand Down Expand Up @@ -38,32 +36,3 @@ func (s Servers) Mount(mux Muxer) {
m.Mount(mux)
}
}

// Replace returns a handler that serves HTTP requests by replacing the
// request URL's Path (and RawPath if set) and invoking the handler h.
// The logic is the same as the standard http package StripPrefix function.
func Replace(old, nw string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var p, rp string
if old != "" {
p = strings.Replace(r.URL.Path, old, nw, 1)
rp = strings.Replace(r.URL.RawPath, old, nw, 1)
} else {
p = nw
if r.URL.RawPath != "" {
rp = nw
}
}
if p != r.URL.Path && (r.URL.RawPath == "" || rp != r.URL.RawPath) {
r2 := new(http.Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p
r2.URL.RawPath = rp
h.ServeHTTP(w, r2)
} else {
http.NotFound(w, r)
}
})
}
63 changes: 0 additions & 63 deletions http/server_test.go

This file was deleted.

0 comments on commit beed4ab

Please sign in to comment.