-
Notifications
You must be signed in to change notification settings - Fork 2
/
handler.go
160 lines (133 loc) · 3.68 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
package clover
import (
"bytes"
"context"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"strings"
"time"
"github.com/go-chi/chi"
"github.com/nyaruka/rp-clover/models"
)
// handles an interchange request
func handleInterchange(s *Server, w http.ResponseWriter, r *http.Request) error {
interchangeUUID := chi.URLParam(r, "interchangeUUID")
// look up our interchange
interchange, err := models.GetInterchange(r.Context(), s.db, interchangeUUID)
if err != nil {
return err
}
if interchange == nil {
return writeErrorResponse(r.Context(), w, http.StatusNotFound, "interchange not found", fmt.Errorf("interchange not found"))
}
// get our URN from our incoming message
err = r.ParseForm()
if err != nil {
return err
}
sender := r.Form.Get("sender")
if sender == "" {
return writeErrorResponse(r.Context(), w, http.StatusBadRequest, "missing sender field", fmt.Errorf("missing sender field"))
}
urn := interchange.Scheme + ":+" + strings.TrimLeft(sender, "+")
// the channel we will route to
var routedChannel *models.Channel
var routingReason string
// get our text
message := r.Form.Get("message")
// see if our text is any of our keywords, if so, assign this URN to that channel
message = strings.ToLower(strings.TrimSpace(message))
for _, channel := range interchange.Channels {
for _, keyword := range channel.Keywords {
if message == keyword {
routedChannel = &channel
routingReason = fmt.Sprintf("keyword '%s'", keyword)
break
}
}
// we found a matching channel, associate this URN
if routedChannel != nil {
err := models.SetChannelForURN(r.Context(), s.db, interchange, routedChannel, urn)
if err != nil {
return err
}
break
}
}
// if not, look up current mapping for this URN
if routedChannel == nil {
routedChannel, err = models.GetChannelForURN(r.Context(), s.db, interchange, urn)
if err != nil {
return err
}
if routedChannel != nil {
routingReason = "urn mapping"
}
}
// didn't find any explicit routes, use our default chanel
if routedChannel == nil {
routedChannel = &interchange.Channels[0]
routingReason = "default channel"
}
slog.Info("forwarding request",
"interchange_uuid", interchange.UUID,
"channel_uuid", routedChannel.UUID,
"base_url", routedChannel.URL,
"urn", urn,
"message", message,
"routing_reason", routingReason,
)
return forwardRequest(r.Context(), w, r, interchange, routedChannel)
}
func forwardRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, interchange *models.Interchange, channel *models.Channel) error {
// parse our channel URL
queryPart := ""
if r.URL.RawQuery != "" {
queryPart = "?" + r.URL.RawQuery
}
outURL, err := url.Parse(channel.URL + queryPart)
if err != nil {
return err
}
// create our new outbound request
outRequest, err := http.NewRequest(r.Method, outURL.String(), bytes.NewReader([]byte(r.PostForm.Encode())))
if err != nil {
return err
}
// set any headers
outRequest.Header = r.Header
log := slog.With(
"channel_uuid", channel.UUID,
"url", outURL,
"method", outRequest.Method,
)
if r.Method == http.MethodPost {
log = log.With("form", r.PostForm)
}
// fire it off
resp, err := client.Do(outRequest)
if err != nil {
log.Error("error fowarding request", "error", err)
return err
}
log.Info("request forwarded", "status_code", resp.StatusCode)
// we respond in the same way our downstream server did
w.WriteHeader(resp.StatusCode)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
_, err = w.Write(body)
return err
}
var client *http.Client
func init() {
tr := &http.Transport{
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
}
client = &http.Client{Transport: tr}
}