-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathcommands.go
More file actions
236 lines (212 loc) · 7.71 KB
/
commands.go
File metadata and controls
236 lines (212 loc) · 7.71 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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package web
import (
"bytes"
"fmt"
"github.com/Masterminds/cookoo"
"html/template"
"io"
"log"
"net/http"
"os"
"path"
"strings"
)
// Common web-oriented commands
// Flush sends content to output.
//
// If no writer is specified, this will attempt to write to whatever is in the
// Context with the key "http.ResponseWriter". If no suitable writer is found, it will
// not write to anything at all.
//
// Params:
// - writer: A Writer of some sort. This will try to write to the HTTP response if no writer
// is specified.
// - content: The content to write as a body. If this is a byte[], it is sent unchanged. Otherwise.
// we first try to convert to a string, then pass it into a writer.
// - contentType: The content type header (e.g. text/html). Default is text/plain
// - responseCode: Integer HTTP Response Code: Default is `http.StatusOK`.
// - headers: a map[string]string of HTTP headers. The keys will be run through
// http.CannonicalHeaderKey()
//
// Note that this is optimized for writing from strings or arrays, not Readers. For larger
// objects, you may find it more efficient to use a different command.
//
// Returns
//
// - boolean true
func Flush(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
// Make sure we have a place to write this stuff.
writer, ok := params.Has("writer")
if writer == nil {
writer, ok = cxt.Has("http.ResponseWriter")
if !ok {
return false, nil
}
}
out := writer.(http.ResponseWriter)
// Get the rest of the info.
code := params.Get("responseCode", http.StatusOK).(int)
header := out.Header()
contentType := params.Get("contentType", "text/plain; charset=utf-8").(string)
// Prepare the content.
var content []byte
rawContent, ok := params.Has("content")
if !ok {
// No content. Send nothing in the body.
content = []byte("")
} else if byteContent, ok := rawContent.([]byte); ok {
// Got a byte[]; add it as is.
content = byteContent
} else {
// Use the formatter to convert to a string, and then
// cast it to bytes.
content = []byte(fmt.Sprintf("%v", rawContent))
}
// Add headers:
header.Set(http.CanonicalHeaderKey("content-type"), contentType)
headerO, ok := params.Has("headers")
if ok {
headers := headerO.(map[string]string)
for k, v := range headers {
header.Add(http.CanonicalHeaderKey(k), v)
}
}
// Send the headers.
out.WriteHeader(code)
//io.WriteString(out, content)
out.Write(content)
return true, nil
}
// RenderHTML renders an HTML template.
//
// This uses the `html/template` system built into Go to render data into a writer.
//
// Params:
// - template (required): An html/templates.Template object.
// - templateName (required): The name of the template to render.
// - values: An interface{} with the values to be passed to the template. If
// this is not specified, the contents of the Context are passed as a map[string]interface{}.
// Note that datasources, in this model, are not accessible to the template.
// - writer: The writer that data should be sent to. By default, this will create a new
// Buffer and put it into the context. (If no Writer was passed in, the returned writer
// is actually a bytes.Buffer.) To flush the contents directly to the client, you can
// use `.Using('writer').From('http.ResponseWriter')`.
//
// Returns
// - An io.Writer. The template's contents have already been written into the writer.
//
// Example:
//
// reg.Route("GET /html", "Test HTML").
// Does(cookoo.AddToContext, "_").
// Using("Title").WithDefault("Hello World").
// Using("Body").WithDefault("This is the body.").
// Does(web.RenderHTML, "render").
// Using("template").From('cxt:templateCache').
// Using("templateName").WithDefault("index.html").
// Does(web.Flush, "_").
// Using("contentType").WithDefault("text/html").
// Using("content").From("cxt:render")
//
// In the example above, we do three things:
// - Add Title and Body to the context. For the template rendered, it will see these as
// {{.Title}} and {{.Body}}.
// - Render the template located in a local file called "index.html". It is recommended that
// a template.Template object be created at startup. This way, all of the templates can
// be cached immediately and shared throughout processing.
// - Flush the result out to the client. This gives you a chance to add any additional headers.
func RenderHTML(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
ok, missing := params.Requires("template", "templateName")
if !ok {
return nil, &cookoo.FatalError{"Missing params: " + strings.Join(missing, ", ")}
}
var buf bytes.Buffer
out := params.Get("writer", &buf).(io.Writer)
tplName := params.Get("templateName", nil).(string)
tpl := params.Get("template", nil).(*template.Template)
vals := params.Get("values", cxt.AsMap())
err := tpl.ExecuteTemplate(out, tplName, vals)
if err != nil {
log.Printf("Recoverable error parsing template: %s", err)
// XXX: This outputs partially completed templates. Is this what we want?
io.WriteString(out, "Template error. The error has been logged.")
return out, &cookoo.RecoverableError{"Template failed to completely render."}
}
return out, nil
}
// ServerInfo gets the server info for this request.
//
// This assumes that `http.Request` and `http.ResponseWriter` are in the context, which
// they are by default.
//
// Returns:
// - boolean true
func ServerInfo(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
req := cxt.Get("http.Request", nil).(*http.Request)
out := cxt.Get("http.ResponseWriter", nil).(http.ResponseWriter)
out.Header().Add("X-Foo", "Bar")
out.Header().Add("Content-type", "text/plain; charset=utf-8")
fmt.Fprintf(out, "Request:\n %+v\n", req)
fmt.Fprintf(out, "\n\n\nResponse:\n%+v\n", out)
return true, nil
}
// ServeFiles is a cookoo command to serve files from a set of filesystem directories.
//
// If no writer is specified, this will attempt to write to whatever is in the
// Context with the key "http.ResponseWriter". If no suitable writer is found, it will
// not write to anything at all.
//
// Example:
//
// registry.Route("GET /**", "Serve assets").
// Does(web.ServeFiles, "fileServer").
// Using("directory").WithDefault("static")
//
// Example 2:
//
// registry.Route("GET /foo/**", "Serve assets").
// Does(web.ServeFiles, "fileServer").
// Using("directory").WithDefault("static").
// Using("removePrefix").WithDefault("/foo")
//
// Params:
// - directory: A directory to serve files from.
// - removePrefix: A prefix to remove from the url before looking for it on the filesystem.
// - writer: A Writer of some sort. This will try to write to the HTTP response if no writer
// is specified.
// - request: A request of some sort. This will try to use the HTTP request if no request
// is specified.
func ServeFiles(cxt cookoo.Context, params *cookoo.Params) (interface{}, cookoo.Interrupt) {
writer, ok := params.Has("writer")
if writer == nil {
writer, ok = cxt.Has("http.ResponseWriter")
if !ok {
return nil, &cookoo.Reroute{"@404"}
}
}
out := writer.(http.ResponseWriter)
req, ok := params.Has("request")
if req == nil {
req, ok = cxt.Has("http.Request")
if !ok {
return nil, &cookoo.Reroute{"@404"}
}
}
in := req.(*http.Request)
directory := params.Get("directory", nil)
if directory == nil {
return nil, &cookoo.Reroute{"@404"}
}
prefix := params.Get("removePrefix", "").(string)
urlPath := strings.TrimPrefix(in.URL.Path, prefix)
staticFile := path.Join(directory.(string), urlPath)
info, err := os.Stat(staticFile)
if err != nil {
return nil, &cookoo.Reroute{"@404"}
}
if info.IsDir() == false {
http.ServeFile(out, in, staticFile)
return true, nil
}
return nil, &cookoo.Reroute{"@404"}
}