Skip to content

Commit ae4ec5f

Browse files
joeybloggsjoeybloggs
authored andcommitted
add option to automatically handle OPTIONS requests, SetAutomaticallyHandleOPTIONS
1 parent 5aa0582 commit ae4ec5f

File tree

4 files changed

+153
-68
lines changed

4 files changed

+153
-68
lines changed

README.md

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ l.SetRedirectTrailingSlash(true)
188188
// Handle 405 ( Method Not allowed ), default is false
189189
l.SetHandle405MethodNotAllowed(false)
190190

191+
// automatically handle OPTION requests; manually configured
192+
// OPTION handlers take precedence. default true
193+
l.SetAutomaticallyHandleOPTIONS(set bool)
194+
191195
// register custom context
192196
l.RegisterContext(ContextFunc)
193197

@@ -225,34 +229,34 @@ NOTICE: lars uses a custom version of [httprouter](https://github.com/julienschm
225229
```go
226230
go test -bench=. -benchmem=true
227231
#GithubAPI Routes: 203
228-
LARS: 49016 Bytes
232+
LARS: 49040 Bytes
229233

230234
#GPlusAPI Routes: 13
231-
LARS: 3624 Bytes
235+
LARS: 3648 Bytes
232236

233237
#ParseAPI Routes: 26
234-
LARS: 6616 Bytes
238+
LARS: 6640 Bytes
235239

236240
#Static Routes: 157
237-
LARS: 30104 Bytes
241+
LARS: 30128 Bytes
238242

239243
PASS
240-
BenchmarkLARS_Param 20000000 75.3 ns/op 0 B/op 0 allocs/op
241-
BenchmarkLARS_Param5 10000000 126 ns/op 0 B/op 0 allocs/op
242-
BenchmarkLARS_Param20 5000000 311 ns/op 0 B/op 0 allocs/op
243-
BenchmarkLARS_ParamWrite 10000000 144 ns/op 0 B/op 0 allocs/op
244-
BenchmarkLARS_GithubStatic 20000000 101 ns/op 0 B/op 0 allocs/op
245-
BenchmarkLARS_GithubParam 10000000 154 ns/op 0 B/op 0 allocs/op
246-
BenchmarkLARS_GithubAll 50000 33295 ns/op 0 B/op 0 allocs/op
247-
BenchmarkLARS_GPlusStatic 20000000 72.4 ns/op 0 B/op 0 allocs/op
248-
BenchmarkLARS_GPlusParam 20000000 99.6 ns/op 0 B/op 0 allocs/op
249-
BenchmarkLARS_GPlus2Params 10000000 124 ns/op 0 B/op 0 allocs/op
250-
BenchmarkLARS_GPlusAll 1000000 1640 ns/op 0 B/op 0 allocs/op
251-
BenchmarkLARS_ParseStatic 20000000 73.9 ns/op 0 B/op 0 allocs/op
252-
BenchmarkLARS_ParseParam 20000000 79.9 ns/op 0 B/op 0 allocs/op
253-
BenchmarkLARS_Parse2Params 20000000 97.1 ns/op 0 B/op 0 allocs/op
254-
BenchmarkLARS_ParseAll 500000 2974 ns/op 0 B/op 0 allocs/op
255-
BenchmarkLARS_StaticAll 50000 23641 ns/op 0 B/op 0 allocs/op
244+
BenchmarkLARS_Param 20000000 77.1 ns/op 0 B/op 0 allocs/op
245+
BenchmarkLARS_Param5 10000000 134 ns/op 0 B/op 0 allocs/op
246+
BenchmarkLARS_Param20 5000000 320 ns/op 0 B/op 0 allocs/op
247+
BenchmarkLARS_ParamWrite 10000000 142 ns/op 0 B/op 0 allocs/op
248+
BenchmarkLARS_GithubStatic 20000000 96.2 ns/op 0 B/op 0 allocs/op
249+
BenchmarkLARS_GithubParam 10000000 156 ns/op 0 B/op 0 allocs/op
250+
BenchmarkLARS_GithubAll 50000 32952 ns/op 0 B/op 0 allocs/op
251+
BenchmarkLARS_GPlusStatic 20000000 72.2 ns/op 0 B/op 0 allocs/op
252+
BenchmarkLARS_GPlusParam 20000000 98.0 ns/op 0 B/op 0 allocs/op
253+
BenchmarkLARS_GPlus2Params 10000000 127 ns/op 0 B/op 0 allocs/op
254+
BenchmarkLARS_GPlusAll 1000000 1619 ns/op 0 B/op 0 allocs/op
255+
BenchmarkLARS_ParseStatic 20000000 72.8 ns/op 0 B/op 0 allocs/op
256+
BenchmarkLARS_ParseParam 20000000 78.6 ns/op 0 B/op 0 allocs/op
257+
BenchmarkLARS_Parse2Params 20000000 96.9 ns/op 0 B/op 0 allocs/op
258+
BenchmarkLARS_ParseAll 500000 2968 ns/op 0 B/op 0 allocs/op
259+
BenchmarkLARS_StaticAll 100000 22810 ns/op 0 B/op 0 allocs/op
256260
```
257261

258262
Package Versioning

doc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ misc examples and noteworthy features
171171
// Handle 405 ( Method Not allowed ), default is false
172172
l.SetHandle405MethodNotAllowed(false)
173173
174+
// automatically handle OPTION requests; manually configured
175+
// OPTION handlers take precedence. default true
176+
l.SetAutomaticallyHandleOPTIONS(set bool)
177+
174178
// register custom context
175179
l.RegisterContext(ContextFunc)
176180

lars.go

Lines changed: 90 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const (
7272
WWWAuthenticate = "WWW-Authenticate"
7373
XForwardedFor = "X-Forwarded-For"
7474
XRealIP = "X-Real-Ip"
75+
Allow = "Allow"
7576

7677
Gzip = "gzip"
7778

@@ -126,7 +127,8 @@ type LARS struct {
126127
http404 HandlersChain // 404 Not Found
127128
http405 HandlersChain // 405 Method Not Allowed
128129

129-
notFound HandlersChain
130+
automaticOPTIONS HandlersChain
131+
notFound HandlersChain
130132

131133
customHandlersFuncs customHandlers
132134

@@ -144,6 +146,10 @@ type LARS struct {
144146
// If no other Method is allowed, the request is delegated to the NotFound
145147
// handler.
146148
handleMethodNotAllowed bool
149+
150+
// if enabled automatically handles OPTION requests; manually configured OPTION
151+
// handlers take precidence. default true
152+
automaticallyHandleOPTIONS bool
147153
}
148154

149155
// RouteMap contains a single routes full path
@@ -161,17 +167,11 @@ var (
161167
}
162168

163169
methodNotAllowedHandler = func(c Context) {
170+
c.Response().WriteHeader(http.StatusMethodNotAllowed)
171+
}
164172

165-
mth, _ := c.Get("methods")
166-
methods := mth.([]string)
167-
168-
res := c.Response()
169-
170-
for _, m := range methods {
171-
res.Header().Add("Allow", m)
172-
}
173-
174-
res.WriteHeader(http.StatusMethodNotAllowed)
173+
automaticOPTIONSHandler = func(c Context) {
174+
c.Response().WriteHeader(http.StatusOK)
175175
}
176176
)
177177

@@ -186,11 +186,12 @@ func New() *LARS {
186186
contextFunc: func(l *LARS) Context {
187187
return NewContext(l)
188188
},
189-
mostParams: 0,
190-
http404: []HandlerFunc{default404Handler},
191-
http405: []HandlerFunc{methodNotAllowedHandler},
192-
redirectTrailingSlash: true,
193-
handleMethodNotAllowed: false,
189+
mostParams: 0,
190+
http404: []HandlerFunc{default404Handler},
191+
http405: []HandlerFunc{methodNotAllowedHandler},
192+
redirectTrailingSlash: true,
193+
handleMethodNotAllowed: false,
194+
automaticallyHandleOPTIONS: true,
194195
}
195196

196197
l.routeGroup.lars = l
@@ -241,6 +242,13 @@ func (l *LARS) Register404(notFound ...Handler) {
241242
l.http404 = chain
242243
}
243244

245+
// SetAutomaticallyHandleOPTIONS tells lars whether to
246+
// automatically handle OPTION requests; manually configured
247+
// OPTION handlers take precedence. default true
248+
func (l *LARS) SetAutomaticallyHandleOPTIONS(set bool) {
249+
l.automaticallyHandleOPTIONS = set
250+
}
251+
244252
// SetRedirectTrailingSlash tells lars whether to try
245253
// and fix a URL by trying to find it
246254
// lowercase -> with or without slash -> 404
@@ -265,6 +273,12 @@ func (l *LARS) Serve() http.Handler {
265273
copy(l.notFound, l.middleware)
266274
copy(l.notFound[len(l.middleware):], l.http404)
267275

276+
if l.automaticallyHandleOPTIONS {
277+
l.automaticOPTIONS = make(HandlersChain, len(l.middleware)+1)
278+
copy(l.automaticOPTIONS, l.middleware)
279+
copy(l.automaticOPTIONS[len(l.middleware):], []HandlerFunc{automaticOPTIONSHandler})
280+
}
281+
268282
return http.HandlerFunc(l.serveHTTP)
269283
}
270284

@@ -280,85 +294,113 @@ func (l *LARS) serveHTTP(w http.ResponseWriter, r *http.Request) {
280294

281295
c.params = c.params[0:0]
282296

283-
if l.redirectTrailingSlash {
297+
if l.redirectTrailingSlash && len(r.URL.Path) > 1 {
284298

285299
// find again all lowercase
286300
lc := strings.ToLower(r.URL.Path)
287301

288302
if lc != r.URL.Path {
289303

290-
r.URL.Path = lc
291-
292-
if c.handlers, _, _ = root.find(r.URL.Path, c.params); c.handlers != nil {
304+
if c.handlers, _, _ = root.find(lc, c.params); c.handlers != nil {
305+
r.URL.Path = lc
293306
c.handlers = l.redirect(r.Method)
294307
goto END
295308
}
296309
}
297310

298-
if r.URL.Path[len(r.URL.Path)-1:] == basePath {
299-
r.URL.Path = r.URL.Path[:len(r.URL.Path)-1]
311+
if lc[len(lc)-1:] == basePath {
312+
lc = lc[:len(lc)-1]
300313
} else {
301-
r.URL.Path = r.URL.Path + basePath
314+
lc = lc + basePath
302315
}
303316

304-
if c.handlers, _, _ = root.find(r.URL.Path, c.params); c.handlers != nil {
317+
if c.handlers, _, _ = root.find(lc, c.params); c.handlers != nil {
318+
r.URL.Path = lc
305319
c.handlers = l.redirect(r.Method)
306320
goto END
307321
}
322+
}
308323

309-
// slow, but get's the job done
310-
if l.handleMethodNotAllowed {
324+
} else {
325+
goto END
326+
}
327+
}
311328

312-
r.URL.Path = lc
329+
if l.automaticallyHandleOPTIONS && r.Method == OPTIONS {
330+
l.getOptions(c)
331+
goto END
332+
}
313333

314-
if l.checkMethodNotAllowed(c) {
315-
goto END
316-
}
317-
}
334+
if l.handleMethodNotAllowed {
335+
336+
if l.checkMethodNotAllowed(c) {
337+
goto END
338+
}
339+
}
340+
341+
// not found
342+
c.handlers = l.notFound
343+
344+
END:
345+
346+
c.parent.Next()
347+
c.parent.RequestEnd()
348+
349+
l.pool.Put(c)
350+
}
351+
352+
func (l *LARS) getOptions(c *Ctx) {
353+
354+
res := c.Response()
355+
356+
if c.request.URL.Path == "*" { // check server-wide OPTIONS
357+
358+
for m := range l.trees {
359+
360+
if m == OPTIONS {
361+
continue
318362
}
319363

320-
// lowercase, fix trailing 405....
321-
c.handlers = l.notFound
364+
res.Header().Add(Allow, m)
322365
}
366+
323367
} else {
368+
for m, tree := range l.trees {
324369

325-
// slow, but get's the job done
326-
if l.handleMethodNotAllowed {
370+
if m == c.request.Method || m == OPTIONS {
371+
continue
372+
}
327373

328-
if l.checkMethodNotAllowed(c) {
329-
goto END
374+
if c.handlers, _, _ = tree.find(c.request.URL.Path, c.params); c.handlers != nil {
375+
res.Header().Add(Allow, m)
330376
}
331377
}
332378

333-
c.handlers = l.notFound
334379
}
335380

336-
END:
381+
res.Header().Add(Allow, OPTIONS)
382+
c.handlers = l.automaticOPTIONS
337383

338-
c.parent.Next()
339-
c.parent.RequestEnd()
340-
341-
l.pool.Put(c)
384+
return
342385
}
343386

344387
func (l *LARS) checkMethodNotAllowed(c *Ctx) (found bool) {
345388

346-
var methods []string
389+
res := c.Response()
347390

348391
for m, tree := range l.trees {
349392

350393
if m != c.request.Method {
351394
if c.handlers, _, _ = tree.find(c.request.URL.Path, c.params); c.handlers != nil {
352395
// add methods
353-
methods = append(methods, m)
396+
res.Header().Add(Allow, m)
397+
found = true
354398
}
355399
}
356400
}
357401

358-
if len(methods) > 0 {
359-
c.Set("methods", methods)
402+
if found {
360403
c.handlers = l.http405
361-
found = true
362404
}
363405

364406
return

lars_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,41 @@ func TestRedirect(t *testing.T) {
894894
Equal(t, code, http.StatusNotFound)
895895
}
896896

897+
func TestAutomaticallyHandleOPTIONS(t *testing.T) {
898+
899+
l := New()
900+
l.SetAutomaticallyHandleOPTIONS(true)
901+
l.Get("/home", func(c Context) {})
902+
l.Post("/home", func(c Context) {})
903+
l.Delete("/user", func(c Context) {})
904+
l.Options("/other", func(c Context) {})
905+
906+
code, _ := request(GET, "/home", l)
907+
Equal(t, code, http.StatusOK)
908+
909+
r, _ := http.NewRequest(OPTIONS, "/home", nil)
910+
w := httptest.NewRecorder()
911+
l.serveHTTP(w, r)
912+
913+
Equal(t, w.Code, http.StatusOK)
914+
915+
allow, ok := w.Header()["Allow"]
916+
917+
Equal(t, ok, true)
918+
Equal(t, len(allow), 3)
919+
920+
r, _ = http.NewRequest(OPTIONS, "*", nil)
921+
w = httptest.NewRecorder()
922+
l.serveHTTP(w, r)
923+
924+
Equal(t, w.Code, http.StatusOK)
925+
926+
allow, ok = w.Header()["Allow"]
927+
928+
Equal(t, ok, true)
929+
Equal(t, len(allow), 4)
930+
}
931+
897932
type closeNotifyingRecorder struct {
898933
*httptest.ResponseRecorder
899934
closed chan bool

0 commit comments

Comments
 (0)