200 lines
6.1 KiB
Go
200 lines
6.1 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"gifuu/routes"
|
||
|
|
"gifuu/tools"
|
||
|
|
"net"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"os/signal"
|
||
|
|
"strings"
|
||
|
|
"sync"
|
||
|
|
"syscall"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
time.Local = time.UTC
|
||
|
|
|
||
|
|
// Startup Services
|
||
|
|
var stopCtx, stop = context.WithCancel(context.Background())
|
||
|
|
var stopWg sync.WaitGroup
|
||
|
|
var syncWg sync.WaitGroup
|
||
|
|
|
||
|
|
tools.LoggerInit.Log(tools.INFO, "Starting Services")
|
||
|
|
for _, fn := range []func(stop context.Context, await *sync.WaitGroup){
|
||
|
|
tools.SetupDatabase,
|
||
|
|
tools.SetupModel,
|
||
|
|
} {
|
||
|
|
syncWg.Add(1)
|
||
|
|
go func() {
|
||
|
|
defer syncWg.Done()
|
||
|
|
fn(stopCtx, &stopWg)
|
||
|
|
}()
|
||
|
|
}
|
||
|
|
syncWg.Wait()
|
||
|
|
go StartupHTTP(stopCtx, &stopWg)
|
||
|
|
|
||
|
|
// Await Shutdown Signal
|
||
|
|
cancel := make(chan os.Signal, 1)
|
||
|
|
signal.Notify(cancel, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||
|
|
<-cancel
|
||
|
|
stop()
|
||
|
|
|
||
|
|
// Begin Shutdown Process
|
||
|
|
tools.LoggerInit.Log(tools.WARN, "Shutting Down!")
|
||
|
|
timeout, finish := context.WithTimeout(context.Background(), tools.TIMEOUT_CONTEXT)
|
||
|
|
defer finish()
|
||
|
|
go func() {
|
||
|
|
<-timeout.Done()
|
||
|
|
if timeout.Err() == context.DeadlineExceeded {
|
||
|
|
tools.LoggerInit.Log(tools.FATAL, "Shutdown Deadline Exceeded")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
stopWg.Wait()
|
||
|
|
os.Exit(0)
|
||
|
|
}
|
||
|
|
|
||
|
|
func SetupMux() http.HandlerFunc {
|
||
|
|
|
||
|
|
var (
|
||
|
|
mux = http.NewServeMux()
|
||
|
|
limitPublic = tools.NewRatelimiter("PUBLIC", 900, 5*time.Minute)
|
||
|
|
limitDelete = tools.NewRatelimiter("DELETE", 100, 5*time.Minute)
|
||
|
|
limitStart = tools.NewRatelimiter("START", 100, 5*time.Minute)
|
||
|
|
limitCreate = tools.NewRatelimiter("CREATE", 50, 5*time.Minute)
|
||
|
|
limitModerators = tools.NewRatelimiter("MOD", 300, 5*time.Minute)
|
||
|
|
gatekeepModerator = tools.NewGatekeeper(true)
|
||
|
|
powExpensive = tools.NewChallenger(20)
|
||
|
|
powNormal = tools.NewChallenger(18)
|
||
|
|
)
|
||
|
|
|
||
|
|
// General
|
||
|
|
mux.Handle("/uploads", tools.MethodHandler{
|
||
|
|
http.MethodPost: tools.Chain(routes.POST_Uploads, limitCreate, powExpensive),
|
||
|
|
})
|
||
|
|
mux.Handle("/tags/popular", tools.MethodHandler{
|
||
|
|
http.MethodGet: tools.Chain(routes.GET_Tags_Popular, limitPublic),
|
||
|
|
})
|
||
|
|
mux.Handle("/tags/autocomplete", tools.MethodHandler{
|
||
|
|
http.MethodGet: tools.Chain(routes.GET_Tags_Autocomplete, limitPublic),
|
||
|
|
})
|
||
|
|
mux.Handle("/art/latest", tools.MethodHandler{
|
||
|
|
http.MethodGet: tools.Chain(routes.GET_Art_Latest, limitPublic),
|
||
|
|
})
|
||
|
|
mux.Handle("/art/search", tools.MethodHandler{
|
||
|
|
http.MethodGet: tools.Chain(routes.GET_Art_Search, limitPublic),
|
||
|
|
})
|
||
|
|
mux.Handle("/art/{id}", tools.MethodHandler{
|
||
|
|
http.MethodGet: tools.Chain(routes.GET_Art_ID, limitPublic),
|
||
|
|
http.MethodDelete: tools.Chain(routes.DELETE_Art_ID, limitDelete),
|
||
|
|
})
|
||
|
|
mux.Handle("/art/{id}/report", tools.MethodHandler{
|
||
|
|
http.MethodPost: tools.Chain(routes.POST_Art_ID_Reports, limitCreate, powNormal),
|
||
|
|
})
|
||
|
|
mux.Handle("/metadata/{id}", tools.MethodHandler{
|
||
|
|
http.MethodGet: tools.Chain(routes.GET_Metadata_ID, limitPublic),
|
||
|
|
})
|
||
|
|
|
||
|
|
// Moderation
|
||
|
|
mux.Handle("/moderation/art/{id}", tools.MethodHandler{
|
||
|
|
http.MethodDelete: tools.Chain(routes.DELETE_Moderation_Art_ID, limitModerators, gatekeepModerator),
|
||
|
|
})
|
||
|
|
// mux.Handle("/moderation/art/{id}/rating", tools.MethodHandler{
|
||
|
|
// // TODO: Sets the items moderation to 100% hiding it from the public
|
||
|
|
// http.MethodPatch: tools.Chain(routes.PATCH_Moderation_Art_ID_Rating, limitModerators, gatekeepModerator),
|
||
|
|
// })
|
||
|
|
// mux.Handle("/moderation/art/{id}/bypass", tools.MethodHandler{
|
||
|
|
// // TODO: Disables Reporting for the item by setting the 'flag_bypass' flag
|
||
|
|
// http.MethodPatch: tools.Chain(routes.PATCH_Moderation_Art_ID_Bypass, limitModerators, gatekeepModerator),
|
||
|
|
// })
|
||
|
|
// mux.Handle("/moderation/art/{id}/reports", tools.MethodHandler{
|
||
|
|
// // TODO: Get Latest Reports for an Item
|
||
|
|
// http.MethodGet: tools.Chain(routes.GET_Moderation_Art_ID_Reports, limitModerators, gatekeepModerator),
|
||
|
|
// // TODO: Delete all Reports for an Item
|
||
|
|
// http.MethodDelete: tools.Chain(routes.DELETE_Moderation_Art_ID_Reports, limitModerators, gatekeepModerator),
|
||
|
|
// })
|
||
|
|
// // TODO: Get the Latest Reports across Site
|
||
|
|
// mux.Handle("/moderation/latest", tools.MethodHandler{
|
||
|
|
// http.MethodGet: tools.Chain(routes.GET_Moderation_Latest, limitModerators, gatekeepModerator),
|
||
|
|
// })
|
||
|
|
|
||
|
|
// Other
|
||
|
|
mux.Handle("/challenge", tools.MethodHandler{
|
||
|
|
http.MethodGet: tools.Chain(routes.GET_Challenge, limitStart),
|
||
|
|
})
|
||
|
|
mux.Handle("/limits", tools.MethodHandler{
|
||
|
|
http.MethodGet: tools.Chain(routes.GET_Limits, limitPublic),
|
||
|
|
})
|
||
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
tools.SendClientError(w, r, tools.ERROR_UNKNOWN_ENDPOINT)
|
||
|
|
})
|
||
|
|
|
||
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
|
||
|
|
// Inject CORS
|
||
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||
|
|
if r.Method == http.MethodOptions {
|
||
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS")
|
||
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Challenge-Nonce, X-Challenge-Counter")
|
||
|
|
w.WriteHeader(http.StatusNoContent)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
mux.ServeHTTP(w, r)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func StartupHTTP(stop context.Context, await *sync.WaitGroup) {
|
||
|
|
var listener net.Listener
|
||
|
|
var err error
|
||
|
|
|
||
|
|
if strings.HasPrefix(tools.HTTP_ADDRESS, "unix/") {
|
||
|
|
path := strings.TrimPrefix(tools.HTTP_ADDRESS, "unix/")
|
||
|
|
os.Remove(path)
|
||
|
|
listener, err = net.Listen("unix", path)
|
||
|
|
if err == nil {
|
||
|
|
os.Chmod(path, 0660)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
listener, err = net.Listen("tcp", tools.HTTP_ADDRESS)
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
tools.LoggerHTTP.Log(tools.FATAL, "Listen failed: %s", err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
svr := http.Server{
|
||
|
|
Handler: SetupMux(),
|
||
|
|
MaxHeaderBytes: 4096,
|
||
|
|
IdleTimeout: 10 * time.Second,
|
||
|
|
ReadHeaderTimeout: 10 * time.Second,
|
||
|
|
ReadTimeout: 30 * time.Second,
|
||
|
|
WriteTimeout: 30 * time.Second,
|
||
|
|
}
|
||
|
|
|
||
|
|
// Shutdown Logic
|
||
|
|
await.Add(1)
|
||
|
|
go func() {
|
||
|
|
defer await.Done()
|
||
|
|
<-stop.Done()
|
||
|
|
|
||
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), tools.TIMEOUT_SHUTDOWN)
|
||
|
|
defer cancel()
|
||
|
|
|
||
|
|
if err := svr.Shutdown(shutdownCtx); err != nil {
|
||
|
|
tools.LoggerHTTP.Log(tools.ERROR, "Shutdown error: %s", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
tools.LoggerHTTP.Log(tools.INFO, "Closed")
|
||
|
|
}()
|
||
|
|
|
||
|
|
tools.LoggerHTTP.Log(tools.INFO, "Listening @ %s", tools.HTTP_ADDRESS)
|
||
|
|
if err := svr.Serve(listener); err != http.ErrServerClosed {
|
||
|
|
tools.LoggerHTTP.Log(tools.FATAL, "Startup Failed: %s", err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|