Throttling with bug
This commit is contained in:
commit
07c3e411e9
|
|
@ -0,0 +1 @@
|
|||
.idea
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/kordondev/meeting/throttling"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
const burstLimit = 2
|
||||
|
||||
func main() {
|
||||
throttles := make(map[string]<-chan time.Time)
|
||||
calledLastHour := make([]string, 0)
|
||||
|
||||
http.HandleFunc("/", serveIndexFile)
|
||||
http.HandleFunc("/input.txt", serveInputFile)
|
||||
http.HandleFunc("/result", func(w http.ResponseWriter, r *http.Request) {
|
||||
tryResult(w, r, throttles, calledLastHour)
|
||||
})
|
||||
|
||||
err := http.ListenAndServe(":3333", nil)
|
||||
if err != nil {
|
||||
fmt.Printf("http.ListenAndServe() failed with %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func serveIndexFile(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "static/index.html")
|
||||
}
|
||||
|
||||
func serveInputFile(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println(r.RemoteAddr)
|
||||
http.ServeFile(w, r, "static/input.txt")
|
||||
}
|
||||
|
||||
func tryResult(w http.ResponseWriter, r *http.Request, throttles map[string]<-chan time.Time, calledLastHour []string) {
|
||||
clientIP := r.RemoteAddr
|
||||
clientResult := r.URL.Query().Get("result")
|
||||
fmt.Println(clientIP, clientResult)
|
||||
if slices.Contains(calledLastHour, clientIP) {
|
||||
calledLastHour = append(calledLastHour, clientIP)
|
||||
}
|
||||
throttle, ok := throttles[clientIP]
|
||||
if !ok {
|
||||
fmt.Println("Creating new throttle")
|
||||
throttle = throttling.CreateThrottle(r.Context(), burstLimit)
|
||||
throttles[clientIP] = throttle
|
||||
}
|
||||
payload := throttling.Payload{
|
||||
R: r,
|
||||
W: w,
|
||||
ClientResult: clientResult,
|
||||
}
|
||||
|
||||
throttling.CallFunction(context.TODO(), &CheckResult{}, &payload, throttle)
|
||||
}
|
||||
|
||||
type CheckResult struct {
|
||||
}
|
||||
|
||||
func (*CheckResult) Call(payload *throttling.Payload) {
|
||||
w := payload.W
|
||||
r := payload.R
|
||||
|
||||
fmt.Println("Serve now")
|
||||
if payload.ClientResult == "12" {
|
||||
http.ServeFile(w, r, "static/success.html")
|
||||
}
|
||||
http.ServeFile(w, r, "static/fail.html")
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Nicht so schnell</title>
|
||||
</head>
|
||||
<body>
|
||||
fail
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Informatikertreffen</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<form action="/result">
|
||||
<label for="result">Name:</label>
|
||||
<input type="text" id="result" name="result">
|
||||
<br>
|
||||
<button type="submit">Abgeben</button>
|
||||
</form>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,11 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Willkommen</title>
|
||||
</head>
|
||||
<body>
|
||||
Success
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
Die Türsteher veranstallten ein Schere-Stein-Papier-Turnier zwischen den Wartenden. Nur die Besten dürfen zu dem Treffen.
|
||||
|
||||
Schere-Stein-Papier ist ein Spiel zwischen zwei Spielern. Jedes Spiel besteht aus mehreren Runden; in jeder Runde wählen die Spieler gleichzeitig eine der Optionen Stein, Papier oder Schere durch eine Handgeste. Dann wird ein Gewinner für die Runde bestimmt: Stein schlägt Schere, Schere schlägt Papier und Papier schlägt Stein. Wählen beide Spieler die gleiche Form, endet die Runde unentschieden.
|
||||
|
||||
Plötzlich bekommst du von einer unbekannten Nummer: Einen verschlüsselten Strategie-Leitfaden (deine Puzzle-Eingabe), von dem gesagt wird, dass er dir sicher helfen wird zu gewinnen. „Die erste Spalte zeigt, was dein Gegner spielen wird: A steht für Stein, B für Papier und C für Schere. Die zweite Spalte—“ Plötzlich ein Funkloch und der Rest der Nachricht fehlt.
|
||||
|
||||
Du folgerst, dass die zweite Spalte das zeigt, was du als Reaktion spielen solltest: A für Stein, B für Papier und C für Schere. Immer zu gewinnen wäre verdächtig, also müssen die Antworten sorgfältig gewählt worden sein.
|
||||
|
||||
Der Gewinner des gesamten Turniers ist der Spieler mit der höchsten Punktzahl. Deine Gesamtpunktzahl ist die Summe deiner Punkte für jede Runde. Die Punktzahl für eine einzelne Runde setzt sich zusammen aus der Punktzahl für die gewählte Form (1 für Stein, 2 für Papier und 3 für Schere) plus der Punktzahl für das Ergebnis der Runde (0, wenn du verloren hast, 3, wenn die Runde unentschieden endete, und 6, wenn du gewonnen hast).
|
||||
|
||||
Da du dir nicht sicher sein kannst, ob dir die unbekannte Person wirklich helfen will oder dich täuschen möchte, solltest du die Punktzahl berechnen, die du erreichen würdest, wenn du den Strategie-Leitfaden befolgst.
|
||||
|
||||
Zum Beispiel, nehmen wir an, du bekommst den folgenden Strategie-Leitfaden:
|
||||
|
||||
A B
|
||||
B A
|
||||
C C
|
||||
|
||||
Dieser Strategie-Leitfaden sagt Folgendes voraus und empfiehlt:
|
||||
|
||||
• In der ersten Runde wird dein Gegner Stein (A) wählen, und du solltest Papier (B) wählen. Dies führt zu einem Sieg für dich mit einer Punktzahl von 8 (2 Punkte, weil du Papier gewählt hast + 6 Punkte für den Sieg).
|
||||
• In der zweiten Runde wird dein Gegner Papier (B) wählen, und du solltest Stein (A) wählen. Dies führt zu einer Niederlage für dich mit einer Punktzahl von 1 (1 Punkt + 0 Punkte).
|
||||
• Die dritte Runde ist ein Unentschieden, da beide Spieler Schere wählen, was dir eine Punktzahl von 6 gibt (3 Punkte für die Wahl von Schere + 3 Punkte für das Unentschieden).
|
||||
|
||||
In diesem Beispiel würdest du, wenn du dem Strategie-Leitfaden folgst, eine Gesamtpunktzahl von 15 erreichen (8 + 1 + 6).
|
||||
|
||||
Was wäre deine Gesamtpunktzahl, wenn alles genau nach dem Strategie-Leitfaden verläuft?
|
||||
|
||||
<form>
|
||||
|
||||
Ich hoffe, diese Übersetzung hilft dir weiter!
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package throttling
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const rateLimit = time.Second * 3 // a call each 3 second
|
||||
|
||||
// Client is an interface that calls something with a payload.
|
||||
type Client interface {
|
||||
Call(*Payload)
|
||||
}
|
||||
|
||||
// Payload is some payload a Client would send in a call.
|
||||
type Payload struct {
|
||||
ClientResult string
|
||||
W http.ResponseWriter
|
||||
R *http.Request
|
||||
}
|
||||
|
||||
// CallFunction allows burst rate limiting client calls with the
|
||||
// payloads.
|
||||
func CallFunction(ctx context.Context, client Client, payload *Payload, throttle <-chan time.Time) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
<-throttle // rate limit our client calls
|
||||
client.Call(payload)
|
||||
}
|
||||
|
||||
func CreateThrottle(ctx context.Context, burstLimit int) <-chan time.Time {
|
||||
throttle := make(chan time.Time, burstLimit)
|
||||
for i := 0; i < burstLimit; i++ {
|
||||
throttle <- time.Now()
|
||||
}
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(rateLimit)
|
||||
defer ticker.Stop()
|
||||
for t := range ticker.C {
|
||||
select {
|
||||
case throttle <- t:
|
||||
case <-ctx.Done():
|
||||
{
|
||||
fmt.Println("Ticker done")
|
||||
return // exit goroutine when surrounding function returns
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return throttle
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package throttling
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCallFunction(t *testing.T) {
|
||||
client := &Callable{}
|
||||
// create context for app
|
||||
ctx := context.Background()
|
||||
|
||||
fmt.Println("Starting")
|
||||
throttle := CreateThrottle(ctx, 2)
|
||||
CallFunction(ctx, client, &Payload{}, throttle)
|
||||
CallFunction(ctx, client, &Payload{}, throttle)
|
||||
CallFunction(ctx, client, &Payload{}, throttle)
|
||||
time.Sleep(2 * time.Second)
|
||||
fmt.Println("Continuing")
|
||||
CallFunction(ctx, client, &Payload{}, throttle)
|
||||
CallFunction(ctx, client, &Payload{}, throttle)
|
||||
CallFunction(ctx, client, &Payload{}, throttle)
|
||||
}
|
||||
|
||||
type Callable struct {
|
||||
}
|
||||
|
||||
func (c *Callable) Call(p *Payload) {
|
||||
fmt.Println("Called with payload")
|
||||
}
|
||||
Loading…
Reference in New Issue