Merge branch 'refs/heads/development' into 52-limit-rproxy-logs

# Conflicts:
#	frontend/nginx.conf
#	reverseproxy.conf
This commit is contained in:
Elmar Kresse
2024-10-06 23:49:28 +02:00
14 changed files with 95 additions and 17 deletions

View File

@ -33,6 +33,8 @@ services:
- DATA_MANAGER_URL=htwkalender-data-manager - DATA_MANAGER_URL=htwkalender-data-manager
networks: networks:
- "net" - "net"
depends_on:
- htwkalender-data-manager
htwkalender-frontend: htwkalender-frontend:
image: DOCKER_REGISTRY_REPO-frontend # DOCKER_REGISTRY_REPO will be replaced by CI image: DOCKER_REGISTRY_REPO-frontend # DOCKER_REGISTRY_REPO will be replaced by CI

View File

@ -33,6 +33,8 @@ services:
- DATA_MANAGER_URL=htwkalender-data-manager - DATA_MANAGER_URL=htwkalender-data-manager
networks: networks:
- "net" - "net"
depends_on:
- htwkalender-data-manager
htwkalender-frontend: htwkalender-frontend:
image: DOCKER_REGISTRY_REPO-frontend # DOCKER_REGISTRY_REPO will be replaced by CI image: DOCKER_REGISTRY_REPO-frontend # DOCKER_REGISTRY_REPO will be replaced by CI

View File

@ -35,6 +35,8 @@ services:
target: dev # prod target: dev # prod
environment: environment:
- DATA_MANAGER_URL=htwkalender-data-manager - DATA_MANAGER_URL=htwkalender-data-manager
depends_on:
- htwkalender-data-manager
htwkalender-frontend: htwkalender-frontend:
build: build:

View File

