5 · Templates, static & forms

Las APIs devuelven JSON; las páginas devuelven HTML. El motor de plantillas de nyx-serve renderiza HTML con un default de seguridad que importa: {{value}} se escapa como HTML. Tienes que optar por salir con {{{raw}}} — lo peligroso es la excepción, no el default que te olvidas.

Renderizar una página

import "nyx-serve/src/template"

fn page_notes(req: Request) -> Response {
    var item1: Map = map_new()
    item1.insert("title", "Buy coffee")
    var item2: Map = map_new()
    item2.insert("title", "<script>alert(1)</script>")
    var items: Array = [item1, item2]

    var ctx: Map = map_new()
    ctx.insert("heading", "My notes")
    ctx.insert("has_items", "1")
    ctx.insert("items", items)

    let tmpl: String = "<h1>{{heading}}</h1>
        {{#if has_items}}
          <ul>{{#each items}}<li>{{title}}</li>{{/each}}</ul>
        {{else}}<p>No notes yet.</p>{{/if}}"
    return response_html(200, tpl_render(tmpl, ctx))
}

Ese segundo ítem — un tag <script> — se renderiza como texto escapado, no como un tag vivo. Lo que ofrece la plantilla:

Archivos estáticos

Serví un directorio bajo un prefijo de URL. La variante _cached agrega Cache-Control y Last-Modified para que navegadores y CDNs se salteen las re-descargas.

import "nyx-serve/src/files"

// in main():
app_static_cached(app, "/assets/", "public/", 3600)

Un request a /assets/style.css ahora sirve public/style.css con una hora de cache.

Forms y uploads

Los campos de form clásicos llegan ya parseados en req.form (lo viste en el paso 3). Las subidas de archivos usan multipart_parse, que es binary-safe — un PNG con bytes NUL llega intacto:

import "nyx-serve/src/multipart"

fn upload(req: Request) -> Response {
    let ctype: String = http_find_header(req.headers_flat, "Content-Type")
    let parts: Array = multipart_parse(req.body, ctype)
    let p: Array = parts[0]
    let filename: String = part_filename(p)
    let data: String = part_value(p)
    return response_text(200,
        filename + ": " + int_to_string(data.length()) + " bytes")
}
Siempre lee las partes con los accessors — part_name, part_filename, part_ctype, part_value. El layout de 4 slots detrás es interno.