-
Notifications
You must be signed in to change notification settings - Fork 8
/
yarf.go
186 lines (155 loc) · 4.34 KB
/
yarf.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package yarf
import (
"fmt"
"log"
"net/http"
)
// Version string
const Version = "0.8.5"
// Yarf is the main entry point for the framework and it centralizes most of the functionality.
// All configuration actions are handled by this object.
type Yarf struct {
// UseCache indicates if the route cache should be used.
UseCache bool
// Debug enables/disables the debug mode.
// On debug mode, extra error information is sent to the client.
Debug bool
// PanicHandler can store a func() that will be defered by each request to be able to recover().
// If you need to log, send information or do anything about a panic, this is your place.
PanicHandler func()
GroupRouter
// Cached routes storage
cache *Cache
// Logger object will be used if present
Logger *log.Logger
// Follow defines a standard http.Handler implementation to follow if no route matches.
Follow http.Handler
// NotFound defines a function interface to execute when a NotFound (404) error is thrown.
NotFound func(c *Context)
}
// New creates a new yarf and returns a pointer to it.
// Performs needed initializations
func New() *Yarf {
y := new(Yarf)
// Init cache
y.UseCache = true
y.cache = NewCache()
y.GroupRouter = RouteGroup("")
// Return object
return y
}
// ServeHTTP Implements http.Handler interface into yarf.
// Initializes a Context object and handles middleware and route actions.
// If an error is returned by any of the actions, the flow is stopped and a response is sent.
// If no route matches, tries to forward the request to the Yarf.Follow (http.Handler type) property if set.
// Otherwise it returns a 404 response.
func (y *Yarf) ServeHTTP(res http.ResponseWriter, req *http.Request) {
if y.PanicHandler != nil {
defer y.PanicHandler()
}
// Set initial context data.
// The Context pointer will be affected by the middleware and resources.
c := NewContext(req, res)
// Cached routes
if y.UseCache {
if cache, ok := y.cache.Get(req.URL.Path); ok {
// Set context params
c.Params = cache.params
c.groupDispatch = cache.route
// Dispatch and stop
err := y.Dispatch(c)
y.finish(c, err)
return
}
}
// Route match
if y.Match(req.URL.Path, c) {
if y.UseCache {
y.cache.Set(req.URL.Path, RouteCache{c.groupDispatch, c.Params})
}
err := y.Dispatch(c)
y.finish(c, err)
return
}
// Follow only when route doesn't match.
// Returned 404 errors won't follow.
if y.Follow != nil {
// Log follow
y.finish(c, nil)
// Follow
y.Follow.ServeHTTP(c.Response, c.Request)
// End here
return
}
// Return 404
y.finish(c, ErrorNotFound())
}
// Finish handles the end of the execution.
// It checks for errors and follow actions to execute.
// It also handles the custom 404 error handler.
func (y *Yarf) finish(c *Context, err error) {
// If a logger is present, lets log everything.
if y.Logger != nil {
// Construct request host string
req := "http"
if c.Request.TLS != nil {
req += "s"
}
req += "://" + c.Request.Host + c.Request.URL.String()
// Check for errors
errorMsg := "OK"
if err != nil {
yerr, ok := err.(YError)
if ok {
if yerr.Code() == 404 && y.NotFound != nil {
errorMsg = "FOLLOW NotFound"
} else {
errorMsg = fmt.Sprintf("ERROR: %d - %s | %s", yerr.Code(), yerr.Body(), yerr.Msg())
}
} else {
errorMsg = "ERROR: " + err.Error()
}
}
y.Logger.Printf(
"%s - %s | %s | %s => %s",
c.GetClientIP(),
c.Request.UserAgent(),
c.Request.Method,
req,
errorMsg,
)
}
// Return if no error
if err == nil {
return
}
// Check error type
yerr, ok := err.(YError)
if !ok {
// Create default 500 error
yerr = &CustomError{
HTTPCode: 500,
ErrorCode: 0,
ErrorMsg: err.Error(),
ErrorBody: err.Error(),
}
}
// Custom 404
if yerr.Code() == 404 && y.NotFound != nil {
y.NotFound(c)
return
}
// Write error data to response.
c.Response.WriteHeader(yerr.Code())
c.Render(yerr.Body())
}
// Start initiates a new http yarf server and start listening.
// It's a shortcut for http.ListenAndServe(address, y)
func (y *Yarf) Start(address string) {
http.ListenAndServe(address, y)
}
// StartTLS initiates a new http yarf server and starts listening to HTTPS requests.
// It is a shortcut for http.ListenAndServeTLS(address, cert, key, yarf)
func (y *Yarf) StartTLS(address, cert, key string) {
http.ListenAndServeTLS(address, cert, key, y)
}