package server import ( "fmt" "html/template" "log/slog" "net/http" "strconv" "git.runcible.io/learning/ratchet/internal/domain/user" ) type RatchetServer struct { http.Handler logger *slog.Logger //Services used by HTTP routes UserService user.UserService } func NewRatchetServer(logger *slog.Logger) *RatchetServer { rs := new(RatchetServer) rs.logger = logger // TODO implement middleware that disables directory listings fileServer := http.FileServer(http.Dir("./ui/static/")) router := http.NewServeMux() // Subtree pattern for static assets router.Handle("GET /static/", http.StripPrefix("/static/", fileServer)) // /{$} is used to prevent subtree path patterns from acting like a wildcard // resulting in this route requiring an exact match on "/" only // You can only include one HTTP method in a route pattern if you choose // GET will match GET & HEAD http request methods router.HandleFunc("GET /{$}", rs.home) router.HandleFunc("GET /snippet/view/{id}", rs.snippetView) router.HandleFunc("GET /snippet/create", rs.snippetCreate) // FYI The http.HandlerFunc() adapter works by automatically adding a ServeHTTP() method to // the passed function router.HandleFunc("POST /snippet/create", rs.snippetCreatePost) // Mux Router implements the Handler interface. AKA it has a ServeHTTP receiver. rs.Handler = router return rs } func (rs *RatchetServer) home(w http.ResponseWriter, r *http.Request) { // TODO middleware should be able to print out these lines for all routes rs.logger.Info("request received", "method", "GET", "path", "/") w.Header().Add("Server", "Go") // Initialize a slice containing the paths to the two files. It's important // to note that the file containing our base template must be the *first* // file in the slice. files := []string{ "./ui/html/base.go.tmpl", "./ui/html/partials/nav.go.tmpl", "./ui/html/pages/home.go.tmpl", } // read template file into template set. ts, err := template.ParseFiles(files...) if err != nil { rs.serverError(w, r, err) return } // Write template content to response body err = ts.ExecuteTemplate(w, "base", nil) if err != nil { // This is the older more verbose way of doing what RatchetServer.serverError does // rs.logger.Error(err.Error()) // http.Error(w, "Internal Server Error", http.StatusInternalServerError) rs.serverError(w, r, err) } } func (rs *RatchetServer) snippetView(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil || id < 1 { // http.NotFound(w, r) rs.clientError(w, http.StatusNotFound) return } // Set a new cache-control header. If an existing "Cache-Control" header exists // it will be overwritten. w.Header().Set("Cache-Control", "public, max-age=31536000") // msg := fmt.Sprintf("Snippet %d...", id) // w.Write([]byte(msg)) // we can rely on the Write() interface to use a differnent // function to write out our response fmt.Fprintf(w, "Snippet %d...", id) } // snippetCreate handles display of the form used to create snippets func (rs *RatchetServer) snippetCreate(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Displaying snippetCreate form...")) } // snippetCreate handles display of the form used to create snippets func (rs *RatchetServer) snippetCreatePost(w http.ResponseWriter, r *http.Request) { // example of a custom header. Must be done before calling WriteHeader // or they will fail to take effect. w.Header().Add("Server", "Dirp") w.WriteHeader(http.StatusCreated) w.Write([]byte("Created snippet...")) }