@@ -19,6 +19,7 @@ import org.http4s.server.middleware.Logger
19
19
import org .http4s .server .staticcontent .*
20
20
import org .http4s .server .staticcontent .FileService
21
21
import org .typelevel .ci .CIStringSyntax
22
+ import org .http4s .EntityBody
22
23
23
24
import fs2 .*
24
25
import fs2 .concurrent .Topic
@@ -36,6 +37,7 @@ import cats.effect.kernel.Resource
36
37
import cats .syntax .all .*
37
38
38
39
import _root_ .io .circe .syntax .EncoderOps
40
+ import org .http4s .Http
39
41
40
42
def routes [F [_]: Files : MonadThrow ](
41
43
stringPath : String ,
@@ -70,8 +72,8 @@ def routes[F[_]: Files: MonadThrow](
70
72
ref
71
73
)(logger)
72
74
73
- val hashFalse = vanillaTemplate(false ).render.hashCode.toString
74
- val hashTrue = vanillaTemplate(true ).render.hashCode.toString
75
+ // val hashFalse = vanillaTemplate(false).render.hashCode.toString
76
+ // val hashTrue = vanillaTemplate(true).render.hashCode.toString
75
77
val zdt = ZonedDateTime .now()
76
78
77
79
def userBrowserCacheHeaders (resp : Response [IO ], lastModZdt : ZonedDateTime , injectStyles : Boolean ) =
@@ -97,73 +99,95 @@ def routes[F[_]: Files: MonadThrow](
97
99
object StaticHtmlMiddleware :
98
100
def apply (service : HttpRoutes [IO ], injectStyles : Boolean )(logger : Scribe [IO ]): HttpRoutes [IO ] = Kleisli {
99
101
(req : Request [IO ]) =>
100
- req.headers.get(ci " If-None-Match " ).map(_.toList) match
101
- case Some (h :: Nil ) if h.value == hashFalse => OptionT .liftF(IO (Response [IO ](Status .NotModified )))
102
- case Some (h :: Nil ) if h.value == hashTrue => OptionT .liftF(IO (Response [IO ](Status .NotModified )))
103
- case _ => service(req).semiflatMap(userBrowserCacheHeaders(_, zdt, injectStyles))
104
- end match
105
-
102
+ service(req).semiflatMap(userBrowserCacheHeaders(_, zdt, injectStyles))
106
103
}
107
104
108
105
end StaticHtmlMiddleware
109
106
110
- def generatedIndexHtml (injectStyles : Boolean ) =
107
+ def generatedIndexHtml (injectStyles : Boolean , modules : Ref [ IO , Map [ String , String ]] ) =
111
108
StaticHtmlMiddleware (
112
109
HttpRoutes .of[IO ] {
113
110
case req @ GET -> Root =>
114
111
logger.trace(" Generated index.html" ) >>
115
- IO (
112
+ vanillaTemplate(injectStyles, modules).map : html =>
116
113
Response [IO ]()
117
- .withEntity(vanillaTemplate(injectStyles) )
114
+ .withEntity(html )
118
115
.withHeaders(
119
116
Header .Raw (ci " Cache-Control " , " no-cache" ),
120
117
Header .Raw (
121
118
ci " ETag " ,
122
- injectStyles match
123
- case true => hashTrue
124
- case false => hashFalse
119
+ html.hashCode.toString
125
120
),
126
121
Header .Raw (ci " Last-Modified " , formatter.format(zdt)),
127
122
Header .Raw (
128
123
ci " Expires " ,
129
124
httpCacheFormat(ZonedDateTime .ofInstant(Instant .now().plusSeconds(10000000 ), ZoneId .of(" GMT" )))
130
125
)
131
126
)
132
- )
127
+
133
128
},
134
129
injectStyles
135
130
)(logger).combineK(
136
131
StaticHtmlMiddleware (
137
132
HttpRoutes .of[IO ] {
138
133
case GET -> Root / " index.html" =>
139
- IO {
140
- Response [IO ]().withEntity(vanillaTemplate(injectStyles) )
141
- }
134
+ vanillaTemplate(injectStyles, modules).map : html =>
135
+ Response [IO ]().withEntity(html )
136
+
142
137
},
143
138
injectStyles
144
139
)(logger)
145
140
)
146
141
147
142
// val formatter = DateTimeFormatter.RFC_1123_DATE_TIME
148
- val staticAssetRoutes : HttpRoutes [IO ] = indexOpts match
149
- case None => generatedIndexHtml(injectStyles = false )
143
+ def staticAssetRoutes ( modules : Ref [ IO , Map [ String , String ]]) : HttpRoutes [IO ] = indexOpts match
144
+ case None => generatedIndexHtml(injectStyles = false , modules )
150
145
151
146
case Some (IndexHtmlConfig .IndexHtmlPath (path)) =>
152
- StaticMiddleware (
153
- Router (
154
- " " -> fileService[IO ](FileService .Config (path.toString()))
155
- ),
156
- fs2.io.file.Path (path.toString())
157
- )(logger)
147
+ // StaticMiddleware(
148
+ // Router(
149
+ // "" ->
150
+ HttpRoutes
151
+ .of[IO ] {
152
+ case req @ GET -> Root =>
153
+ StaticFile
154
+ .fromPath[IO ](path / " index.html" )
155
+ .getOrElseF(NotFound ())
156
+ .flatMap {
157
+ f =>
158
+ f.body
159
+ .through(text.utf8.decode)
160
+ .compile
161
+ .string
162
+ .flatMap {
163
+ body =>
164
+ for str <- injectModulePreloads(modules, body)
165
+ yield
166
+ val bytes = str.getBytes()
167
+ f.withEntity(bytes)
168
+ Response [IO ]().withEntity(bytes).putHeaders(" Content-Type" -> " text/html" )
169
+
170
+ }
171
+ }
172
+
173
+ }
174
+ .combineK(
175
+ StaticMiddleware (
176
+ Router (
177
+ " " -> fileService[IO ](FileService .Config (path.toString()))
178
+ ),
179
+ fs2.io.file.Path (path.toString())
180
+ )(logger)
181
+ )
158
182
159
183
case Some (IndexHtmlConfig .StylesOnly (stylesPath)) =>
160
184
NoCacheMiddlware (
161
185
Router (
162
186
" " -> fileService[IO ](FileService .Config (stylesPath.toString()))
163
187
)
164
- )(logger).combineK(generatedIndexHtml(injectStyles = true ))
188
+ )(logger).combineK(generatedIndexHtml(injectStyles = true , modules ))
165
189
166
- val clientSpaRoutes : HttpRoutes [IO ] =
190
+ def clientSpaRoutes ( modules : Ref [ IO , Map [ String , String ]]) : HttpRoutes [IO ] =
167
191
clientRoutingPrefix match
168
192
case None => HttpRoutes .empty[IO ]
169
193
case Some (spaRoute) =>
@@ -173,9 +197,9 @@ def routes[F[_]: Files: MonadThrow](
173
197
StaticHtmlMiddleware (
174
198
HttpRoutes .of[IO ] {
175
199
case req @ GET -> root /: path =>
176
- IO (
177
- Response [IO ]().withEntity(vanillaTemplate( false ) )
178
- )
200
+ vanillaTemplate( false , modules).map : html =>
201
+ Response [IO ]().withEntity(html )
202
+
179
203
},
180
204
false
181
205
)(logger)
@@ -184,9 +208,8 @@ def routes[F[_]: Files: MonadThrow](
184
208
StaticHtmlMiddleware (
185
209
HttpRoutes .of[IO ] {
186
210
case GET -> root /: spaRoute /: path =>
187
- IO (
188
- Response [IO ]().withEntity(vanillaTemplate(true ))
189
- )
211
+ vanillaTemplate(true , modules).map: html =>
212
+ Response [IO ]().withEntity(html)
190
213
},
191
214
true
192
215
)(logger)
@@ -195,7 +218,24 @@ def routes[F[_]: Files: MonadThrow](
195
218
StaticFileMiddleware (
196
219
HttpRoutes .of[IO ] {
197
220
case req @ GET -> spaRoute /: path =>
198
- StaticFile .fromPath(dir / " index.html" , Some (req)).getOrElseF(NotFound ())
221
+ StaticFile
222
+ .fromPath(dir / " index.html" , Some (req))
223
+ .getOrElseF(NotFound ())
224
+ .flatMap {
225
+ f =>
226
+ f.body
227
+ .through(text.utf8.decode)
228
+ .compile
229
+ .string
230
+ .flatMap: body =>
231
+ for str <- injectModulePreloads(modules, body)
232
+ yield
233
+ val bytes = str.getBytes()
234
+ f.withEntity(bytes)
235
+ f
236
+
237
+ }
238
+
199
239
},
200
240
dir / " index.html"
201
241
)(logger)
@@ -215,8 +255,8 @@ def routes[F[_]: Files: MonadThrow](
215
255
refreshRoutes
216
256
.combineK(linkedAppWithCaching)
217
257
.combineK(proxyRoutes)
218
- .combineK(clientSpaRoutes)
219
- .combineK(staticAssetRoutes)
258
+ .combineK(clientSpaRoutes(ref) )
259
+ .combineK(staticAssetRoutes(ref) )
220
260
)
221
261
222
262
clientRoutingPrefix.fold(IO .unit)(s => logger.trace(s " client spa at : $s" )).toResource >>
0 commit comments