@ -27,11 +27,10 @@ http {
include mime.types; include mime.types;
default_type application/octet-stream; default_type application/octet-stream;
# Define a custom log format for anonymizing logs
log_format anonymized '[$time_local] "$request" $status $body_bytes_sent "$http_referer"'; log_format anonymized '[$time_local] "$request" $status $body_bytes_sent "$http_referer"';
access_log /opt/bitnami/nginx/logs/proxy_access.log anonymized; access_log /opt/bitnami/nginx/logs/proxy_access.log anonymized;
error_log /opt/bitnami/nginx/logs/proxy_error.log; error_log /opt/bitnami/nginx/logs/proxy_error.log error;
sendfile on; sendfile on;
keepalive_timeout 180s; keepalive_timeout 180s;

View File

@ -59,11 +59,10 @@ http {
real_ip_header CF-Connecting-IP; real_ip_header CF-Connecting-IP;
# Define a custom log format for anonymizing logs
log_format anonymized '[$time_local] "$request" $status $body_bytes_sent "$http_referer"'; log_format anonymized '[$time_local] "$request" $status $body_bytes_sent "$http_referer"';
access_log /opt/bitnami/nginx/logs/proxy_access.log anonymized; access_log /opt/bitnami/nginx/logs/proxy_access.log anonymized;
error_log /opt/bitnami/nginx/logs/proxy_error.log; error_log /opt/bitnami/nginx/logs/proxy_error.log error;
sendfile on; sendfile on;
keepalive_timeout 180s; keepalive_timeout 180s;

View File

@ -60,8 +60,10 @@ http {
real_ip_header CF-Connecting-IP; real_ip_header CF-Connecting-IP;
access_log /opt/bitnami/nginx/logs/proxy_access.log; log_format anonymized '[$time_local] "$request" $status $body_bytes_sent "$http_referer"';
error_log /opt/bitnami/nginx/logs/proxy_error.log;
access_log /opt/bitnami/nginx/logs/proxy_access.log anonymized;
error_log /opt/bitnami/nginx/logs/proxy_error.log error;
sendfile on; sendfile on;
keepalive_timeout 180s; keepalive_timeout 180s;

View File

@ -10,6 +10,12 @@ events {
http { http {
include mime.types; include mime.types;
default_type application/octet-stream; default_type application/octet-stream;
gzip on;
log_format anonymized '[$time_local] "$request" $status $body_bytes_sent "$http_referer"';
access_log /opt/bitnami/nginx/logs/proxy_access.log anonymized;
error_log /opt/bitnami/nginx/logs/proxy_error.log error;
map $request_method $ratelimit_key { map $request_method $ratelimit_key {
POST $binary_remote_addr; POST $binary_remote_addr;

View File

@ -2,8 +2,10 @@ package grpc
import ( import (
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"google.golang.org/grpc/keepalive"
"log" "log"
"net" "net"
"time"
"google.golang.org/grpc" "google.golang.org/grpc"
pb "htwkalender/common/genproto/modules" pb "htwkalender/common/genproto/modules"
@ -14,7 +16,20 @@ func StartGRPCServer(app *pocketbase.PocketBase) {
if err != nil { if err != nil {
log.Fatalf("failed to listen: %v", err) log.Fatalf("failed to listen: %v", err)
} }
s := grpc.NewServer() s := grpc.NewServer(
grpc.KeepaliveParams(keepalive.ServerParameters{
MaxConnectionIdle: 5 * time.Minute, // Idle timeout before closing connection
MaxConnectionAge: 30 * time.Minute, // Max time before connection is closed
MaxConnectionAgeGrace: 5 * time.Minute, // Allow grace period before closing
Time: 2 * time.Minute, // Ping the client every 2 minutes
Timeout: 20 * time.Second, // Wait 20 seconds for ping ack
}),
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: 1 * time.Minute, // Minimum time between pings from clients
PermitWithoutStream: true, // Don't allow pings when there are no active RPCs
}),
grpc.MaxConcurrentStreams(0),
)
pb.RegisterModuleServiceServer(s, &ModuleServiceHandler{ pb.RegisterModuleServiceServer(s, &ModuleServiceHandler{
app: app, app: app,

View File

@ -39,8 +39,15 @@ func main() {
} }
grpcClient := grpc.ConnectGRPCServer(host) grpcClient := grpc.ConnectGRPCServer(host)
// Close the grpc connection when the main function ends
defer grpc.CloseGRPCServer(grpcClient) defer grpc.CloseGRPCServer(grpcClient)
// Log the grpc connection
// Test the connection to the grpc server
grpcClient.Connect()
slog.Info("GRPC connection state", "state", grpcClient.GetState())
// Initialize a new Fiber app // Initialize a new Fiber app
webdavRequestMethods := []string{"PROPFIND", "MKCOL", "COPY", "MOVE"} webdavRequestMethods := []string{"PROPFIND", "MKCOL", "COPY", "MOVE"}

View File

@ -37,6 +37,14 @@ func (events Events) Sort() {
}) })
} }
func (events Events) String() string {
var str strings.Builder
for _, event := range events {
str.WriteString(event.String())
}
return str.String()
}
type AnonymizedEventDTO struct { type AnonymizedEventDTO struct {
Day string `db:"Day" json:"day"` Day string `db:"Day" json:"day"`
Week string `db:"Week" json:"week"` Week string `db:"Week" json:"week"`
@ -109,3 +117,7 @@ func (e *Event) GetName() string {
func (e *Event) SetName(name string) { func (e *Event) SetName(name string) {
e.Name = name e.Name = name
} }
func (e *Event) String() string {
return e.UUID + e.Day + e.Week + e.Start.String() + e.End.String() + e.Name + e.EventType + e.Compulsory + e.Prof + e.Rooms + e.Notes + e.BookedAt + e.Course + e.Semester
}

View File

@ -92,3 +92,7 @@ func ToJSONTime(timeString string) JSONTime {
} }
return JSONTime(t) return JSONTime(t)
} }
func (j JSONTime) String() string {
return time.Time(j).Format(DefaultDateLayout)
}

View File

@ -19,14 +19,31 @@ package grpc
import ( import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"log/slog" "log/slog"
"sync"
"time"
) )
var conn *grpc.ClientConn
var once sync.Once
func ConnectGRPCServer(host string) *grpc.ClientConn { func ConnectGRPCServer(host string) *grpc.ClientConn {
conn, err := grpc.NewClient(host+":50051", grpc.WithTransportCredentials(insecure.NewCredentials())) once.Do(func() {
var err error
conn, err = grpc.NewClient(
host+":50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 2 * time.Minute,
Timeout: 20 * time.Second,
PermitWithoutStream: true,
}),
)
if err != nil { if err != nil {
slog.Error("could not connect to grpc server", "error", err) slog.Error("could not connect to grpc server", "error", err)
} }
})
return conn return conn
} }

View File

@ -23,6 +23,7 @@ import (
"htwkalender/ical/model" "htwkalender/ical/model"
"htwkalender/ical/service/connector" "htwkalender/ical/service/connector"
htwkalenderGrpc "htwkalender/ical/service/connector/grpc" htwkalenderGrpc "htwkalender/ical/service/connector/grpc"
"htwkalender/ical/service/functions"
"log/slog" "log/slog"
"time" "time"
) )
@ -31,7 +32,7 @@ const expirationTime = 5 * time.Minute
var FeedDeletedError = fmt.Errorf("feed deleted") var FeedDeletedError = fmt.Errorf("feed deleted")
func Feed(app model.AppType, token string) (string, error) { func Feed(app model.AppType, token string) (string, string, error) {
var events model.Events var events model.Events
modules := map[string]model.FeedCollection{} modules := map[string]model.FeedCollection{}
@ -47,17 +48,17 @@ func Feed(app model.AppType, token string) (string, error) {
// get feed by token // get feed by token
feed, err := htwkalenderGrpc.GetFeed(token, app.GrpcClient) feed, err := htwkalenderGrpc.GetFeed(token, app.GrpcClient)
if err != nil { if err != nil {
return "", err return "", "", err
} }
if feed.Deleted { if feed.Deleted {
return "", FeedDeletedError return "", "", FeedDeletedError
} }
// Get all events for modules // Get all events for modules
events, err = htwkalenderGrpc.GetEvents(feed.Modules, app.GrpcClient) events, err = htwkalenderGrpc.GetEvents(feed.Modules, app.GrpcClient)
if err != nil { if err != nil {
return "", err return "", "", err
} }
// Sort events by start date // Sort events by start date
@ -68,11 +69,14 @@ func Feed(app model.AppType, token string) (string, error) {
} }
} }
// Generate one Hash for E-TAG from all events and modules
etag := functions.HashString(events.String() + fmt.Sprint(modules))
b := bytes.Buffer{} b := bytes.Buffer{}
goics.NewICalEncode(&b).Encode(IcalModel{Events: events, Mapping: modules}) goics.NewICalEncode(&b).Encode(IcalModel{Events: events, Mapping: modules})
icalFeed := &model.FeedModel{Content: b.String(), ExpiresAt: model.JSONTime(time.Now().Add(expirationTime))} icalFeed := &model.FeedModel{Content: b.String(), ExpiresAt: model.JSONTime(time.Now().Add(expirationTime))}
return icalFeed.Content, nil return icalFeed.Content, etag, nil
} }
func FeedRecord(app model.AppType, token string) (model.FeedRecord, error) { func FeedRecord(app model.AppType, token string) (model.FeedRecord, error) {

View File

@ -34,7 +34,8 @@ func AddFeedRoutes(app model.AppType) {
app.Fiber.Get("/api/feed", func(c fiber.Ctx) error { app.Fiber.Get("/api/feed", func(c fiber.Ctx) error {
token := c.Query("token") token := c.Query("token")
results, err := ical.Feed(app, token) ifNoneMatch := c.Get("If-None-Match")
results, eTag, err := ical.Feed(app, token)
if errors.Is(err, ical.FeedDeletedError) { if errors.Is(err, ical.FeedDeletedError) {
return c.SendStatus(fiber.StatusGone) return c.SendStatus(fiber.StatusGone)
@ -44,10 +45,16 @@ func AddFeedRoutes(app model.AppType) {
slog.Error("Failed to get feed", "error", err, "token", token) slog.Error("Failed to get feed", "error", err, "token", token)
return c.SendStatus(fiber.StatusNotFound) return c.SendStatus(fiber.StatusNotFound)
} }
if ifNoneMatch == eTag && ifNoneMatch != "" {
return c.SendStatus(fiber.StatusNotModified)
}
c.Response().Header.Set("Content-type", "text/calendar") c.Response().Header.Set("Content-type", "text/calendar")
c.Response().Header.Set("charset", "utf-8") c.Response().Header.Set("charset", "utf-8")
c.Response().Header.Set("Content-Disposition", "inline") c.Response().Header.Set("Content-Disposition", "inline")
c.Response().Header.Set("filename", "calendar.ics") c.Response().Header.Set("filename", "calendar.ics")
c.Response().Header.Set("ETag", eTag)
return c.SendString(results) return c.SendString(results)
}) })