Browse Source

Bumped to 1.13, added centralized logging

main
Artemis 2 years ago
parent
commit
b02acedd69
  1. 27
      config.go
  2. 2
      go.mod
  3. 98
      handlers.go
  4. 67
      log.go
  5. 21
      main.go
  6. 2
      templates.go

27
config.go

@ -4,40 +4,50 @@ import (
"github.com/spf13/viper"
)
// TODO remove
var NotFoundErr = "This document could not be found. Maybe it expired?"
var ConfigName = "paste"
// ProjectName is the binary name
var ProjectName = "paste"
// RedisConfig is the redis-specific configuration
type RedisConfig struct {
Address string
Password string
Database int
}
type HttpConfig struct {
// HTTPConfig is the HTTP-specific configuration
type HTTPConfig struct {
Host string
}
// Config is the app config
type Config struct {
Redis RedisConfig
Http HttpConfig
HTTP HTTPConfig
}
// LoadConfig loads the config at the given path
// Loading order:
// - Environment
// - Configuration file
// - Default values
func LoadConfig(path string) (*Config, error) {
// Default values, can be changed here
config := &Config{
Redis: RedisConfig{},
Http: HttpConfig{},
HTTP: HTTPConfig{},
}
loader := viper.New()
loader.SetConfigName(ConfigName)
loader.SetConfigName(ProjectName)
// Defaults
for key, defaults := range map[string]interface{}{
"Redis.Address": "127.0.0.1:6379",
"Redis.Password": "",
"Redis:Database": 0,
"Http.Host": "127.0.0.1:1234",
"HTTP.Host": "127.0.0.1:1234",
} {
loader.SetDefault(key, defaults)
}
@ -47,11 +57,8 @@ func LoadConfig(path string) (*Config, error) {
loader.AutomaticEnv()
err := loader.ReadInConfig()
if err != nil {
return config, err
}
config.Http.Host = loader.GetString("Http.Host")
config.HTTP.Host = loader.GetString("Http.Host")
config.Redis.Address = loader.GetString("Redis.Address")
config.Redis.Password = loader.GetString("Redis.Password")
config.Redis.Database = loader.GetInt("Redis.Database")

2
go.mod

@ -9,3 +9,5 @@ require (
github.com/spf13/viper v1.3.2
gitlab.com/Artemix/validator-go v0.0.4
)
go 1.13

98
handlers.go

@ -2,14 +2,15 @@ package main
import (
"fmt"
"github.com/gorilla/mux"
"gitlab.com/Artemix/paste/storage"
"gitlab.com/Artemix/validator-go"
"gitlab.com/Artemix/validator-go/rules"
"html/template"
"net/http"
"net/url"
"time"
"github.com/gorilla/mux"
"gitlab.com/Artemix/paste/storage"
"gitlab.com/Artemix/validator-go"
"gitlab.com/Artemix/validator-go/rules"
)
// region Data Structures and types
@ -36,26 +37,35 @@ var PasteSubmitSchema = map[string][]validator.Rule{
"time": {rules.Required{}, rules.Choice{Choices: []string{"1h", "6h", "12h", "24h"}}},
}
var genericErrors = map[int]string{
http.StatusBadRequest: "Bad request",
http.StatusNotFound: "File not found",
http.StatusInternalServerError: "Internal server error",
}
// endregion
// region Router
// GetRouter builds a HTTP router based on Gorilla/Mux, to handle
// HTTP traffic
func (h Handlers) GetRouter() *mux.Router {
r := mux.NewRouter()
r.Use(LogRequest)
r.HandleFunc("/", h.HandleNewPaste).
r.HandleFunc("/", h.handleNewPaste).
Methods(http.MethodPost)
r.Use(OpenCORSMiddleware("/"))
r.HandleFunc("/", h.HandleHome).
r.HandleFunc("/", h.handleHome).
Methods(http.MethodGet)
r.HandleFunc("/about", h.HandleAbout).Methods(http.MethodGet)
r.HandleFunc("/about", h.handleAbout).Methods(http.MethodGet)
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
// TODO add format filters
r.HandleFunc("/s/{key}", h.HandleShowPaste).Methods(http.MethodGet)
r.HandleFunc("/r/{key}", h.HandleShowRaw).Methods(http.MethodGet)
r.HandleFunc("/s/{key}", h.handleShowPaste).Methods(http.MethodGet)
r.HandleFunc("/r/{key}", h.handleShowRaw).Methods(http.MethodGet)
return r
}
@ -86,18 +96,22 @@ func OpenCORSMiddleware(route string) mux.MiddlewareFunc {
// region Static pages
func (h Handlers) HandleHome(w http.ResponseWriter, req *http.Request) {
_ = h.Templates.ExecuteTemplate(w, "homepage", nil)
func (h Handlers) handleHome(w http.ResponseWriter, req *http.Request) {
ErrIf(h.Templates.ExecuteTemplate(w, "homepage", nil))
}
func (h Handlers) HandleAbout(w http.ResponseWriter, req *http.Request) {
_ = h.Templates.ExecuteTemplate(w, "about", nil)
func (h Handlers) handleAbout(w http.ResponseWriter, req *http.Request) {
ErrIf(h.Templates.ExecuteTemplate(w, "about", nil))
}
// endregion
// region Form validators
func ValidateAndTransformPasteSubmitCommand(form url.Values) (PasteSubmitCommand, validator.Errors) {
// ValidateAndTransformPasteSubmitCommand validates the form based on the
// PasteSubmit schema, And fills a PasteSubmit command with associated values
func ValidateAndTransformPasteSubmitCommand(form url.Values) (
PasteSubmitCommand, validator.Errors) {
values, validationErrors := validator.Validator{Schema: PasteSubmitSchema}.Validate(form)
if len(validationErrors) > 0 {
@ -128,14 +142,15 @@ func ValidateAndTransformPasteSubmitCommand(form url.Values) (PasteSubmitCommand
// region Paste ephemeral handling
func (h Handlers) HandleNewPaste(w http.ResponseWriter, req *http.Request) {
func (h Handlers) handleNewPaste(w http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err != nil {
// TODO rework errors
w.WriteHeader(400)
fmt.Println(err)
_ = h.Templates.ExecuteTemplate(w, "homepage", &Content{Error: "Form submit error"})
ErrIf(h.Templates.ExecuteTemplate(w,
"homepage", &Content{Error: "Form submit error"}))
return
}
@ -145,7 +160,8 @@ func (h Handlers) HandleNewPaste(w http.ResponseWriter, req *http.Request) {
// TODO rework errors
w.WriteHeader(400)
fmt.Println(err)
_ = h.Templates.ExecuteTemplate(w, "homepage", &Content{Error: "Form submit error"})
ErrIf(h.Templates.ExecuteTemplate(w, "homepage",
&Content{Error: "Form submit error"}))
return
}
@ -154,69 +170,65 @@ func (h Handlers) HandleNewPaste(w http.ResponseWriter, req *http.Request) {
err = h.BackendService.
Persist(key, command.Paste, command.ExpirationTime)
if err != nil {
// TODO rework errors
w.WriteHeader(500)
fmt.Println(err)
_ = h.Templates.ExecuteTemplate(w, "homepage",
&Content{Error: "Failed to store the value", Value: command.Paste})
h.GenericHTTPError(500, err.Error())(w, req)
return
}
http.Redirect(w, req, "/s/"+key, http.StatusFound)
_, _ = fmt.Fprint(w, key)
_, err = fmt.Fprint(w, key)
ErrIf(err)
}
func (h Handlers) HandleShowPaste(w http.ResponseWriter, req *http.Request) {
func (h Handlers) handleShowPaste(w http.ResponseWriter, req *http.Request) {
key := mux.Vars(req)["key"]
res, err := h.BackendService.Retrieve(key)
if err != nil {
h.GenericHttpError(500, err.Error())(w, req)
h.GenericHTTPError(500, err.Error())(w, req)
return
} else if res == "" {
h.GenericHttpError(404, NotFoundErr)(w, req)
h.GenericHTTPError(404, NotFoundErr)(w, req)
return
}
err = h.Templates.ExecuteTemplate(w, "show", &Content{
ErrIf(h.Templates.ExecuteTemplate(w, "show", &Content{
Value: res,
Raw: "/r/" + key,
})
if err != nil {
fmt.Println(err)
}
}))
}
func (h Handlers) HandleShowRaw(w http.ResponseWriter, req *http.Request) {
func (h Handlers) handleShowRaw(w http.ResponseWriter, req *http.Request) {
key := mux.Vars(req)["key"]
res, err := h.BackendService.Retrieve(key)
if err != nil {
h.GenericHttpError(500, err.Error())(w, req)
h.GenericHTTPError(500, err.Error())(w, req)
return
} else if res == "" {
h.GenericHttpError(404, NotFoundErr)(w, req)
h.GenericHTTPError(404, NotFoundErr)(w, req)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf8")
_, _ = fmt.Fprint(w, res)
_, err = fmt.Fprint(w, res)
ErrIf(err)
}
// endregion
// region Error handlers
func (h *Handlers) GenericHttpError(statusCode int, error string) func(w http.ResponseWriter, req *http.Request) {
// GenericHTTPError takes a status code and an error, and
// - logs the error using the global ErrIf method
// - writes the generic template `http-error` with the status code and message
func (h *Handlers) GenericHTTPError(statusCode int, error string) func(w http.ResponseWriter, req *http.Request) {
ErrIf(error)
return func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(statusCode)
err := h.Templates.ExecuteTemplate(w, "http-error", map[string]string{
"Error": error,
})
// TODO proper logging
if err != nil {
println(err)
}
ErrIf(h.Templates.ExecuteTemplate(w, "http-error", map[string]string{
"Error": genericErrors[statusCode],
}))
}
}

67
log.go

@ -0,0 +1,67 @@
package main
import (
"log"
"net/http"
"os"
)
var (
_prefix = ProjectName + ":: "
_status = log.New(os.Stdout, _prefix, 0)
_error = log.New(os.Stderr, _prefix, 0)
)
// Logf uses Printf as backend, to write data to stdout
func Logf(format string, data ...interface{}) {
_status.Printf(format, data...)
}
// ErrIf checks if the err value isn't null, and in which case,
// it logs it on stderr
func ErrIf(err interface{}) {
if nil != err {
_error.Println(err)
}
}
// DieIf checks if the err value isn't null, and in which case, it
// logs it on stderr, then dies with status code 1
func DieIf(err interface{}) {
if nil != err {
_error.Println(err)
os.Exit(1)
}
}
// StatusRecorder is a custom ResponseWriter type that wraps the standard
// writer, to allow status code extraction.
// Like RW, by default, it uses http.StatusOK (200)
type StatusRecorder struct {
http.ResponseWriter
status int
}
// WriteHeader keeps note of the given status code
func (rec *StatusRecorder) WriteHeader(code int) {
rec.status = code
rec.ResponseWriter.WriteHeader(code)
}
// LogRequest takes the current request and logs the result:
// IP: PROTOCOL_VERSION STATUS_CODE METHOD "URI"
func LogRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
rec := &StatusRecorder{w, http.StatusOK}
next.ServeHTTP(rec, req)
_status.Printf("%s: %s %d %s \"%s\"\n",
req.RemoteAddr,
req.Proto,
rec.status,
req.Method,
req.RequestURI,
)
})
}

21
main.go

@ -1,28 +1,23 @@
package main
import (
"fmt"
"github.com/go-redis/redis"
"gitlab.com/Artemix/paste/storage"
"log"
"net/http"
"time"
"github.com/go-redis/redis"
"gitlab.com/Artemix/paste/storage"
)
func main() {
config, err := LoadConfig(".")
if err != nil {
log.Println(err)
}
ErrIf(err)
redisBackend, err := storage.New(&redis.Options{
Addr: config.Redis.Address,
Password: config.Redis.Password,
DB: config.Redis.Database,
})
if err != nil {
log.Fatal(err)
}
DieIf(err)
handlers := &Handlers{
Templates: InitTemplates(),
@ -30,12 +25,12 @@ func main() {
}
srv := &http.Server{
Addr: config.Http.Host,
Addr: config.HTTP.Host,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
Handler: handlers.GetRouter(),
}
fmt.Printf("HTTP server about to start listening on %s\n", config.Http.Host)
log.Fatal(srv.ListenAndServe())
Logf("HTTP server about to start listening on %s\n", config.HTTP.Host)
DieIf(srv.ListenAndServe())
}

2
templates.go

@ -14,7 +14,7 @@ func Lineno(input string) string {
strings.Split(input, "\n")
for idx, _ := range strings.Split(input, "\n") {
output += fmt.Sprintf("%d\n", idx + 1)
output += fmt.Sprintf("%d\n", idx+1)
}
return output[:len(output)-1]

Loading…
Cancel
Save