-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandler.go
170 lines (154 loc) · 4.46 KB
/
handler.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package preview
import (
"context"
"encoding/base64"
"errors"
"io"
"mime"
"net/http"
"path"
"path/filepath"
"strings"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/redis/go-redis/v9"
)
func (s *Server) handleProxy(prefix string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s.logger.Info("proxying request", "prefix", prefix, "url", r.URL)
r.Host = s.proxyTarget.Host
s.proxy.ServeHTTP(w, r)
}
}
var ErrDir = errors.New("path is dir")
func (s *Server) tryRead(requestedPath string, w http.ResponseWriter) error {
f, err := s.staticFS.Open(path.Join("dist", requestedPath))
if err != nil {
return err
}
defer f.Close()
stat, _ := f.Stat()
if stat.IsDir() {
return ErrDir
}
contentType := mime.TypeByExtension(filepath.Ext(requestedPath))
w.Header().Set("Content-Type", contentType)
_, err = io.Copy(w, f)
return err
}
func (s *Server) notFoundHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s.logger.Info("received request on not found handler", "path", r.URL)
err := s.tryRead(r.URL.Path, w)
if err == nil {
s.logger.Info("file found; serving", "path", r.URL)
return
}
s.logger.Info("file not found; trying index.html next", "path", r.URL, "err", err)
err = s.tryRead("index.html", w)
if err != nil {
s.logger.Info("error reading index.html", "path", r.URL, "err", err)
w.WriteHeader(http.StatusNotFound)
}
}
}
func (s *Server) authKeyCheck(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authKey := r.Header.Get("X-CUSTOM-AUTH-KEY")
if authKey != s.authKey {
s.logger.Info("unauthorized request", "authkey", authKey)
w.WriteHeader(http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
func (s *Server) handleImageRequest(src string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
s.logger.Info("generate request", "src", src, "id", id)
if id == "" {
w.WriteHeader(http.StatusBadRequest)
return
}
//change id to include src
id = src + "/" + id
ctx, cancel := context.WithTimeout(r.Context(), s.generateTimeout)
defer cancel()
pubsub := s.rdb.Subscribe(ctx, id)
defer pubsub.Close()
res := s.rdb.Get(ctx, id)
s.logger.Info("got get from redis", "res_length", len(res.Val()))
switch res.Err() {
case nil:
if val := res.Val(); !strings.HasPrefix(val, "wip") {
s.logger.Info("id already in wip")
s.handleResult(val, w)
return
}
//wait for existing result
case redis.Nil:
s.logger.Info("id no result; starting new")
s.rdb.Set(ctx, id, "wip", s.generateTimeout)
go s.do(id)
default:
//exception case where something goes wrong with redis
s.logger.Info("unexpected get with non nil err", "id", id, "err", res.Err())
w.WriteHeader(http.StatusInternalServerError)
return
}
//wip, sub to topic == id and wait for ok or time out
ch := pubsub.Channel()
select {
case <-ctx.Done():
s.logger.Info("context done before receiving msg", "reason", ctx.Err())
w.WriteHeader(http.StatusRequestTimeout)
return
case msg := <-ch:
requestID := ctx.Value(middleware.RequestIDKey)
s.logger.Info("received msg from redis", "request_id", requestID, "msg", msg.Payload)
if msg.Payload != "done" {
//this is an error message so just skip getting
w.WriteHeader(http.StatusOK)
w.Write([]byte(msg.Payload))
return
}
//key must be available now?
res := s.rdb.Get(ctx, id)
if err := res.Err(); err != nil {
s.logger.Info("redis get unexpected err", "request_id", requestID, "err", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
//res can't be wip still
val := res.Val()
if strings.HasPrefix(val, "wip") {
s.logger.Info("unexpected res is still wip", "request_id", requestID)
w.WriteHeader(http.StatusInternalServerError)
return
}
s.handleResult(val, w)
return
}
}
}
func (s *Server) handleResult(val string, w http.ResponseWriter) {
if strings.HasPrefix(val, "error: ") {
val = strings.TrimPrefix(val, "error: ")
w.WriteHeader(http.StatusUnprocessableEntity)
w.Write([]byte(val))
return
}
data, err := decode(val)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(data)
}
func encode(data []byte) string {
return base64.StdEncoding.EncodeToString(data)
}
func decode(data string) ([]byte, error) {
return base64.StdEncoding.DecodeString(data)
}