# Luan websites
A Luan website is a directory, usually `src/`, served by Luan's web server. This file covers how requests are served, static sites, and hosting. Sites that store data are in [dynamic-sites.md](dynamic-sites.md). The `Http` module is described in [library/http/Http.md](library/http/Http.md).
## How a URL finds its code
- A URL maps to `site:` plus the path plus `.luan`: `/x/page.html` runs `src/x/page.html.luan`, and `/x/run` runs `src/x/run.luan`.
- A path ending in `/` tries `index.html` first, meaning a static `src/index.html` or `src/index.html.luan`. If neither exists, the directory is listed. So `/` is usually `src/index.html.luan`.
- A file without `.luan` is served as it is: `/site.css` is `src/site.css`. A path that is neither runs `Http.not_found_handler` if the site defines one, otherwise it's a 404.
- A page module returns a function, which the server calls for each request. It reads `Http.request` and writes to `Http.response`. If the function writes nothing, the response is empty.
- `init.luan` in `src/`, if present, runs once when the site starts. It is optional; blog has none.
## How a request reaches a site
The server is goodjava's webserver, where a handler takes a `Request` struct and returns a `Response` struct, or `null` if it doesn't handle the request. Luan chains these handlers (`src/luan/modules/http/Server.luan` locally, `src/luan/host/WebHandler.java` when hosted):
1. `LogHandler` logs the request.
2. `SafeHandler` rejects any path containing `..` with 400, turns an exception into a 500 page, and turns "no handler" into a 404.
3. `HeadersHandler`, then, when hosted, the password check for `/private/`.
4. The Luan page or the static file (for a path ending in `/`, its `index.html`), then a directory listing, then `Http.not_found_handler`.
`ContentTypeHandler` wraps the page, file and not-found handlers, and sets `Content-Type` from the path's extension when the response has none (`.luan` is `text/plain`, `.md` is `text/markdown`, `.html` is HTML). So a handler that serves a file needn't set the type. Check this chain before deciding that a response lacks a header.
## The request model
Each page module is loaded once into the site's main interpreter, and cached. Each request then runs the page's function in a fresh clone of that interpreter, so per-request state is just per-interpreter state and needs no synchronization. Because of the cache, restart the server after changing code.
## A page
```lua
local Luan = require "luan:Luan.luan"
local error = Luan.error
local Io = require "luan:Io.luan"
local Http = require "luan:http/Http.luan"
local Shared = require "site:/lib/Shared.luan"
local head = Shared.head or error()
local header = Shared.header or error()
return function()
Io.stdout = Http.response.text_writer()
%>
<!doctype html>
<html lang="en">
<head>
<% head() %>
<title>About</title>
</head>
<body>
<% header() %>
<div content>
<h1>About</h1>
<p>...</p>
</div>
</body>
</html>
<%
end
```
`lib/Shared.luan` holds what the site's pages share. `Shared.head()` writes the `<meta>` and stylesheet lines, and `Shared.header()` the top of each page:
```lua
local Shared = {}
local started = Time.now()
function Shared.head()
%>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@import "/site.css?s=<%=started%>";
</style>
<script src="/site.js?s=<%=started%>"></script>
<%
end
return Shared
```
The `?s=<%=started%>` changes each time the server starts, so browsers fetch new copies of the CSS and JavaScript after a change. The `@import` is used instead of `<link rel="stylesheet">` because it is easier to read; the small cost in speed doesn't matter.
`lib/Utils.luan` holds general helpers that know nothing about the site, so it could be copied to any site. `Shared.luan` holds what is particular to this site, and may require `Utils`, never the other way round.
## Static sites
The luan website (`website/src` in the luan repository) and the Reactionary Software site are static sites written in Luan: pages are generated, but nothing is stored.
- Most pages are a returned function with one template, as above.
- Documentation pages build a table of sections, each with a `title`, a `content` function holding a template, and optional `subs`, and render it with `Shared.show_toc` and `Shared.show_content` (see `website/src/docs.html.luan` and `manual.html.luan`).
- Small working examples are in `website/src/examples`: forms (`hi2`), chunked output, server-sent events (`sse_push`, `sse_pull`), file upload and mail (`upload-and-email`).
- These sites are in English only; readers use their browser's translation. Text that a translator must not change is marked `translate="no"`: every `<code>` and `<pre>`. Code-like text inside prose is `<code bold translate="no">` or `<code italic translate="no">`, styled like ordinary bold and italic. Plain `<b translate="no">` isn't enough, because translators then mangle the spacing around it. Browser translation is still poor, so this is not finished.
## Hosting
Luan sites are hosted on Luan's own hosting service.
- `luan luan:host/push.luan <domain> <password> src` publishes a directory to a hosted site: it copies new and changed files, creates directories, deletes remote files that no longer exist locally, and reloads the site. Projects have scripts for this, like `push-test.sh`. Directories named `local` are never pushed or deleted, so a site keeps its runtime data under `private/local/` and the data survives pushes.
- When hosted, `src/luan/host/init.luan` runs before the site. It calls `Http.set_domain(domain)`, sets `Hosted.is_hosted = true`, and defines functions like `Hosted.set_https`. Site code calls such functions only if they exist: `Hosted.set_https and Hosted.set_https(...)`.
- `/private/` is protected with HTTP authentication when hosted (user `admin`, the site's password). `Io.password` is that password when hosted, and `"password"` locally.
- A site can add its own functions to `Rpc.functions`, callable from outside. The host doesn't check the password for these, so each must check it itself: `Io.password == password or error "wrong password"`.
- Hosted sites can't use Java directly, and `file:` URIs must stay inside the site's directory. The standard library uses Java on the site's behalf.
- The domain `www.me.luan.software` maps to localhost, for testing hosting locally.
## Security
- Escape everything shown in HTML with `Html.encode`, including values from the request. `Html.encode` escapes `<`, `>`, `"` and entity-like `&`, but not `'`, so use it only in text and in double-quoted attributes. It errors on `nil`.
- Better still, write values from the database rather than from the request: `post.id`, not the `post` parameter. A successful lookup doesn't make a parameter safe, because Lucene's query parser accepts extra terms: `?post=3 \"><script>...` still finds post 3.
- A URL supplied by a user needs its scheme checked (`starts_with(url,"http")`), because encoding doesn't stop `javascript:` in an `href`.
- Code that maps a request path to a file is protected from `..` by `SafeHandler`. Test any such code with `curl --path-as-is` using both `..` and `%2e%2e`; browsers remove `..` before sending, so they never show the problem.
- For a check that a path stays inside a directory, compare canonical paths (Java `getCanonicalPath()`), and match the directory plus `/`, so `site_other` doesn't match `site`.