├── .dockerignore
├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── tasks.json
└── vscode_config_extensions.json
├── Dockerfile
├── LICENSE
├── README.md
├── TODO.md
├── app
├── README.md
└── app.go
├── build.go
├── cmd
└── main.go
├── config.go
├── dot.go
├── dot_db.go
├── dot_db_config.go
├── dot_flags.go
├── dot_flush.go
├── dot_fs.go
├── dot_fs_config.go
├── dot_instance.go
├── dot_kv.go
├── dot_nats.go
├── dot_nats_config.go
├── dot_req.go
├── dot_resp.go
├── frontmatter.go
├── funcs.go
├── go.mod
├── go.sum
├── handlers.go
├── instance.go
├── make_tool.cue
├── server.go
└── test
├── .gitattributes
├── .gitignore
├── caddy.json
├── config.json
├── data
├── foo.txt
├── hello.txt
└── subdir
│ └── world.txt
├── migrations
├── manual.1.sql
├── manual.2.sql
├── schema.1.sql
├── schema.10.sql
└── schema.2.sql
├── templates
├── .migration.html
├── assets
│ ├── empty.txt
│ ├── file.txt
│ ├── file.txt.gz
│ ├── reset.css
│ ├── reset.css.br
│ ├── reset.css.gz
│ └── standalone.gz
├── db
│ ├── .init.html
│ ├── index.html
│ └── manual.html
├── favicon.ico
├── flags
│ └── index.html
├── fs
│ ├── browse
│ │ └── {filepath...}.html
│ ├── openclose.html
│ └── serve.html
├── index{$}.html
├── nats
│ └── index.html
├── ready.html
├── routing
│ ├── .hidden.html
│ ├── file.html
│ ├── index.html
│ └── visible.html
└── sse
│ ├── hotreload.html
│ └── test.html
└── tests
├── assets.hurl
├── db.hurl
├── flags.hurl
├── fs.hurl
├── nats.hurl.disabled
├── routing.hurl
└── sse.hurl
/.dockerignore:
--------------------------------------------------------------------------------
1 | go.work*
2 | xtemplate*
3 | caddy
4 | Dockerfile
5 | .dockerignore
6 | .gitignore
7 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: [push, pull_request, workflow_dispatch]
4 |
5 | permissions:
6 | contents: write
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: actions/setup-go@v5
14 | with:
15 | go-version: '1.23'
16 | - uses: gacts/install-hurl@v1
17 | - uses: cue-lang/setup-cue@v1.0.0
18 | - run: go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
19 |
20 | - name: Check secrets availability
21 | id: secrets_available
22 | continue-on-error: true
23 | run: |
24 | [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ] && echo "::error::Secrets unavailable" && exit 1 || exit 0
25 | - name: Login to Docker Hub
26 | uses: docker/login-action@v3
27 | if: ${{ steps.secrets_available.conclusion == 'success' }}
28 | with:
29 | username: ${{ github.repository_owner }}
30 | password: ${{ secrets.DOCKERHUB_TOKEN }}
31 |
32 | # CUE_DEBUG_TOOLS_FLOW=true cue cmd ci
33 | - run: cue cmd ci
34 |
35 | - name: Archive test results
36 | uses: actions/upload-artifact@v4
37 | if: always()
38 | with:
39 | name: logs
40 | path: |
41 | test/**/*.log
42 | test/**/report/
43 |
44 | - uses: actions/upload-artifact@v4
45 | with:
46 | name: xtemplate-amd64-linux
47 | path: 'dist/xtemplate-amd64-linux/*'
48 |
49 | - uses: actions/upload-artifact@v4
50 | with:
51 | name: xtemplate-amd64-darwin
52 | path: 'dist/xtemplate-amd64-darwin/*'
53 |
54 | - uses: actions/upload-artifact@v4
55 | with:
56 | name: xtemplate-amd64-windows
57 | path: 'dist/xtemplate-amd64-windows/*'
58 |
59 | - name: Release
60 | if: startsWith(github.ref, 'refs/tags/v')
61 | uses: softprops/action-gh-release@v1
62 | with:
63 | files: 'dist/*.zip'
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | go.work*
2 | xtemplate
3 | xtemplate.*
4 | caddy
5 | dist
6 | __debug_bin*
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "golang.go",
4 | "cuelangorg.vscode-cue"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Debug xtemplate",
6 | "type": "go",
7 | "request": "launch",
8 | "mode": "debug",
9 | "showLog": true,
10 | "program": "${workspaceFolder}/cmd",
11 | "cwd": "${workspaceFolder}/test",
12 | "args": [
13 | "--loglevel",
14 | "-4",
15 | "--listen",
16 | ":8080",
17 | "--config-file",
18 | "config.json"
19 | ],
20 | "env": {
21 | "CGO_ENABLED": "1"
22 | }
23 | },
24 | {
25 | "name": "Debug xtemplate-caddy",
26 | "type": "go",
27 | "request": "launch",
28 | "mode": "debug",
29 | "showLog": true,
30 | "program": "${workspaceFolder}/test/caddy",
31 | "cwd": "${workspaceFolder}/test",
32 | "args": [
33 | "run",
34 | "--config",
35 | "caddy.json"
36 | ]
37 | },
38 | ]
39 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "Test",
8 | "type": "shell",
9 | "command": "hurl --test --glob '${workspaceFolder}/test/tests/*.hurl'",
10 | "problemMatcher": []
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/.vscode/vscode_config_extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["golang.go"]
3 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1-alpine AS deps
2 |
3 | RUN apk add --no-cache build-base
4 |
5 | WORKDIR /src
6 | COPY go.mod go.sum ./
7 | RUN go mod download -x
8 |
9 | ###
10 |
11 | FROM deps AS build
12 |
13 | ARG LDFLAGS
14 |
15 | COPY app ./app/
16 | COPY cmd ./cmd/
17 | COPY *.go ./
18 | RUN CGO_ENABLED=1 \
19 | GOFLAGS='-tags="sqlite_json"' \
20 | GOOS=linux \
21 | GOARCH=amd64 \
22 | go build -x -ldflags="${LDFLAGS} -X 'github.com/infogulch/xtemplate/app.defaultWatchTemplates=false' -X 'github.com/infogulch/xtemplate/app.defaultListenAddress=0.0.0.0:80'" -o /build/xtemplate ./cmd
23 |
24 | ###
25 |
26 | FROM alpine AS dist
27 |
28 | ENV USER=appuser
29 | ENV UID=10001
30 | RUN adduser \
31 | --disabled-password \
32 | --gecos "" \
33 | --home "/nonexistent" \
34 | --shell "/sbin/nologin" \
35 | --no-create-home \
36 | --uid "${UID}" \
37 | "${USER}"
38 | WORKDIR /app
39 | USER $USER:$USER
40 | EXPOSE 80
41 |
42 | COPY --from=build /build/xtemplate /app/xtemplate
43 |
44 | ENTRYPOINT ["/app/xtemplate"]
45 |
46 | ###
47 |
48 | FROM dist AS test
49 |
50 | COPY ./test/templates /app/templates/
51 | COPY ./test/data /app/data/
52 | COPY ./test/migrations /app/migrations/
53 | COPY ./test/config.json /app/
54 |
55 | USER root:root
56 | RUN mkdir /app/dataw
57 |
58 | VOLUME /app/dataw
59 |
60 | RUN ["/app/xtemplate", "--version"]
61 |
62 | WORKDIR /app/dataw
63 |
64 | CMD ["--loglevel", "-4", "--config-file", "../config.json"]
65 |
66 | ###
67 |
68 | FROM dist as final
69 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2015 Matthew Holt and The Caddy Authors
190 | Copyright 2024 Joseph Taber (infogulch)
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # xtemplate
2 |
3 | `xtemplate` is a html/template-based hypertext preprocessor and rapid
4 | application development web server written in Go. It streamlines construction of
5 | hypermedia-exchange-oriented web sites by efficiently handling basic server
6 | tasks, enabling authors to focus on defining routes and responding to them using
7 | templates and configurable data sources.
8 |
9 | ## 🎯 Goal
10 |
11 | After bulding some sites with [htmx](https://htmx.org) and Go, I wished that
12 | everything would just get out of the way of the fundamentals:
13 |
14 | - URLs and path patterns
15 | - Access to a backing data source
16 | - Executing a template to return HTML
17 |
18 | 🎇 **The idea of `xtemplate` is that *templates* can be the nexus of these
19 | fundamentals.**
20 |
21 | 🚫 Anti-goals
22 |
23 | `xtemplate` needs to implement some of the things that are required to make a
24 | good web server in a way that avoids common issues with existing web server
25 | designs, otherwise they'll be in the way of the fundamentals:
26 |
27 | * **Rigid template behavior**: Most designs relegate templates to be dumb string
28 | concatenators with just enough dynamic behavior to walk over some fixed data
29 | structure.
30 | * **Inefficient template loading**: Some designs read template files from disk
31 | and parse them on every request. This seems wasteful when the web server
32 | definition is typically static.
33 | * **Constant rebuilds**: On the other end of the spectrum, some designs require
34 | rebuilding the entire server from source when any little thing changes. This
35 | seems wasteful and makes graceful restarts more difficult than necessary when
36 | all you're doing is changing a button name.
37 | * **Repetitive route definitions**: Why should you have to name a http handler
38 | and add it to a central registry (or maintain a pile of code that plumbs these
39 | together for you) when new routes are often only relevant to the local html?
40 | * **Default unsafe**: Some designs require authors to vigilantly escape user
41 | inputs, risking XSS attacks that could have been avoided with less effort.
42 | * **Inefficient asset serving**: Some designs compress static assets at request
43 | time, instead of serving pre-compressed content with sendfile(2) and
44 | negotiated content encoding. Most designs don't give templates access to the
45 | hash of asset files, depriving clients of enough information to optimize
46 | caching behavior and check resource integrity.
47 |
48 |
49 |
50 | ## ✨ Features
51 |
52 | *Click a feature to expand and show details:*
53 |
54 | ⚡ Efficient design
55 |
56 | > All template files are read and parsed *once*, at startup, and kept in memory
57 | > during the life of an xtemplate *instance*. Requests are routed to a handler
58 | > that immediately starts executing a template reference in response. No slow
59 | > cascading disk accesses or parsing overhead before you even begin crafting the
60 | > response.
61 |
62 |
63 | 🔄 Live reload
64 |
65 | > Template files are loaded into a new instance and validated milliseconds after
66 | > they are modified, no need to restart the server. If an error occurs during
67 | > load the previous instance remains intact and continues to serve while the
68 | > loading error is printed to the logs. A successful reload atomically swaps the
69 | > handler so new requests are served by the new instance; pending requests are
70 | > allowed to complete gracefully.
71 | >
72 | > Add this template definition and one-line script to your page, then
73 | > clients will automatically reload when the server does:
74 | >
75 | > ```html
76 | > {{- define "SSE /reload"}}{{.WaitForServerStop}}data: reload{{printf "\n\n"}}{{end}}
77 | >
78 | >
79 | > ```
80 |
81 |
82 | 🗃️ Simple file-based routing
83 |
84 | > `GET` requests are handled by invoking a matching template file at that path.
85 | > (Hidden files that start with `.` are loaded but not routed by default.)
86 | >
87 | > ```
88 | > File path: HTTP path:
89 | > .
90 | > ├── index.html GET /
91 | > ├── todos.html GET /todos
92 | > ├── admin
93 | > │ └── settings.html GET /admin/settings
94 | > └── shared
95 | > └── .head.html (not routed because it starts with '.')
96 | > ```
97 |
98 |
99 | 🔱 Add custom routes to handle any method and path pattern
100 |
101 | > Handle any [Go 1.22 ServeMux][servemux] pattern by **defining a template with
102 | > that pattern as its name**. Path placeholders are available during template
103 | > execution with the `.Req.PathValue` method.
104 | >
105 | > ```html
106 | >
107 | > {{define "GET /contact/{id}"}}
108 | > {{$contact := .QueryRow `SELECT name,phone FROM contacts WHERE id=?` (.Req.PathValue "id")}}
109 | >
113 | > {{end}}
114 | >
115 | >
116 | > {{define "DELETE /contact/{id}"}}
117 | > {{$_ := .Exec `DELETE from contacts WHERE id=?` (.Req.PathValue "id")}}
118 | > {{.RespStatus 204}}
119 | > {{end}}
120 | > ```
121 |
122 | [servemux]: https://tip.golang.org/doc/go1.22#enhanced_routing_patterns
123 |
124 |
125 |
126 | 👨💻 Define and invoke custom templates
127 |
128 | > All html files under the template root directory are available to invoke by
129 | > their full path relative to the template root dir starting with `/`:
130 | >
131 | > ```html
132 | >
133 | > Home
134 | >
135 | > {{template "/shared/.head.html" .}}
136 | >
137 | >
138 | >
139 | > {{template "navbar" .}}
140 | > ...
141 | >
142 | >
143 | > ```
144 |
145 |
146 | 🛡️ XSS safe by default
147 |
148 | > The html/template library automatically escapes user content, so you can rest
149 | > easy from basic XSS attacks. The defacto standard html sanitizer for Go,
150 | > BlueMonday, is available for cases where you need finer grained control.
151 | >
152 | > If you have some html string that you do trust, it's easy to inject if that's
153 | > your intention with the `trustHtml` func.
154 |
155 |
156 | 🎨 Customize the context to provide selected data sources
157 |
158 | > Configure xtemplate to get access to built-in and custom data sources like
159 | > running SQL queries against a database, sending and receiving messages using a
160 | > message streaming client like NATS, read and list files from a local
161 | > directory, reading static config from a key-value store, **or perform any
162 | > action you can define by writing a Go API**, like the common "repository"
163 | > design pattern for example.
164 | >
165 | > Modify `Config` to add built-in or custom `ContextProvider` implementations,
166 | > and they will be made available in the dot context.
167 | >
168 | > Some built-in context providers are listed next:
169 |
170 |
171 | 💽 Database context provider: Execute queries
172 |
173 | > Add the built-in Database Context Provider to run queries using the configured
174 | > Go driver and connection string for your database. (Supports the `sqlite3`
175 | > driver by default, compile with your desired driver to use it.)
176 | >
177 | > ```html
178 | >
183 | > ```
184 |
185 |
186 | 🗄️ Filesystem context provider: List and read local files
187 |
188 | > Add the built-in Filesystem Context Provider to List and read
189 | > files from the configured directory.
190 | >
191 | > ```html
192 | >
Here are the files:
193 | >
194 | > {{range .ListFiles "dir/"}}
195 | >
{{.Name}}
196 | > {{end}}
197 | >
198 | > ```
199 |
200 |
201 | 💬 NATS context provider: Send and receive messages
202 |
203 | > Add and configure the NATS Context Provider to send messages, use the
204 | > Request-Response pattern, and even send live updates to a client.
205 | >
206 | > ```html
207 | >
208 | > ```
209 |
210 |
211 | 📤 Optimal asset serving
212 |
213 | > Non-template files in the templates directory are served directly from disk
214 | > with appropriate caching responses, negotiating with the client to serve
215 | > compressed versions. Efficient access to the content hash is available to
216 | > templates for efficient SRI and perfect cache behavior.
217 | >
218 | > If a static file also has .gz, .br, .zip, or .zst copies, they are decoded and
219 | > hashed for consistency on startup, and use the `Accept-Encoding` header to
220 | > negotiate an appropriate `Content-Encoding` with the client and served
221 | > directly from disk.
222 | >
223 | > Templates can efficiently access the static file's precalculated content hash
224 | > to build a `
3 |
4 | {{$RE := `^manual\.(\d+)\.sql$`}}
5 |
19 | {{else}}
20 | File size: {{$stat.Size}}
21 | {{end}}
22 |
--------------------------------------------------------------------------------
/test/templates/fs/openclose.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | You can open a file and close it manually:
4 |
5 | {{$file := .FS.Open "foo.txt"}}
6 | {{$file.Close}}
7 |
8 | Any opened files are automatically closed at the end of the request, but if you
9 | open many files in the same request it may be necessary to close them manually
10 | to conserve resources.
11 |
--------------------------------------------------------------------------------
/test/templates/fs/serve.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | You can serve a file by opening it and using .Resp.ServeContent, which discards
4 | any content rendered so far and responds with the contents of the file instead.
5 |
6 | You can still set headers that are added to the response.
7 | {{.Resp.AddHeader "Content-Type" "text/plain; charset=utf-8"}}
8 |
9 | {{$file := .FS.Open "foo.txt"}}
10 | {{$stat := $file.Stat}}
11 | {{.Resp.ServeContent $stat.Name $stat.ModTime $file}}
12 |
13 | Opened files are automatically closed when the request completes.
14 |
--------------------------------------------------------------------------------
/test/templates/index{$}.html:
--------------------------------------------------------------------------------
1 |
2 | {{- with $hash := .X.StaticFileHash `/assets/reset.css`}}
3 |
4 | {{- end}}
5 |