~repos /gromer

#golang#htmx#ssr

git clone https://pyrossh.dev/repos/gromer.git

gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.


f3604a4a pyros2097

5 years ago
handle panics
Files changed (5) hide show
  1. app_nowasm.go +7 -7
  2. app_wasm.go +5 -10
  3. example/main.go +1 -0
  4. example/panic.go +9 -0
  5. router.go +28 -406
app_nowasm.go CHANGED
@@ -5,6 +5,7 @@ package app
5
5
  import (
6
6
  "bytes"
7
7
  "fmt"
8
+ "io"
8
9
  "net/http"
9
10
  "os"
10
11
  "path/filepath"
@@ -46,19 +47,19 @@ func Run() {
46
47
  return
47
48
  }
48
49
  assetsFS := http.FileServer(pkger.Dir(filepath.Join(wd, "assets")))
49
- router.GET("/assets/*filepath", func(w http.ResponseWriter, r *http.Request) {
50
+ AppRouter.GET("/assets/*filepath", func(w http.ResponseWriter, r *http.Request) {
50
51
  r.URL.Path = strings.Replace(r.URL.Path, "/assets", "", 1)
51
52
  assetsFS.ServeHTTP(w, r)
52
53
  })
53
54
  }
54
55
  if isLambda {
55
56
  println("running in lambda mode")
56
- algnhsa.ListenAndServe(router, &algnhsa.Options{
57
+ algnhsa.ListenAndServe(AppRouter, &algnhsa.Options{
57
58
  BinaryContentTypes: []string{"application/wasm", "image/png"},
58
59
  })
59
60
  } else {
60
61
  println("Serving on HTTP port: 1234")
61
- http.ListenAndServe(":1234", router)
62
+ http.ListenAndServe(":1234", AppRouter)
62
63
  }
63
64
  }
64
65
 
@@ -68,10 +69,10 @@ func Reload() {
68
69
 
69
70
  func Route(path string, render RenderFunc) {
70
71
  println("registering route: " + path)
71
- router.GET(path, render)
72
+ AppRouter.GET(path, render)
72
73
  }
73
74
 
74
- func createPage(ui UI) *bytes.Buffer {
75
+ func writePage(ui UI, w io.Writer) {
75
76
  isLambda := os.Getenv("_LAMBDA_SERVER_PORT") != ""
76
77
  page := bytes.NewBuffer(nil)
77
78
  page.WriteString("<!DOCTYPE html>\n")
@@ -92,6 +93,5 @@ func createPage(ui UI) *bytes.Buffer {
92
93
  Script(`const enosys = () => { const a = new Error("not implemented"); return a.code = "ENOSYS", a }; let outputBuf = ""; window.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, writeSync(a, b) { outputBuf += decoder.decode(b); const c = outputBuf.lastIndexOf("\n"); return -1 != c && (console.log(outputBuf.substr(0, c)), outputBuf = outputBuf.substr(c + 1)), b.length }, write(a, b, c, d, e, f) { if (0 !== c || d !== b.length || null !== e) return void f(enosys()); const g = this.writeSync(a, b); f(null, g) } }; const encoder = new TextEncoder("utf-8"), decoder = new TextDecoder("utf-8"); class Go { constructor() { this.argv = ["js"], this.env = {}, this.exit = a => { 0 !== a && console.warn("exit code:", a) }, this._exitPromise = new Promise(a => { this._resolveExitPromise = a }), this._pendingEvent = null, this._scheduledTimeouts = new Map, this._nextCallbackTimeoutID = 1; const a = (a, b) => { this.mem.setUint32(a + 0, b, !0), this.mem.setUint32(a + 4, Math.floor(b / 4294967296), !0) }, b = a => { const b = this.mem.getUint32(a + 0, !0), c = this.mem.getInt32(a + 4, !0); return b + 4294967296 * c }, c = a => { const b = this.mem.getFloat64(a, !0); if (0 !== b) { if (!isNaN(b)) return b; const c = this.mem.getUint32(a, !0); return this._values[c] } }, d = (a, b) => { const c = 2146959360; if ("number" == typeof b) return isNaN(b) ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 0, !0)) : 0 === b ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 1, !0)) : void this.mem.setFloat64(a, b, !0); switch (b) { case void 0: return void this.mem.setFloat64(a, 0, !0); case null: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 2, !0); case !0: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 3, !0); case !1: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 4, !0); }let d = this._ids.get(b); d === void 0 && (d = this._idPool.pop(), d === void 0 && (d = this._values.length), this._values[d] = b, this._goRefCounts[d] = 0, this._ids.set(b, d)), this._goRefCounts[d]++; let e = 1; switch (typeof b) { case "string": e = 2; break; case "symbol": e = 3; break; case "function": e = 4; }this.mem.setUint32(a + 4, 2146959360 | e, !0), this.mem.setUint32(a, d, !0) }, e = a => { const c = b(a + 0), d = b(a + 8); return new Uint8Array(this._inst.exports.mem.buffer, c, d) }, f = d => { const e = b(d + 0), f = b(d + 8), g = Array(f); for (let a = 0; a < f; a++)g[a] = c(e + 8 * a); return g }, g = a => { const c = b(a + 0), d = b(a + 8); return decoder.decode(new DataView(this._inst.exports.mem.buffer, c, d)) }, h = Date.now() - performance.now(); this.importObject = { go: { "runtime.wasmExit": a => { const b = this.mem.getInt32(a + 8, !0); this.exited = !0, delete this._inst, delete this._values, delete this._goRefCounts, delete this._ids, delete this._idPool, this.exit(b) }, "runtime.wasmWrite": a => { const c = b(a + 8), d = b(a + 16), e = this.mem.getInt32(a + 24, !0); fs.writeSync(c, new Uint8Array(this._inst.exports.mem.buffer, d, e)) }, "runtime.resetMemoryDataView": () => { this.mem = new DataView(this._inst.exports.mem.buffer) }, "runtime.nanotime1": b => { a(b + 8, 1e6 * (h + performance.now())) }, "runtime.walltime1": b => { const c = new Date().getTime(); a(b + 8, c / 1e3), this.mem.setInt32(b + 16, 1e6 * (c % 1e3), !0) }, "runtime.scheduleTimeoutEvent": a => { const c = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++, this._scheduledTimeouts.set(c, setTimeout(() => { for (this._resume(); this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"), this._resume() }, b(a + 8) + 1)), this.mem.setInt32(a + 16, c, !0) }, "runtime.clearTimeoutEvent": a => { const b = this.mem.getInt32(a + 8, !0); clearTimeout(this._scheduledTimeouts.get(b)), this._scheduledTimeouts.delete(b) }, "runtime.getRandomData": a => { crypto.getRandomValues(e(a + 8)) }, "syscall/js.finalizeRef": a => { const b = this.mem.getUint32(a + 8, !0); if (this._goRefCounts[b]--, 0 === this._goRefCounts[b]) { const a = this._values[b]; this._values[b] = null, this._ids.delete(a), this._idPool.push(b) } }, "syscall/js.stringVal": a => { d(a + 24, g(a + 8)) }, "syscall/js.valueGet": a => { const b = Reflect.get(c(a + 8), g(a + 16)); a = this._inst.exports.getsp(), d(a + 32, b) }, "syscall/js.valueSet": a => { Reflect.set(c(a + 8), g(a + 16), c(a + 32)) }, "syscall/js.valueDelete": a => { Reflect.deleteProperty(c(a + 8), g(a + 16)) }, "syscall/js.valueIndex": a => { d(a + 24, Reflect.get(c(a + 8), b(a + 16))) }, "syscall/js.valueSetIndex": a => { Reflect.set(c(a + 8), b(a + 16), c(a + 24)) }, "syscall/js.valueCall": a => { try { const b = c(a + 8), e = Reflect.get(b, g(a + 16)), h = f(a + 32), i = Reflect.apply(e, b, h); a = this._inst.exports.getsp(), d(a + 56, i), this.mem.setUint8(a + 64, 1) } catch (b) { d(a + 56, b), this.mem.setUint8(a + 64, 0) } }, "syscall/js.valueInvoke": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.apply(b, void 0, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueNew": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.construct(b, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueLength": b => { a(b + 16, parseInt(c(b + 8).length)) }, "syscall/js.valuePrepareString": b => { const e = encoder.encode(c(b + 8) + ""); d(b + 16, e), a(b + 24, e.length) }, "syscall/js.valueLoadString": a => { const b = c(a + 8); e(a + 16).set(b) }, "syscall/js.valueInstanceOf": a => { this.mem.setUint8(a + 24, c(a + 8) instanceof c(a + 16)) }, "syscall/js.copyBytesToGo": b => { const d = e(b + 8), f = c(b + 32); if (!(f instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, "syscall/js.copyBytesToJS": b => { const d = c(b + 8), f = e(b + 16); if (!(d instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, debug: a => { console.log(a) } } } } async run(a) { this._inst = a, this.mem = new DataView(this._inst.exports.mem.buffer), this._values = [NaN, 0, null, !0, !1, window, this], this._goRefCounts = [], this._ids = new Map, this._idPool = [], this.exited = !1; let b = 4096; const c = a => { const c = b, d = encoder.encode(a + "\0"); return new Uint8Array(this.mem.buffer, b, d.length).set(d), b += d.length, 0 != b % 8 && (b += 8 - b % 8), c }, d = this.argv.length, e = []; this.argv.forEach(a => { e.push(c(a)) }), e.push(0); const f = Object.keys(this.env).sort(); f.forEach(a => { e.push(c(a + "=" + this.env[a])) }), e.push(0); const g = b; e.forEach(a => { this.mem.setUint32(b, a, !0), this.mem.setUint32(b + 4, 0, !0), b += 8 }), this._inst.exports.run(d, g), this.exited && this._resolveExitPromise(), await this._exitPromise } _resume() { if (this.exited) throw new Error("Go program has already exited"); this._inst.exports.resume(), this.exited && this._resolveExitPromise() } _makeFuncWrapper(a) { const b = this; return function () { const c = { id: a, this: this, args: arguments }; return b._pendingEvent = c, b._resume(), c.result } } } const go = new Go; WebAssembly.instantiateStreaming(fetch("`+basePath+`main.wasm`+`"), go.importObject).then(a => go.run(a.instance)).catch(a => console.error("could not load wasm", a));`),
93
94
  ),
94
95
  Body(ui),
95
- ).Html(page)
96
+ ).Html(w)
96
- return page
97
97
  }
app_wasm.go CHANGED
@@ -2,19 +2,15 @@
2
2
  package app
3
3
 
4
4
  import (
5
- "bytes"
5
+ "io"
6
6
 
7
7
  "github.com/pyros2097/wapp/js"
8
8
  )
9
9
 
10
10
  func Run() {
11
- handle, _, _ := router.Lookup("GET", js.Window.URL().Path)
11
+ handle, _, _ := AppRouter.Lookup("GET", js.Window.URL().Path)
12
12
  if handle == nil {
13
- if router.NotFound != nil {
14
- renderFunc = router.NotFound
13
+ renderFunc = AppRouter.NotFound
15
- } else {
16
- renderFunc = DefaultNotFound
17
- }
18
14
  } else {
19
15
  renderFunc, _ = handle.(RenderFunc)
20
16
  }
@@ -46,7 +42,7 @@ func Reload() {
46
42
  }
47
43
 
48
44
  func Route(path string, render RenderFunc) {
49
- router.GET(path, render)
45
+ AppRouter.GET(path, render)
50
46
  }
51
47
 
52
48
  func initBody() {
@@ -81,6 +77,5 @@ func initContent() {
81
77
  // return u.Fragment != ""
82
78
  // }
83
79
 
84
- func createPage(ui UI) *bytes.Buffer {
80
+ func writePage(ui UI, w io.Writer) {
85
- return &bytes.Buffer{}
86
81
  }
example/main.go CHANGED
@@ -5,6 +5,7 @@ import (
5
5
  )
6
6
 
7
7
  func main() {
8
+ Route("/panic", Panic)
8
9
  Route("/about", About)
9
10
  Route("/clock", Clock)
10
11
  Route("/container", Container)
example/panic.go ADDED
@@ -0,0 +1,9 @@
1
+ package main
2
+
3
+ import (
4
+ . "github.com/pyros2097/wapp"
5
+ )
6
+
7
+ func Panic(c *RenderContext) UI {
8
+ panic("Hello")
9
+ }
router.go CHANGED
@@ -3,157 +3,12 @@ package app
3
3
  import (
4
4
  "context"
5
5
  "net/http"
6
- "strconv"
7
6
  "strings"
8
7
  "sync"
9
8
  "unicode"
10
9
  "unicode/utf8"
11
10
  )
12
11
 
13
- // CleanPath is the URL version of path.Clean, it returns a canonical URL path
14
- // for p, eliminating . and .. elements.
15
- //
16
- // The following rules are applied iteratively until no further processing can
17
- // be done:
18
- // 1. Replace multiple slashes with a single slash.
19
- // 2. Eliminate each . path name element (the current directory).
20
- // 3. Eliminate each inner .. path name element (the parent directory)
21
- // along with the non-.. element that precedes it.
22
- // 4. Eliminate .. elements that begin a rooted path:
23
- // that is, replace "/.." by "/" at the beginning of a path.
24
- //
25
- // If the result of this process is an empty string, "/" is returned
26
- func CleanPath(p string) string {
27
- const stackBufSize = 128
28
-
29
- // Turn empty string into "/"
30
- if p == "" {
31
- return "/"
32
- }
33
-
34
- // Reasonably sized buffer on stack to avoid allocations in the common case.
35
- // If a larger buffer is required, it gets allocated dynamically.
36
- buf := make([]byte, 0, stackBufSize)
37
-
38
- n := len(p)
39
-
40
- // Invariants:
41
- // reading from path; r is index of next byte to process.
42
- // writing to buf; w is index of next byte to write.
43
-
44
- // path must start with '/'
45
- r := 1
46
- w := 1
47
-
48
- if p[0] != '/' {
49
- r = 0
50
-
51
- if n+1 > stackBufSize {
52
- buf = make([]byte, n+1)
53
- } else {
54
- buf = buf[:n+1]
55
- }
56
- buf[0] = '/'
57
- }
58
-
59
- trailing := n > 1 && p[n-1] == '/'
60
-
61
- // A bit more clunky without a 'lazybuf' like the path package, but the loop
62
- // gets completely inlined (bufApp calls).
63
- // So in contrast to the path package this loop has no expensive function
64
- // calls (except make, if needed).
65
-
66
- for r < n {
67
- switch {
68
- case p[r] == '/':
69
- // empty path element, trailing slash is added after the end
70
- r++
71
-
72
- case p[r] == '.' && r+1 == n:
73
- trailing = true
74
- r++
75
-
76
- case p[r] == '.' && p[r+1] == '/':
77
- // . element
78
- r += 2
79
-
80
- case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
81
- // .. element: remove to last /
82
- r += 3
83
-
84
- if w > 1 {
85
- // can backtrack
86
- w--
87
-
88
- if len(buf) == 0 {
89
- for w > 1 && p[w] != '/' {
90
- w--
91
- }
92
- } else {
93
- for w > 1 && buf[w] != '/' {
94
- w--
95
- }
96
- }
97
- }
98
-
99
- default:
100
- // Real path element.
101
- // Add slash if needed
102
- if w > 1 {
103
- bufApp(&buf, p, w, '/')
104
- w++
105
- }
106
-
107
- // Copy element
108
- for r < n && p[r] != '/' {
109
- bufApp(&buf, p, w, p[r])
110
- w++
111
- r++
112
- }
113
- }
114
- }
115
-
116
- // Re-append trailing slash
117
- if trailing && w > 1 {
118
- bufApp(&buf, p, w, '/')
119
- w++
120
- }
121
-
122
- // If the original string was not modified (or only shortened at the end),
123
- // return the respective substring of the original string.
124
- // Otherwise return a new string from the buffer.
125
- if len(buf) == 0 {
126
- return p[:w]
127
- }
128
- return string(buf[:w])
129
- }
130
-
131
- // Internal helper to lazily create a buffer if necessary.
132
- // Calls to this function get inlined.
133
- func bufApp(buf *[]byte, s string, w int, c byte) {
134
- b := *buf
135
- if len(b) == 0 {
136
- // No modification of the original string so far.
137
- // If the next character is the same as in the original string, we do
138
- // not yet have to allocate a buffer.
139
- if s[w] == c {
140
- return
141
- }
142
-
143
- // Otherwise use either the stack buffer, if it is large enough, or
144
- // allocate a new buffer on the heap, and copy all previous characters.
145
- if l := len(s); l > cap(b) {
146
- *buf = make([]byte, len(s))
147
- } else {
148
- *buf = (*buf)[:l]
149
- }
150
- b = *buf
151
-
152
- copy(b, s[:w])
153
- }
154
- b[w] = c
155
- }
156
-
157
12
  func min(a, b int) int {
158
13
  if a <= b {
159
14
  return a
@@ -857,31 +712,12 @@ func ParamsFromContext(ctx context.Context) Params {
857
712
  return p
858
713
  }
859
714
 
860
- // MatchedRoutePathParam is the Param name under which the path of the matched
861
- // route is stored, if Router.SaveMatchedRoutePath is set.
862
- var MatchedRoutePathParam = "$matchedRoutePath"
863
-
864
- // MatchedRoutePath retrieves the path of the matched route.
865
- // Router.SaveMatchedRoutePath must have been enabled when the respective
866
- // handler was added, otherwise this function always returns an empty string.
867
- func (ps Params) MatchedRoutePath() string {
868
- return ps.ByName(MatchedRoutePathParam)
869
- }
870
-
871
715
  // Router is a http.Handler which can be used to dispatch requests to different
872
716
  // handler functions via configurable routes
873
717
  type Router struct {
874
- trees map[string]*node
718
+ trees map[string]*node
875
-
876
719
  paramsPool sync.Pool
877
720
  maxParams uint16
878
-
879
- // If enabled, adds the matched route path onto the http.Request context
880
- // before invoking the handler.
881
- // The matched route path is only added to handlers of routes that were
882
- // registered when this option was enabled.
883
- SaveMatchedRoutePath bool
884
-
885
721
  // Enables automatic redirection if the current route can't be matched but a
886
722
  // handler for the path with (without) the trailing slash exists.
887
723
  // For example if /foo/ is requested but a route only exists for /foo, the
@@ -889,66 +725,25 @@ type Router struct {
889
725
  // and 308 for all other request methods.
890
726
  RedirectTrailingSlash bool
891
727
 
892
- // If enabled, the router tries to fix the current request path, if no
893
- // handle is registered for it.
894
- // First superfluous path elements like ../ or // are removed.
895
- // Afterwards the router does a case-insensitive lookup of the cleaned path.
896
- // If a handle can be found for this route, the router makes a redirection
897
- // to the corrected path with status code 301 for GET requests and 308 for
898
- // all other request methods.
899
- // For example /FOO and /..//Foo could be redirected to /foo.
900
- // RedirectTrailingSlash is independent of this option.
901
- RedirectFixedPath bool
902
-
903
- // If enabled, the router checks if another method is allowed for the
904
- // current route, if the current request can not be routed.
905
- // If this is the case, the request is answered with 'Method Not Allowed'
906
- // and HTTP status code 405.
907
- // If no other Method is allowed, the request is delegated to the NotFound
908
- // handler.
909
- HandleMethodNotAllowed bool
910
-
911
- // If enabled, the router automatically replies to OPTIONS requests.
912
- // Custom OPTIONS handlers take priority over automatic replies.
913
- HandleOPTIONS bool
914
-
915
- // An optional http.Handler that is called on automatic OPTIONS requests.
916
- // The handler is only called if HandleOPTIONS is true and no OPTIONS
917
- // handler for the specific path was set.
918
- // The "Allowed" header is set before calling the handler.
919
- GlobalOPTIONS http.Handler
920
-
921
- // Cached value of global (*) allowed methods
922
- globalAllowed string
923
-
924
- // Configurable http.Handler which is called when no matching route is
728
+ // Configurable handler which is called when no matching route is found.
925
- // found. If it is not set, http.NotFound is used.
926
729
  NotFound RenderFunc
927
730
 
928
- // Configurable http.Handler which is called when a request
731
+ // Configurable handler which is called when an error occurs.
929
- // cannot be routed and HandleMethodNotAllowed is true.
930
- // If it is not set, http.Error with http.StatusMethodNotAllowed is used.
931
- // The "Allow" header with allowed request methods is set before the handler
932
- // is called.
933
- MethodNotAllowed http.Handler
934
-
935
- // Function to handle panics recovered from http handlers.
936
- // It should be used to generate a error page and return the http error code
937
- // 500 (Internal Server Error).
938
- // The handler can be used to keep your server from crashing because of
939
- // unrecovered panics.
732
+ Error RenderFunc
940
- PanicHandler func(http.ResponseWriter, *http.Request, interface{})
941
733
  }
942
734
 
943
- // func(http.ResponseWriter, *http.Request, Params)
944
- // Make sure the Router conforms with the http.Handler interface
945
- // var _ http.Handler = New()
946
-
947
- var router = &Router{
735
+ var AppRouter = &Router{
948
- RedirectTrailingSlash: true,
736
+ RedirectTrailingSlash: true,
949
- RedirectFixedPath: true,
737
+ NotFound: func(c *RenderContext) UI {
950
- HandleMethodNotAllowed: true,
738
+ return Col(
739
+ Row(
951
- HandleOPTIONS: true,
740
+ Text("This is the default 404 - Not Found Route handler"),
741
+ ),
742
+ Row(
743
+ Text("Create a notfound.go file and add a func NotFound(c *RenderContext) UI {} to override it"),
744
+ ),
745
+ )
746
+ },
952
747
  }
953
748
 
954
749
  func (r *Router) getParams() *Params {
@@ -963,43 +758,11 @@ func (r *Router) putParams(ps *Params) {
963
758
  }
964
759
  }
965
760
 
966
- // func (r *Router) saveMatchedRoutePath(path string, handle interface{}) interface{} {
967
- // return func(w http.ResponseWriter, req *http.Request, ps Params) {
968
- // if ps == nil {
969
- // psp := r.getParams()
970
- // ps = (*psp)[0:1]
971
- // ps[0] = Param{Key: MatchedRoutePathParam, Value: path}
972
- // println("route: " + r.URL.Path)
973
- // page := createPage(info, render(NewRenderContext()))
974
- // w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
975
- // w.Header().Set("Content-Type", "text/html")
976
- // w.WriteHeader(http.StatusOK)
977
- // w.Write(page.Bytes())
978
- // handle(w, req, ps)
979
- // r.putParams(psp)
980
- // } else {
981
- // ps = append(ps, Param{Key: MatchedRoutePathParam, Value: path})
982
- // handle(w, req, ps)
983
- // }
984
- // }
985
- // }
986
-
987
761
  // GET is a shortcut for router.Handle(http.MethodGet, path, handle)
988
762
  func (r *Router) GET(path string, handle interface{}) {
989
763
  r.Handle(http.MethodGet, path, handle)
990
764
  }
991
765
 
992
- // HEAD is a shortcut for router.Handle(http.MethodHead, path, handle)
993
- func (r *Router) HEAD(path string, handle interface{}) {
994
- r.Handle(http.MethodHead, path, handle)
995
- }
996
-
997
- // OPTIONS is a shortcut for router.Handle(http.MethodOptions, path, handle)
998
- func (r *Router) OPTIONS(path string, handle interface{}) {
999
- r.Handle(http.MethodOptions, path, handle)
1000
- }
1001
-
1002
- // POST is a shortcut for router.Handle(http.MethodPost, path, handle)
1003
766
  func (r *Router) POST(path string, handle interface{}) {
1004
767
  r.Handle(http.MethodPost, path, handle)
1005
768
  }
@@ -1009,11 +772,6 @@ func (r *Router) PUT(path string, handle interface{}) {
1009
772
  r.Handle(http.MethodPut, path, handle)
1010
773
  }
1011
774
 
1012
- // PATCH is a shortcut for router.Handle(http.MethodPatch, path, handle)
1013
- func (r *Router) PATCH(path string, handle interface{}) {
1014
- r.Handle(http.MethodPatch, path, handle)
1015
- }
1016
-
1017
775
  // DELETE is a shortcut for router.Handle(http.MethodDelete, path, handle)
1018
776
  func (r *Router) DELETE(path string, handle interface{}) {
1019
777
  r.Handle(http.MethodDelete, path, handle)
@@ -1053,8 +811,6 @@ func (r *Router) Handle(method, path string, handle interface{}) {
1053
811
  if root == nil {
1054
812
  root = new(node)
1055
813
  r.trees[method] = root
1056
-
1057
- r.globalAllowed = r.allowed("*", "")
1058
814
  }
1059
815
 
1060
816
  root.addRoute(path, handle)
@@ -1073,34 +829,6 @@ func (r *Router) Handle(method, path string, handle interface{}) {
1073
829
  }
1074
830
  }
1075
831
 
1076
- // Handler is an adapter which allows the usage of an http.Handler as a
1077
- // request handle.
1078
- // The Params are available in the request context under ParamsKey.
1079
- // func (r *Router) Handler(method, path string, handler http.Handler) {
1080
- // r.Handle(method, path,
1081
- // func(w http.ResponseWriter, req *http.Request, p Params) {
1082
- // if len(p) > 0 {
1083
- // ctx := req.Context()
1084
- // ctx = context.WithValue(ctx, ParamsKey, p)
1085
- // req = req.WithContext(ctx)
1086
- // }
1087
- // handler.ServeHTTP(w, req)
1088
- // },
1089
- // )
1090
- // }
1091
-
1092
- // HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a
1093
- // request handle.
1094
- // func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
1095
- // r.Handler(method, path, handler)
1096
- // }
1097
-
1098
- func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
1099
- if rcv := recover(); rcv != nil {
1100
- r.PanicHandler(w, req, rcv)
1101
- }
1102
- }
1103
-
1104
832
  // Lookup allows the manual lookup of a method + path combo.
1105
833
  // This is e.g. useful to build a framework around this router.
1106
834
  // If the path was found, it returns the handle function and the path parameter
@@ -1121,62 +849,16 @@ func (r *Router) Lookup(method, path string) (interface{}, Params, bool) {
1121
849
  return nil, nil, false
1122
850
  }
1123
851
 
1124
- func (r *Router) allowed(path, reqMethod string) (allow string) {
1125
- allowed := make([]string, 0, 9)
1126
-
1127
- if path == "*" { // server-wide
1128
- // empty method is used for internal calls to refresh the cache
1129
- if reqMethod == "" {
1130
- for method := range r.trees {
1131
- if method == http.MethodOptions {
1132
- continue
1133
- }
1134
- // Add request method to list of allowed methods
1135
- allowed = append(allowed, method)
1136
- }
1137
- } else {
1138
- return r.globalAllowed
1139
- }
1140
- } else { // specific path
1141
- for method := range r.trees {
1142
- // Skip the requested method - we already tried this one
1143
- if method == reqMethod || method == http.MethodOptions {
1144
- continue
1145
- }
1146
-
1147
- handle, _, _ := r.trees[method].getValue(path, nil)
1148
- if handle != nil {
1149
- // Add request method to list of allowed methods
1150
- allowed = append(allowed, method)
1151
- }
1152
- }
1153
- }
1154
-
1155
- if len(allowed) > 0 {
1156
- // Add request method to list of allowed methods
1157
- allowed = append(allowed, http.MethodOptions)
1158
-
1159
- // Sort allowed methods.
1160
- // sort.Strings(allowed) unfortunately causes unnecessary allocations
1161
- // due to allowed being moved to the heap and interface conversion
1162
- for i, l := 1, len(allowed); i < l; i++ {
1163
- for j := i; j > 0 && allowed[j] < allowed[j-1]; j-- {
1164
- allowed[j], allowed[j-1] = allowed[j-1], allowed[j]
1165
- }
1166
- }
1167
-
1168
- // return as comma separated list
1169
- return strings.Join(allowed, ", ")
1170
- }
1171
-
1172
- return allow
1173
- }
1174
-
1175
852
  // ServeHTTP makes the router implement the http.Handler interface.
1176
853
  func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
854
+ // Handle errors
855
+ defer func() {
1177
- if r.PanicHandler != nil {
856
+ if rcv := recover(); rcv != nil {
1178
- defer r.recv(w, req)
857
+ writePage(r.Error(NewRenderContext()), w)
858
+ w.Header().Set("Content-Type", "text/html")
859
+ w.WriteHeader(http.StatusInternalServerError)
1179
- }
860
+ }
861
+ }()
1180
862
 
1181
863
  path := req.URL.Path
1182
864
 
@@ -1185,11 +867,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1185
867
  if handle, _, tsr := root.getValue(path, r.getParams); handle != nil {
1186
868
  println("route: " + req.URL.Path)
1187
869
  if render, ok := handle.(RenderFunc); ok {
1188
- page := createPage(render(NewRenderContext()))
870
+ writePage(render(NewRenderContext()), w)
1189
- w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
1190
871
  w.Header().Set("Content-Type", "text/html")
1191
872
  w.WriteHeader(http.StatusOK)
1192
- w.Write(page.Bytes())
1193
873
  return
1194
874
  } else {
1195
875
  handle.(func(w http.ResponseWriter, r *http.Request))(w, req)
@@ -1213,69 +893,11 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1213
893
  http.Redirect(w, req, req.URL.String(), code)
1214
894
  return
1215
895
  }
1216
-
1217
- // Try to fix the request path
1218
- if r.RedirectFixedPath {
1219
- fixedPath, found := root.findCaseInsensitivePath(
1220
- CleanPath(path),
1221
- r.RedirectTrailingSlash,
1222
- )
1223
- if found {
1224
- req.URL.Path = fixedPath
1225
- http.Redirect(w, req, req.URL.String(), code)
1226
- return
1227
- }
1228
- }
1229
- }
1230
- }
1231
-
1232
- if req.Method == http.MethodOptions && r.HandleOPTIONS {
1233
- // Handle OPTIONS requests
1234
- if allow := r.allowed(path, http.MethodOptions); allow != "" {
1235
- w.Header().Set("Allow", allow)
1236
- if r.GlobalOPTIONS != nil {
1237
- r.GlobalOPTIONS.ServeHTTP(w, req)
1238
- }
1239
- return
1240
- }
1241
- } else if r.HandleMethodNotAllowed { // Handle 405
1242
- if allow := r.allowed(path, req.Method); allow != "" {
1243
- w.Header().Set("Allow", allow)
1244
- if r.MethodNotAllowed != nil {
1245
- r.MethodNotAllowed.ServeHTTP(w, req)
1246
- } else {
1247
- http.Error(w,
1248
- http.StatusText(http.StatusMethodNotAllowed),
1249
- http.StatusMethodNotAllowed,
1250
- )
1251
- }
1252
- return
1253
896
  }
1254
897
  }
1255
898
 
1256
899
  // Handle 404
1257
- if r.NotFound != nil {
1258
- page := createPage(r.NotFound(NewRenderContext()))
900
+ writePage(r.NotFound(NewRenderContext()), w)
1259
- w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
1260
- w.Header().Set("Content-Type", "text/html")
901
+ w.Header().Set("Content-Type", "text/html")
1261
- w.WriteHeader(http.StatusOK)
902
+ w.WriteHeader(http.StatusNotFound)
1262
- w.Write(page.Bytes())
1263
- } else {
1264
- page := createPage(DefaultNotFound(NewRenderContext()))
1265
- w.Header().Set("Content-Length", strconv.Itoa(page.Len()))
1266
- w.Header().Set("Content-Type", "text/html")
1267
- w.WriteHeader(http.StatusOK)
1268
- w.Write(page.Bytes())
1269
- }
1270
- }
1271
-
1272
- func DefaultNotFound(c *RenderContext) UI {
1273
- return Col(
1274
- Row(
1275
- Text("This is the default 404 - Not Found Route handler"),
1276
- ),
1277
- Row(
1278
- Text("Create a notfound.go file and add a func NotFound(c *RenderContext) UI {} to override it"),
1279
- ),
1280
- )
1281
903
  }