-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrequest.go
More file actions
191 lines (164 loc) · 4.85 KB
/
Copy pathrequest.go
File metadata and controls
191 lines (164 loc) · 4.85 KB
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package compass
import (
"bytes"
"errors"
"net/http"
"net/url"
"slices"
"strings"
"time"
)
type Request struct {
Method string
URL *url.URL
Route *Route
Http *http.Request
}
// NewRequestFromHttp constructs a Request from a standard http.Request.
//
// The HTTP method is normalized to lowercase. The Route field is not
// populated and must be assigned later during routing.
func NewRequestFromHttp(r *http.Request) Request {
return Request{
Method: strings.ToLower(r.Method),
URL: r.URL,
Http: r,
}
}
// writeResponse writes the headers, content type, status code, and body
// of a Response to the client.
//
// Headers prefixed with "--COMPASS" are skipped. This is the shared
// path for all standard responses.
func (s *Server) writeResponse(w http.ResponseWriter, r Request, resp Response) error {
s.writeCookies(w, resp.cookies)
for key, value := range resp.Headers {
if strings.HasPrefix(key, "--COMPASS") {
continue
}
w.Header().Set(key, value)
}
if resp.ContentType != nil {
w.Header().Set("Content-Type", *resp.ContentType)
} else {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
}
return s.write(w, r.Http, resp.Body, resp.StatusCode)
}
// handleRequest processes an incoming Request and writes the response.
//
// If no route is matched, it delegates to handleNotFound. Otherwise,
// it executes the route handler and writes the resulting response,
// including headers, status code, and body.
//
// Special internal ContentType values control behavior:
//
// "--COMPASS-redirect": performs an HTTP redirect
// "--COMPASS-serve": serves content as a file
//
// Headers prefixed with "--COMPASS" are ignored. All successful
// responses are logged. If the handler signals an internal error,
// it is returned.
func (s *Server) handleRequest(w http.ResponseWriter, r Request) error {
if r.Route == nil {
return s.writeResponse(w, r, s.NotFoundHandler(r))
}
if !slices.Contains(r.Route.AllowedMethods, r.Method) {
return s.writeResponse(w, r, s.MethodNotAllowedHandler(r))
}
pResp := s.Preprocessor(r)
var resp Response
if pResp != nil {
resp = *pResp
} else {
resp = r.Route.handler(r)
}
if resp.internalError {
return errors.New(string(resp.Body))
}
if resp.ContentType != nil {
switch *resp.ContentType {
case "--COMPASS-redirect":
s.writeCookies(w, resp.cookies)
http.Redirect(w, r.Http, string(resp.Body), resp.StatusCode)
s.Logger.Request(r.Http, resp.StatusCode)
return nil
case "--COMPASS-serve":
rs := bytes.NewReader(resp.Body)
s.writeCookies(w, resp.cookies)
http.ServeContent(w, r.Http, resp.Headers["-Compass-File-Name"], time.Now(), rs)
s.Logger.Request(r.Http, resp.StatusCode)
return nil
}
}
return s.writeResponse(w, r, resp)
}
// GetRouteParam returns the value of a named route parameter.
//
// The parameter is resolved using the route's internal mapping and
// extracted from the URL path. Any defined prefix or suffix on the
// route part is removed before returning the value.
//
// If the parameter does not exist, the route is not set, or the index
// is out of bounds, an empty string and false are returned.
func (r *Request) GetRouteParam(id string) (string, bool) {
if len(id) < 1 {
return "", false
}
index, ok := r.Route.partIdMap[id]
if !ok {
return "", false
}
split := splitUrlPath(r.URL.Path)
if index > len(split)-1 {
return "", false
}
part := r.Route.parts[index]
value := split[index]
value = strings.TrimPrefix(value, part.prefix)
value = strings.TrimSuffix(value, part.suffix)
return value, true
}
// GetCookie returns the value of the named cookie from the incoming request.
//
// The second return value is false if no cookie with that name was sent.
// Note that incoming cookies carry only name and value and no attributes
// like Path, Expires or HttpOnly.
func (r *Request) GetCookie(name string) (string, bool) {
c, err := r.Http.Cookie(name)
if err != nil {
return "", false
}
return c.Value, true
}
// GetCookies returns all cookies sent with the request as a name-value map.
//
// If the same cookie name appears more than once, the last value wins.
// Note that incoming cookies carry only name and value and no attributes
// like Path, Expires or HttpOnly.
func (r *Request) GetCookies() map[string]string {
result := make(map[string]string)
for _, c := range r.Http.Cookies() {
result[c.Name] = c.Value
}
return result
}
// GetSession returns the session by the value of the _compassId cookie from
// the incoming request.
//
// The second return value is false if no valid session was found,
// no cookie was set, or the session was destroyed.
func (r *Request) GetSession(server *Server) (*Session, bool) {
cookie, ok := r.GetCookie("_compassId")
if !ok {
return nil, false
}
session, ok := server.sessions[cookie]
if !ok {
return nil, false
}
if session.destroyed {
return nil, false
}
return session, true
}