@@ -58,22 +58,100 @@ public extension ServerResponse {
58
58
59
59
public extension Express {
60
60
61
+ /**
62
+ * Locate the rendering engine for a given path and render it with the options
63
+ * that are passed in.
64
+ *
65
+ * Refer to the ``ServerResponse/render`` method for details.
66
+ *
67
+ * - Parameters:
68
+ * - path: the filesystem path to a template.
69
+ * - options: Any options passed to the rendering engine.
70
+ * - res: The response the rendering will be sent to.
71
+ */
72
+ func render( path: String , options: Any ? , to res: ServerResponse ) {
73
+ let log = self . log
74
+ let ext = fs. path. extname ( path)
75
+ let viewEngine = ext. isEmpty ? defaultEngine : ext
76
+
77
+ guard let engine = engines [ viewEngine] else {
78
+ log. error ( " Did not find view engine for extension: \( viewEngine) " )
79
+ res. emit ( error: ExpressRenderingError . unsupportedViewEngine ( viewEngine) )
80
+ res. finishRender500IfNecessary ( )
81
+ return
82
+ }
83
+
84
+ engine ( path, options) { ( results: Any? ... ) in
85
+ let rc = results. count
86
+ let v0 = rc > 0 ? results [ 0 ] : nil
87
+ let v1 = rc > 1 ? results [ 1 ] : nil
88
+
89
+ if let error = v0 {
90
+ res. emit ( error: ExpressRenderingError
91
+ . templateError ( error as? Swift . Error ) )
92
+ log. error ( " template error: " , error)
93
+ res. writeHead ( 500 )
94
+ res. end ( )
95
+ return
96
+ }
97
+
98
+ guard let result = v1 else { // Hm?
99
+ log. warn ( " template returned no content: \( path) \( results) " )
100
+ res. writeHead ( 204 )
101
+ res. end ( )
102
+ return
103
+ }
104
+
105
+ // TBD: maybe support a stream as a result? (result.pipe(res))
106
+ // Or generators, there are many more options.
107
+ if !( result is String ) {
108
+ log. warn ( " template rendering result is not a String: " , result)
109
+ }
110
+
111
+ let s = ( result as? String ) ?? " \( result) "
112
+
113
+ // Wow, this is harder than it looks when we want to consider a MIMEType
114
+ // object as a value :-)
115
+ var setContentType = true
116
+ if let oldType = res. getHeader ( " Content-Type " ) {
117
+ let s = ( oldType as? String ) ?? String ( describing: oldType) // FIXME
118
+ setContentType = ( s == " httpd/unix-directory " ) // a hack for Apache
119
+ }
120
+
121
+ if setContentType {
122
+ // FIXME: also consider extension of template (.html, .vcf etc)
123
+ res. setHeader ( " Content-Type " , detectTypeForContent ( string: s) )
124
+ }
125
+
126
+ res. writeHead ( 200 )
127
+ res. write ( s)
128
+ res. end ( )
129
+ }
130
+ }
131
+
61
132
/**
62
133
* Lookup a template with the given name, locate the rendering engine for it,
63
134
* and render it with the options that are passed in.
64
135
*
65
136
* Refer to the ``ServerResponse/render`` method for details.
66
137
*/
67
138
func render( template: String , options: Any ? , to res: ServerResponse ) {
68
- let log = self . log
69
-
70
- let defaultEngine = self . defaultEngine
139
+ let log = self . log
140
+ let cacheOn = settings. enabled ( " view cache " )
71
141
let emptyOpts : [ String : Any ] = [ : ]
72
142
let appViewOptions = get ( " view options " ) ?? emptyOpts // Any?
73
143
let viewOptions = options ?? appViewOptions // TODO: merge if possible
74
144
// not usually possible, because not guaranteed to be dicts!
75
145
76
- let view = View ( name: template, options: self )
146
+ if cacheOn, let view = viewCache. withLockedValue ( { $0 [ template] } ) ,
147
+ let path = view. path
148
+ {
149
+ log. trace ( " Using cached view: " , template)
150
+ return self . render ( path: path, options: viewOptions, to: res)
151
+ }
152
+
153
+ let viewType : View . Type = ( get ( " view " ) as? View . Type) ?? View . self
154
+ let view = viewType. init ( name: template, options: self )
77
155
let name = path. basename ( template, path. extname ( template) )
78
156
view. lookup ( name) { pathOrNot in
79
157
guard let path = pathOrNot else {
@@ -82,61 +160,15 @@ public extension Express {
82
160
return
83
161
}
84
162
85
- let ext = fs. path. extname ( path)
86
- let viewEngine = ext. isEmpty ? defaultEngine : ext
87
- guard let engine = self . engines [ viewEngine] else {
88
- log. error ( " Did not find view engine for extension: \( viewEngine) " )
89
- res. emit ( error: ExpressRenderingError . unsupportedViewEngine ( viewEngine) )
90
- res. finishRender500IfNecessary ( )
91
- return
92
- }
93
-
94
- engine ( path, viewOptions) { ( results: Any? ... ) in
95
- let rc = results. count
96
- let v0 = rc > 0 ? results [ 0 ] : nil
97
- let v1 = rc > 1 ? results [ 1 ] : nil
98
-
99
- if let error = v0 {
100
- res. emit ( error: ExpressRenderingError
101
- . templateError ( error as? Swift . Error ) )
102
- log. error ( " template error: " , error)
103
- res. writeHead ( 500 )
104
- res. end ( )
105
- return
106
- }
107
-
108
- guard let result = v1 else { // Hm?
109
- log. warn ( " template returned no content: \( template) \( results) " )
110
- res. writeHead ( 204 )
111
- res. end ( )
112
- return
113
- }
114
-
115
- // TBD: maybe support a stream as a result? (result.pipe(res))
116
- // Or generators, there are many more options.
117
- if !( result is String ) {
118
- log. warn ( " template rendering result is not a String: " , result)
119
- }
120
-
121
- let s = ( result as? String ) ?? " \( result) "
122
-
123
- // Wow, this is harder than it looks when we want to consider a MIMEType
124
- // object as a value :-)
125
- var setContentType = true
126
- if let oldType = res. getHeader ( " Content-Type " ) {
127
- let s = ( oldType as? String ) ?? String ( describing: oldType) // FIXME
128
- setContentType = ( s == " httpd/unix-directory " ) // a hack for Apache
129
- }
130
-
131
- if setContentType {
132
- // FIXME: also consider extension of template (.html, .vcf etc)
133
- res. setHeader ( " Content-Type " , detectTypeForContent ( string: s) )
163
+ view. path = path // cache path
164
+ if cacheOn {
165
+ log. trace ( " Caching view: " , template)
166
+ self . viewCache. withLockedValue {
167
+ $0 [ template] = view
134
168
}
135
-
136
- res. writeHead ( 200 )
137
- res. write ( s)
138
- res. end ( )
139
169
}
170
+
171
+ self . render ( path: path, options: viewOptions, to: res)
140
172
}
141
173
}
142
174
0 commit comments