/
answer.go
122 lines (100 loc) · 2.19 KB
/
answer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package rucaptcha
import (
"context"
"math"
"net/http"
"sync"
"time"
"github.com/jfk9w-go/based"
"github.com/pkg/errors"
)
type answerer interface {
answer(ctx context.Context, requestID string) (string, error)
}
type resClient interface {
res(ctx context.Context, in resIn) (string, error)
}
type answerPoller struct {
client resClient
}
func (p *answerPoller) answer(ctx context.Context, requestID string) (string, error) {
in := &resGetIn{
ID: requestID,
}
timeout := 10 * time.Second
for {
select {
case <-time.After(timeout):
result, err := p.client.res(ctx, in)
if err == nil {
return result, nil
}
var clientErr Error
if errors.As(err, &clientErr) && clientErr.Code != "CAPCHA_NOT_READY" {
return "", err
}
timeout = time.Duration(math.Max(float64(timeout)/2, 2))
case <-ctx.Done():
return "", ctx.Err()
}
}
}
type asyncAnswer struct {
c chan string
created time.Time
}
type answerListener struct {
clock based.Clock
answers map[string]asyncAnswer
mu sync.Mutex
}
func newAsyncListener(clock based.Clock) *answerListener {
return &answerListener{
clock: clock,
answers: make(map[string]asyncAnswer),
}
}
func (pb *answerListener) ServeHTTP(w http.ResponseWriter, req *http.Request) {
id := req.FormValue("id")
code := req.FormValue("code")
if id == "" || code == "" {
w.WriteHeader(http.StatusBadRequest)
return
}
answer := pb.getAsyncAnswer(id)
select {
case answer.c <- code:
w.WriteHeader(http.StatusOK)
case <-req.Context().Done():
w.WriteHeader(http.StatusUnprocessableEntity)
}
}
func (pb *answerListener) answer(ctx context.Context, id string) (string, error) {
answer := pb.getAsyncAnswer(id)
select {
case result := <-answer.c:
return result, nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func (pb *answerListener) getAsyncAnswer(id string) asyncAnswer {
pb.mu.Lock()
defer pb.mu.Unlock()
now := pb.clock.Now()
ans, ok := pb.answers[id]
if !ok {
ans = asyncAnswer{
c: make(chan string, 1),
created: now,
}
pb.answers[id] = ans
}
for id, ans := range pb.answers {
if now.Sub(ans.created) > 5*time.Minute {
close(ans.c)
delete(pb.answers, id)
}
}
return ans
}