requests-log db
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
||||
"blackforestbytes.com/simplecloudnotifier/api/apihighlight"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -14,6 +15,9 @@ import (
|
||||
|
||||
type HTTPResponse interface {
|
||||
Write(g *gin.Context)
|
||||
Statuscode() int
|
||||
BodyString() *string
|
||||
ContentType() string
|
||||
}
|
||||
|
||||
type jsonHTTPResponse struct {
|
||||
@@ -25,6 +29,22 @@ func (j jsonHTTPResponse) Write(g *gin.Context) {
|
||||
g.JSON(j.statusCode, j.data)
|
||||
}
|
||||
|
||||
func (j jsonHTTPResponse) Statuscode() int {
|
||||
return j.statusCode
|
||||
}
|
||||
|
||||
func (j jsonHTTPResponse) BodyString() *string {
|
||||
v, err := json.Marshal(j.data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return langext.Ptr(string(v))
|
||||
}
|
||||
|
||||
func (j jsonHTTPResponse) ContentType() string {
|
||||
return "application/json"
|
||||
}
|
||||
|
||||
type emptyHTTPResponse struct {
|
||||
statusCode int
|
||||
}
|
||||
@@ -33,6 +53,18 @@ func (j emptyHTTPResponse) Write(g *gin.Context) {
|
||||
g.Status(j.statusCode)
|
||||
}
|
||||
|
||||
func (j emptyHTTPResponse) Statuscode() int {
|
||||
return j.statusCode
|
||||
}
|
||||
|
||||
func (j emptyHTTPResponse) BodyString() *string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j emptyHTTPResponse) ContentType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type textHTTPResponse struct {
|
||||
statusCode int
|
||||
data string
|
||||
@@ -42,6 +74,18 @@ func (j textHTTPResponse) Write(g *gin.Context) {
|
||||
g.String(j.statusCode, "%s", j.data)
|
||||
}
|
||||
|
||||
func (j textHTTPResponse) Statuscode() int {
|
||||
return j.statusCode
|
||||
}
|
||||
|
||||
func (j textHTTPResponse) BodyString() *string {
|
||||
return langext.Ptr(j.data)
|
||||
}
|
||||
|
||||
func (j textHTTPResponse) ContentType() string {
|
||||
return "text/plain"
|
||||
}
|
||||
|
||||
type dataHTTPResponse struct {
|
||||
statusCode int
|
||||
data []byte
|
||||
@@ -52,6 +96,18 @@ func (j dataHTTPResponse) Write(g *gin.Context) {
|
||||
g.Data(j.statusCode, j.contentType, j.data)
|
||||
}
|
||||
|
||||
func (j dataHTTPResponse) Statuscode() int {
|
||||
return j.statusCode
|
||||
}
|
||||
|
||||
func (j dataHTTPResponse) BodyString() *string {
|
||||
return langext.Ptr(string(j.data))
|
||||
}
|
||||
|
||||
func (j dataHTTPResponse) ContentType() string {
|
||||
return j.contentType
|
||||
}
|
||||
|
||||
type errorHTTPResponse struct {
|
||||
statusCode int
|
||||
data any
|
||||
@@ -62,6 +118,22 @@ func (j errorHTTPResponse) Write(g *gin.Context) {
|
||||
g.JSON(j.statusCode, j.data)
|
||||
}
|
||||
|
||||
func (j errorHTTPResponse) Statuscode() int {
|
||||
return j.statusCode
|
||||
}
|
||||
|
||||
func (j errorHTTPResponse) BodyString() *string {
|
||||
v, err := json.Marshal(j.data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return langext.Ptr(string(v))
|
||||
}
|
||||
|
||||
func (j errorHTTPResponse) ContentType() string {
|
||||
return "application/json"
|
||||
}
|
||||
|
||||
func Status(sc int) HTTPResponse {
|
||||
return &emptyHTTPResponse{statusCode: sc}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,24 @@ package ginresp
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/dataext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WHandlerFunc func(*gin.Context) HTTPResponse
|
||||
|
||||
func Wrap(fn WHandlerFunc) gin.HandlerFunc {
|
||||
type RequestLogAcceptor interface {
|
||||
InsertRequestLog(data models.RequestLog)
|
||||
}
|
||||
|
||||
func Wrap(rlacc RequestLogAcceptor, fn WHandlerFunc) gin.HandlerFunc {
|
||||
|
||||
maxRetry := scn.Conf.RequestMaxRetry
|
||||
retrySleep := scn.Conf.RequestRetrySleep
|
||||
@@ -27,6 +33,8 @@ func Wrap(fn WHandlerFunc) gin.HandlerFunc {
|
||||
g.Request.Body = dataext.NewBufferedReadCloser(g.Request.Body)
|
||||
}
|
||||
|
||||
t0 := time.Now()
|
||||
|
||||
for ctr := 1; ; ctr++ {
|
||||
|
||||
wrap, panicObj := callPanicSafe(fn, g)
|
||||
@@ -36,6 +44,9 @@ func Wrap(fn WHandlerFunc) gin.HandlerFunc {
|
||||
}
|
||||
|
||||
if g.Writer.Written() {
|
||||
if scn.Conf.ReqLogEnabled {
|
||||
rlacc.InsertRequestLog(createRequestLog(g, t0, ctr, nil, langext.Ptr("Writing in WrapperFunc is not supported")))
|
||||
}
|
||||
panic("Writing in WrapperFunc is not supported")
|
||||
}
|
||||
|
||||
@@ -52,6 +63,9 @@ func Wrap(fn WHandlerFunc) gin.HandlerFunc {
|
||||
}
|
||||
|
||||
if reqctx.Err() == nil {
|
||||
if scn.Conf.ReqLogEnabled {
|
||||
rlacc.InsertRequestLog(createRequestLog(g, t0, ctr, wrap, nil))
|
||||
}
|
||||
wrap.Write(g)
|
||||
}
|
||||
|
||||
@@ -62,6 +76,62 @@ func Wrap(fn WHandlerFunc) gin.HandlerFunc {
|
||||
|
||||
}
|
||||
|
||||
func createRequestLog(g *gin.Context, t0 time.Time, ctr int, resp HTTPResponse, panicstr *string) models.RequestLog {
|
||||
|
||||
t1 := time.Now()
|
||||
|
||||
ua := g.Request.UserAgent()
|
||||
auth := g.Request.Header.Get("Authorization")
|
||||
ct := g.Request.Header.Get("Content-Type")
|
||||
|
||||
var reqbody []byte = nil
|
||||
if g.Request.Body != nil {
|
||||
brcbody, err := g.Request.Body.(dataext.BufferedReadCloser).BufferedAll()
|
||||
if err == nil {
|
||||
reqbody = brcbody
|
||||
}
|
||||
}
|
||||
var strreqbody *string = nil
|
||||
if len(reqbody) < scn.Conf.ReqLogMaxBodySize {
|
||||
strreqbody = langext.Ptr(string(reqbody))
|
||||
}
|
||||
|
||||
var respbody *string = nil
|
||||
|
||||
var strrespbody *string = nil
|
||||
if resp != nil {
|
||||
respbody = resp.BodyString()
|
||||
if respbody != nil && len(*respbody) < scn.Conf.ReqLogMaxBodySize {
|
||||
strrespbody = respbody
|
||||
}
|
||||
}
|
||||
|
||||
permObj, hasPerm := g.Get("perm")
|
||||
|
||||
return models.RequestLog{
|
||||
Method: g.Request.Method,
|
||||
URI: g.Request.URL.String(),
|
||||
UserAgent: langext.Conditional(ua == "", nil, &ua),
|
||||
Authentication: langext.Conditional(auth == "", nil, &auth),
|
||||
RequestBody: strreqbody,
|
||||
RequestBodySize: int64(len(reqbody)),
|
||||
RequestContentType: ct,
|
||||
RemoteIP: g.RemoteIP(),
|
||||
UserID: langext.ConditionalFn10(hasPerm, func() *models.UserID { return permObj.(models.PermissionSet).UserID }, nil),
|
||||
Permissions: langext.ConditionalFn10(hasPerm, func() *string { return langext.Ptr(string(permObj.(models.PermissionSet).KeyType)) }, nil),
|
||||
ResponseStatuscode: langext.ConditionalFn10(resp != nil, func() *int64 { return langext.Ptr(int64(resp.Statuscode())) }, nil),
|
||||
ResponseBodySize: langext.ConditionalFn10(strrespbody != nil, func() *int64 { return langext.Ptr(int64(len(*respbody))) }, nil),
|
||||
ResponseBody: strrespbody,
|
||||
ResponseContentType: langext.ConditionalFn10(resp != nil, func() string { return resp.ContentType() }, ""),
|
||||
RetryCount: int64(ctr),
|
||||
Panicked: panicstr != nil,
|
||||
PanicStr: panicstr,
|
||||
ProcessingTime: t1.Sub(t0),
|
||||
TimestampStart: t0,
|
||||
TimestampFinish: t1,
|
||||
}
|
||||
}
|
||||
|
||||
func callPanicSafe(fn WHandlerFunc, g *gin.Context) (res HTTPResponse, panicObj any) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
|
||||
@@ -50,10 +50,10 @@ func (r *Router) Init(e *gin.Engine) {
|
||||
|
||||
commonAPI := e.Group("/api")
|
||||
{
|
||||
commonAPI.Any("/ping", ginresp.Wrap(r.commonHandler.Ping))
|
||||
commonAPI.POST("/db-test", ginresp.Wrap(r.commonHandler.DatabaseTest))
|
||||
commonAPI.GET("/health", ginresp.Wrap(r.commonHandler.Health))
|
||||
commonAPI.POST("/sleep/:secs", ginresp.Wrap(r.commonHandler.Sleep))
|
||||
commonAPI.Any("/ping", r.Wrap(r.commonHandler.Ping))
|
||||
commonAPI.POST("/db-test", r.Wrap(r.commonHandler.DatabaseTest))
|
||||
commonAPI.GET("/health", r.Wrap(r.commonHandler.Health))
|
||||
commonAPI.POST("/sleep/:secs", r.Wrap(r.commonHandler.Sleep))
|
||||
}
|
||||
|
||||
// ================ Swagger ================
|
||||
@@ -61,48 +61,48 @@ func (r *Router) Init(e *gin.Engine) {
|
||||
docs := e.Group("/documentation")
|
||||
{
|
||||
docs.GET("/swagger", ginext.RedirectTemporary("/documentation/swagger/"))
|
||||
docs.GET("/swagger/*sub", ginresp.Wrap(swagger.Handle))
|
||||
docs.GET("/swagger/*sub", r.Wrap(swagger.Handle))
|
||||
}
|
||||
|
||||
// ================ Website ================
|
||||
|
||||
frontend := e.Group("")
|
||||
{
|
||||
frontend.GET("/", ginresp.Wrap(r.websiteHandler.Index))
|
||||
frontend.GET("/index.php", ginresp.Wrap(r.websiteHandler.Index))
|
||||
frontend.GET("/index.html", ginresp.Wrap(r.websiteHandler.Index))
|
||||
frontend.GET("/index", ginresp.Wrap(r.websiteHandler.Index))
|
||||
frontend.GET("/", r.Wrap(r.websiteHandler.Index))
|
||||
frontend.GET("/index.php", r.Wrap(r.websiteHandler.Index))
|
||||
frontend.GET("/index.html", r.Wrap(r.websiteHandler.Index))
|
||||
frontend.GET("/index", r.Wrap(r.websiteHandler.Index))
|
||||
|
||||
frontend.GET("/api", ginresp.Wrap(r.websiteHandler.APIDocs))
|
||||
frontend.GET("/api.php", ginresp.Wrap(r.websiteHandler.APIDocs))
|
||||
frontend.GET("/api.html", ginresp.Wrap(r.websiteHandler.APIDocs))
|
||||
frontend.GET("/api", r.Wrap(r.websiteHandler.APIDocs))
|
||||
frontend.GET("/api.php", r.Wrap(r.websiteHandler.APIDocs))
|
||||
frontend.GET("/api.html", r.Wrap(r.websiteHandler.APIDocs))
|
||||
|
||||
frontend.GET("/api_more", ginresp.Wrap(r.websiteHandler.APIDocsMore))
|
||||
frontend.GET("/api_more.php", ginresp.Wrap(r.websiteHandler.APIDocsMore))
|
||||
frontend.GET("/api_more.html", ginresp.Wrap(r.websiteHandler.APIDocsMore))
|
||||
frontend.GET("/api_more", r.Wrap(r.websiteHandler.APIDocsMore))
|
||||
frontend.GET("/api_more.php", r.Wrap(r.websiteHandler.APIDocsMore))
|
||||
frontend.GET("/api_more.html", r.Wrap(r.websiteHandler.APIDocsMore))
|
||||
|
||||
frontend.GET("/message_sent", ginresp.Wrap(r.websiteHandler.MessageSent))
|
||||
frontend.GET("/message_sent.php", ginresp.Wrap(r.websiteHandler.MessageSent))
|
||||
frontend.GET("/message_sent.html", ginresp.Wrap(r.websiteHandler.MessageSent))
|
||||
frontend.GET("/message_sent", r.Wrap(r.websiteHandler.MessageSent))
|
||||
frontend.GET("/message_sent.php", r.Wrap(r.websiteHandler.MessageSent))
|
||||
frontend.GET("/message_sent.html", r.Wrap(r.websiteHandler.MessageSent))
|
||||
|
||||
frontend.GET("/favicon.ico", ginresp.Wrap(r.websiteHandler.FaviconIco))
|
||||
frontend.GET("/favicon.png", ginresp.Wrap(r.websiteHandler.FaviconPNG))
|
||||
frontend.GET("/favicon.ico", r.Wrap(r.websiteHandler.FaviconIco))
|
||||
frontend.GET("/favicon.png", r.Wrap(r.websiteHandler.FaviconPNG))
|
||||
|
||||
frontend.GET("/js/:fn", ginresp.Wrap(r.websiteHandler.Javascript))
|
||||
frontend.GET("/css/:fn", ginresp.Wrap(r.websiteHandler.CSS))
|
||||
frontend.GET("/js/:fn", r.Wrap(r.websiteHandler.Javascript))
|
||||
frontend.GET("/css/:fn", r.Wrap(r.websiteHandler.CSS))
|
||||
}
|
||||
|
||||
// ================ Compat (v1) ================
|
||||
|
||||
compat := e.Group("/api/")
|
||||
{
|
||||
compat.GET("/register.php", ginresp.Wrap(r.compatHandler.Register))
|
||||
compat.GET("/info.php", ginresp.Wrap(r.compatHandler.Info))
|
||||
compat.GET("/ack.php", ginresp.Wrap(r.compatHandler.Ack))
|
||||
compat.GET("/requery.php", ginresp.Wrap(r.compatHandler.Requery))
|
||||
compat.GET("/update.php", ginresp.Wrap(r.compatHandler.Update))
|
||||
compat.GET("/expand.php", ginresp.Wrap(r.compatHandler.Expand))
|
||||
compat.GET("/upgrade.php", ginresp.Wrap(r.compatHandler.Upgrade))
|
||||
compat.GET("/register.php", r.Wrap(r.compatHandler.Register))
|
||||
compat.GET("/info.php", r.Wrap(r.compatHandler.Info))
|
||||
compat.GET("/ack.php", r.Wrap(r.compatHandler.Ack))
|
||||
compat.GET("/requery.php", r.Wrap(r.compatHandler.Requery))
|
||||
compat.GET("/update.php", r.Wrap(r.compatHandler.Update))
|
||||
compat.GET("/expand.php", r.Wrap(r.compatHandler.Expand))
|
||||
compat.GET("/upgrade.php", r.Wrap(r.compatHandler.Upgrade))
|
||||
}
|
||||
|
||||
// ================ Manage API ================
|
||||
@@ -110,44 +110,48 @@ func (r *Router) Init(e *gin.Engine) {
|
||||
apiv2 := e.Group("/api/")
|
||||
{
|
||||
|
||||
apiv2.POST("/users", ginresp.Wrap(r.apiHandler.CreateUser))
|
||||
apiv2.GET("/users/:uid", ginresp.Wrap(r.apiHandler.GetUser))
|
||||
apiv2.PATCH("/users/:uid", ginresp.Wrap(r.apiHandler.UpdateUser))
|
||||
apiv2.POST("/users", r.Wrap(r.apiHandler.CreateUser))
|
||||
apiv2.GET("/users/:uid", r.Wrap(r.apiHandler.GetUser))
|
||||
apiv2.PATCH("/users/:uid", r.Wrap(r.apiHandler.UpdateUser))
|
||||
|
||||
apiv2.GET("/users/:uid/clients", ginresp.Wrap(r.apiHandler.ListClients))
|
||||
apiv2.GET("/users/:uid/clients/:cid", ginresp.Wrap(r.apiHandler.GetClient))
|
||||
apiv2.POST("/users/:uid/clients", ginresp.Wrap(r.apiHandler.AddClient))
|
||||
apiv2.DELETE("/users/:uid/clients/:cid", ginresp.Wrap(r.apiHandler.DeleteClient))
|
||||
apiv2.GET("/users/:uid/clients", r.Wrap(r.apiHandler.ListClients))
|
||||
apiv2.GET("/users/:uid/clients/:cid", r.Wrap(r.apiHandler.GetClient))
|
||||
apiv2.POST("/users/:uid/clients", r.Wrap(r.apiHandler.AddClient))
|
||||
apiv2.DELETE("/users/:uid/clients/:cid", r.Wrap(r.apiHandler.DeleteClient))
|
||||
|
||||
apiv2.GET("/users/:uid/channels", ginresp.Wrap(r.apiHandler.ListChannels))
|
||||
apiv2.POST("/users/:uid/channels", ginresp.Wrap(r.apiHandler.CreateChannel))
|
||||
apiv2.GET("/users/:uid/channels/:cid", ginresp.Wrap(r.apiHandler.GetChannel))
|
||||
apiv2.PATCH("/users/:uid/channels/:cid", ginresp.Wrap(r.apiHandler.UpdateChannel))
|
||||
apiv2.GET("/users/:uid/channels/:cid/messages", ginresp.Wrap(r.apiHandler.ListChannelMessages))
|
||||
apiv2.GET("/users/:uid/channels/:cid/subscriptions", ginresp.Wrap(r.apiHandler.ListChannelSubscriptions))
|
||||
apiv2.GET("/users/:uid/channels", r.Wrap(r.apiHandler.ListChannels))
|
||||
apiv2.POST("/users/:uid/channels", r.Wrap(r.apiHandler.CreateChannel))
|
||||
apiv2.GET("/users/:uid/channels/:cid", r.Wrap(r.apiHandler.GetChannel))
|
||||
apiv2.PATCH("/users/:uid/channels/:cid", r.Wrap(r.apiHandler.UpdateChannel))
|
||||
apiv2.GET("/users/:uid/channels/:cid/messages", r.Wrap(r.apiHandler.ListChannelMessages))
|
||||
apiv2.GET("/users/:uid/channels/:cid/subscriptions", r.Wrap(r.apiHandler.ListChannelSubscriptions))
|
||||
|
||||
apiv2.GET("/users/:uid/subscriptions", ginresp.Wrap(r.apiHandler.ListUserSubscriptions))
|
||||
apiv2.POST("/users/:uid/subscriptions", ginresp.Wrap(r.apiHandler.CreateSubscription))
|
||||
apiv2.GET("/users/:uid/subscriptions/:sid", ginresp.Wrap(r.apiHandler.GetSubscription))
|
||||
apiv2.DELETE("/users/:uid/subscriptions/:sid", ginresp.Wrap(r.apiHandler.CancelSubscription))
|
||||
apiv2.PATCH("/users/:uid/subscriptions/:sid", ginresp.Wrap(r.apiHandler.UpdateSubscription))
|
||||
apiv2.GET("/users/:uid/subscriptions", r.Wrap(r.apiHandler.ListUserSubscriptions))
|
||||
apiv2.POST("/users/:uid/subscriptions", r.Wrap(r.apiHandler.CreateSubscription))
|
||||
apiv2.GET("/users/:uid/subscriptions/:sid", r.Wrap(r.apiHandler.GetSubscription))
|
||||
apiv2.DELETE("/users/:uid/subscriptions/:sid", r.Wrap(r.apiHandler.CancelSubscription))
|
||||
apiv2.PATCH("/users/:uid/subscriptions/:sid", r.Wrap(r.apiHandler.UpdateSubscription))
|
||||
|
||||
apiv2.GET("/messages", ginresp.Wrap(r.apiHandler.ListMessages))
|
||||
apiv2.GET("/messages/:mid", ginresp.Wrap(r.apiHandler.GetMessage))
|
||||
apiv2.DELETE("/messages/:mid", ginresp.Wrap(r.apiHandler.DeleteMessage))
|
||||
apiv2.GET("/messages", r.Wrap(r.apiHandler.ListMessages))
|
||||
apiv2.GET("/messages/:mid", r.Wrap(r.apiHandler.GetMessage))
|
||||
apiv2.DELETE("/messages/:mid", r.Wrap(r.apiHandler.DeleteMessage))
|
||||
}
|
||||
|
||||
// ================ Send API ================
|
||||
|
||||
sendAPI := e.Group("")
|
||||
{
|
||||
sendAPI.POST("/", ginresp.Wrap(r.messageHandler.SendMessage))
|
||||
sendAPI.POST("/send", ginresp.Wrap(r.messageHandler.SendMessage))
|
||||
sendAPI.POST("/send.php", ginresp.Wrap(r.messageHandler.SendMessageCompat))
|
||||
sendAPI.POST("/", r.Wrap(r.messageHandler.SendMessage))
|
||||
sendAPI.POST("/send", r.Wrap(r.messageHandler.SendMessage))
|
||||
sendAPI.POST("/send.php", r.Wrap(r.messageHandler.SendMessageCompat))
|
||||
}
|
||||
|
||||
if r.app.Config.ReturnRawErrors {
|
||||
e.NoRoute(ginresp.Wrap(r.commonHandler.NoRoute))
|
||||
e.NoRoute(r.Wrap(r.commonHandler.NoRoute))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (r *Router) Wrap(fn ginresp.WHandlerFunc) gin.HandlerFunc {
|
||||
return ginresp.Wrap(r.app, fn)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user