GenHTTP Lambda

Comment ça marche

Vous écrivez un snippet C#. Ce qu’il renvoie est hébergé à une adresse publique, en HTTPS, en quelques secondes. Voici tout ce qu’il faut savoir, dans l’ordre où vous le découvrirez.

Qu’est-ce qu’une lambda ?

Une lambda est un snippet qui renvoie un handler GenHTTP. La plateforme le compile, le charge et monte ce qu’il renvoie sous votre propre adresse. Pas de projet, pas de fichier de build, pas d’instruction using. Tous les modules GenHTTP sont déjà importés pour vous.

return Content.From(Resource.FromString("hello"));

Voilà une lambda complète. Déployée sur /lambda/your-key/, elle répond « hello » à chaque requête.

Le snippet est une suite d’instructions, pas une classe. Il finit par renvoyer quelque chose qui sait servir des requêtes : un handler, ou un builder de handler.

Votre première lambda

  1. 1
    Cliquez sur Créer ma lambda. Vous obtenez une adresse publique et une clé d’édition. Cette clé est le seul moyen d’y revenir : gardez-la. Personne ne pourra la récupérer pour vous.
  2. 2
    Vous arrivez sur son tableau de bord, avec un petit service REST déjà écrit en guise de première version. Ce n’est qu’un point de départ.
  3. 3
    Donnez la clé d’édition à un agent et dites-lui quoi construire : il écrit de nouvelles versions via MCP. Ou ouvrez Code et écrivez le code vous-même : Vérifier compile sans rien enregistrer et vous montre ce qu’en dit le compilateur, avec le fichier et la ligne.
  4. 4
    Cliquez sur Déployer. Votre lambda est en ligne. Avant ça, rien n’est accessible. Chaque nouveau déploiement prolonge sa durée en ligne.

Le tableau de bord

Le lien d’édition ouvre un tableau de bord plutôt qu’une zone de texte : ici, l’essentiel du code est écrit par des agents, donc l’écran montre d’abord comment va votre lambda. La barre latérale contient la lambda (en ligne ou non, son adresse, et un bouton quand une version plus récente attend d’être mise en ligne) et ses sections. Tout ce qui sert rarement, comme changer l’adresse ou supprimer la lambda, se trouve dans le menu ⋯.

Vue d’ensemble
Ce qu’est l’application, en ligne ou non, le nombre de requêtes du jour et d’échecs, la dernière modification, et la place qui reste.
Documentation
Ce qu’est l’application, à qui elle s’adresse et pourquoi, et pourquoi elle est construite ainsi : rédigée par les agents, gardée avec chaque version.
Modifier
Dites ce qui doit changer, et l’agent de ce serveur s’en charge sous vos yeux. Il essaie la modification dans un brouillon - une copie à sa propre adresse - et la met en ligne une fois qu’elle fonctionne. Désactivez Mettre en ligne une fois terminé pour essayer vous-même le brouillon d’abord. Il ne travaille que sur votre app : une demande qui ne la concerne pas, ou qui vise à nuire, est refusée, et il dit pourquoi.
Brouillons
Des modifications essayées avant d’être mises en ligne, chacune à sa propre adresse et sur ses propres données de test. Une fois ouvert, un brouillon a son propre code, ses propres données de test et ses propres logs. La section apparaît dès qu’il y a un brouillon.
Fichiers
Les fichiers d’une version : son code et ses assets, le programme lui-même. Un cadenas ou un globe indique si le public peut y accéder.
Données
Ce que la lambda garde pendant qu’elle tourne, partagé par toutes les versions : la base de données, le workspace et les secrets, chacun dans son onglet. Consultez les tables et les fichiers, envoyez des fichiers, définissez des secrets, ou activez et désactivez un type. La vue simple l’affiche dès que l’application garde quelque chose.
Versions
Ce que chaque version a changé, ce qui avait été demandé, et la différence avec la précédente. C’est ici qu’on déploie ou qu’on revient en arrière, ou qu’on démarre un brouillon à partir de n’importe quelle version.
Déploiements
Ce qui était en ligne, quand, et ce qui l’a arrêté.
Stats
Requêtes, échecs, temps de réponse et chemins les plus demandés, sur la dernière heure ou les dernières 24 heures.
Logs
Ses requêtes, ce qu’elle affiche, et la stack trace de tout ce qui plante, en direct.
Code
Pour écrire le code à la main. Vérifier compile, Enregistrer crée une version, Déployer met en ligne. Dans un brouillon, Enregistrer garde le code dans le brouillon et l’affiche à l’adresse du brouillon. Ctrl-S enregistre ; F12 va à une déclaration.
Tests
Comment l’application est testée automatiquement, avec les scripts et les données de test prévus pour cela. Uniquement dans la vue complète.

Chaque section fonctionne de la même façon : son titre, un ⓘ qui l’explique, ses actions à droite et, quand elle a plusieurs vues, une rangée d’onglets en dessous. Pour le code, les onglets sont ses fichiers. La vue complète range les sections en groupes : la façon dont on la trouve, là où se fait une modification, le programme et ses données, et son fonctionnement.

Le trafic et les logs sont gardés en mémoire, pour suivre ce qui se passe, pas pour archiver : un redémarrage du serveur les remet à zéro. Les versions et l’historique des déploiements, eux, sont enregistrés.

Dire pourquoi

Une version, c’est le code, plus deux notes facultatives : la spécification, ce que veut l’utilisateur et pourquoi, si possible avec ses mots, et le changement, une ligne sur ce que fait la version. Elles s’affichent à côté du diff dans l’historique des versions. Le pourquoi reste ainsi à côté du quoi, pour vous, et pour le prochain agent qui lira l’historique avant de toucher à quoi que ce soit.

POST /api/v1/lambdas/{editorKey}/versions
{
  "files": [ { "name": "lambda.cs", "code": "..." } ],
  "specification": "Un livre d’or que les gens peuvent signer ; les messages doivent survivre à un redémarrage",
  "change": "Garde les messages dans la base de données pour qu’ils survivent à un redémarrage"
}

Les agents passent les deux mêmes champs à write_code. Dans Code, l’enregistrement vous demande le changement. Les deux sont facultatifs. Trop longs, ils ne sont pas refusés mais coupés : à 4 000 caractères pour la spécification, à 500 pour le changement. Un brouillon a ses deux notes à lui, et la version dans laquelle il est intégré les reprend.

Documentation et tests

Chaque version garde, à côté de son programme, ce qui est écrit à son sujet : sa documentation (ce qu’est l’application, à qui elle s’adresse et pourquoi, et pourquoi elle est construite ainsi) et ses tests, qui disent comment vérifier automatiquement qu’elle fonctionne, avec les scripts et les données de test prévus pour cela. Les agents les rédigent avec une nouvelle lambda et les tiennent à jour à chaque modification. Le prochain agent qui modifie la lambda les lit d’abord : il sait ainsi à quoi sert l’application et ce qui doit continuer à marcher, ce que le code seul ne dit pas.

.lambda/docs/product.md
ce qu’est l’application, à qui elle s’adresse, ce qu’on en fait et pourquoi
.lambda/docs/decisions.md
les décisions techniques, et pourquoi elles ont été prises
.lambda/tests/README.md
comment l’application est testée automatiquement, et comment lancer les tests
.lambda/tests/…
les scripts et les données de test qu’utilisent les tests

Ce sont des fichiers de la version comme les autres, dans le dossier .lambda : l’historique montre ce qu’une version y a changé, revenir en arrière ramène la documentation qui valait pour cette version, et un brouillon en a sa propre copie, mise en ligne avec lui. Ils ne sont jamais compilés ni servis, et comptent dans la place que peuvent occuper les assets d’une version.

Dans le tableau de bord, Documentation montre les pages à lire, et Tests comment l’application est testée, avec les fichiers qui l’accompagnent ; la version se choisit comme pour ses fichiers. Une page peut aussi y être modifiée, ce qui enregistre la version suivante. La vue simple appelle la documentation À propos et ne montre que ce à quoi sert l’application : pour la corriger, dites-le à l’agent.

Ils sont rédigés dans la langue que vous utilisez avec l’agent, pour la personne ou l’agent qui modifiera l’application ensuite. Pas une copie du code : à quoi elle sert, et pourquoi.

Modifier sans risque

Une version ne change plus une fois enregistrée, et c’est ce qui fait que chacune vaut la peine d’être gardée : on peut comparer n’importe laquelle, et la remettre en ligne exactement telle qu’elle était. Pour modifier une lambda que des gens utilisent, démarrez plutôt un brouillon.

  1. 1
    Démarrez-le à partir de n’importe quelle version, dans Versions, ou laissez l’agent en démarrer un. C’est une copie du code, des assets, de la documentation et des tests de cette version, et des données de la lambda.
  2. 2
    Modifiez-le autant de fois qu’il le faut, dans Code ou en le demandant à l’agent. Son aperçu répond à une adresse qui lui est propre, /features/…/, avec ses propres données de test. Les visiteurs de la lambda n’en voient rien, et rien de ce qu’il écrit n’atteint les données de la lambda.
  3. 3
    Une fois au point, cliquez sur Mettre en ligne : il devient la prochaine version, avec ses notes, et passe en ligne. Le brouillon disparaît alors, avec son aperçu et ses données de test.
POST /api/v1/lambdas/{editorKey}/features
{ "name": "Classement" }

PUT  /api/v1/lambdas/{editorKey}/features/{feature}/files?deploy=true
POST /api/v1/lambdas/{editorKey}/features/{feature}/merge
{ "deploy": true }

On peut travailler sur plusieurs brouillons à la fois. Seul un brouillon à jour par rapport à la version la plus récente peut être mis en ligne, afin de ne jamais annuler une version enregistrée après son début. Si un autre a été mis en ligne avant, reportez ses modifications (ou demandez-le à l’agent), puis marquez le brouillon comme à jour. Rien ne passe en ligne tout seul, et c’est voulu. L’API appelle un brouillon une feature, et sa mise en ligne un merge.

Plusieurs fichiers

Les types n’ont pas besoin d’être sous le code qui les utilise. Dans Code, cliquez sur + à côté des fichiers : le nouveau fichier est compilé avec le snippet, dans le même namespace, donc rien à importer pour y accéder. Un nom sans extension est considéré comme du C#.

lambda.cs
var shelf = new Shelf();

return Inline.Create()
             .Get(() => shelf.All())
             .Post((Book book) => shelf.Add(book));
Shelf.cs
public sealed class Shelf
{
    private readonly List<Book> _books = [];

    public IEnumerable<Book> All() => _books;

    public Book Add(Book book)
    {
        _books.Add(book);
        return book;
    }
}

public record Book(string Title, string Author);

Servir une page

Il y a deux façons de servir une page, et une troisième pour ce que les gens importent à côté.

Une page, écrite dans le code

Parfait pour quelque chose de petit. La page fait partie du snippet.

var page = Resource.FromString("""
                              <!doctype html>
                              <title>Mine</title>
                              <h1>It works</h1>
                              """)
                   .Type(new ContentType("text/html; charset=utf-8"));

return Content.From(page);

Un dossier de vrais fichiers

Ce qu’il vous faut dès qu’il y a une feuille de style et un script. Les fichiers s’ajoutent comme un fichier C#, et sont servis exactement tels que vous les avez écrits. Rien ne les compile.

return Layout.Create()
             .Add("api", api)
             .Add(Assets.App("site"));

Des fichiers importés, depuis les données

Pour ce que les gens importent ou ce que la lambda crée (photos, documents), servi à côté de l’app. Pas pour les pages de l’app elle-même : leur place est dans un dossier de fichiers, où elles sont versionnées avec le code qui en a besoin.

return Layout.Create()
             .Add("api", api)
             .Add("uploads", Workspace.Files("uploads"))
             .Add(Assets.App("site"));

Un front-end, étape par étape

La deuxième façon, en entier. Chaque démo sert sa page ainsi, depuis un dossier nommé web : ouvrez demo-crud pour en lire une. Les démos sont en lecture seule ; leur clé d’édition est leur nom.

  1. 1
    Dans Code, cliquez sur + à côté des fichiers et tapez site/index.html. Un nom qui contient un slash place le fichier dans un dossier ; un nom avec une extension donne le type du fichier.
  2. 2
    Ajoutez site/app.css et site/app.js de la même façon. Votre page les appelle par leur nom, comme dans href="app.css", car le dossier est la racine de ce qui est servi, pas une partie de l’adresse.
  3. 3
    Pour tout ce qui n’est pas du texte, comme une image ou une police, ouvrez un fichier dans site et cliquez sur le bouton d’import à côté des fichiers : le fichier arrive dans le même dossier. Un PNG ne se tape pas dans un éditeur de texte, c’est donc par là qu’il faut passer.
  4. 4
    Dans lambda.cs, servez le dossier :
    return Layout.Create().Add(Assets.App("site"));
  5. 5
    Cliquez sur Déployer. site/index.html répond sur /, site/app.css sur /app.css, et toute adresse qui ne correspond à aucun fichier renvoie la page. Un front-end qui gère son propre routage marche donc aussi quand quelqu’un recharge un lien profond.
  6. 6
    Ajoutez une API à côté, pour que la page ait à qui parler :
    var api = Inline.Create().Get("notes", () => notes);
    
    return Layout.Create()
                 .Add("api", api)
                 .Add(Assets.App("site"));

Les deux endroits où vivent les fichiers

Une lambda garde des fichiers à deux endroits, et l’éditeur les montre séparément : Fichiers contient les fichiers d’une version (le programme), et Données contient le workspace (ce que le programme garde). La différence, c’est à qui ils appartiennent. Les fichiers d’une version appartiennent à cette version ; les données appartiennent à la lambda, et toutes les versions les partagent.

Dans une versionDans les données
ce qu’il contientle code et les assets : le programme, front-end compris, ainsi que sa documentation et ses teststout ce que la lambda écrit, ou que quelqu’un importe
quand il changejamais : une modification donne une nouvelle versiondès que quelque chose y est écrit
un déploiementmet exactement ces fichiers en lignen’y touche jamais
revenir en arrièrerestaure les anciens fichiersaucun effet : toutes les versions les partagent
un brouilloncommence par une copie de ces fichierstravaille sur une copie de ces données
quand il disparaîtavec les anciennes versions, au-delà de la limiteavec la lambda, ou quand vous le désactivez
accessible dans le code viaAssetsWorkspace

Impossible d’en faire un seul endroit. Sinon, soit un déploiement effacerait tout ce que votre lambda a écrit depuis, soit rien ne pourrait jamais être retiré de ce qu’elle publie. Un jeu qui tient un classement a besoin du second cas ; la page qu’il sert, du premier. La page va donc dans la version, et le classement dans les données.

Garder des enregistrements

Les enregistrements – entrées, comptes, commandes, votes – ont leur place dans la base de données : une base SQLite propre à la lambda, que vous activez sous Données. Le code ouvre une connexion avec Database.GetConnection() et lit et écrit les données via Entity Framework Core, avec son propre contexte, qui mappe les tables :

// migrations/V1__Create_notes.sql:
//   CREATE TABLE notes (id INTEGER PRIMARY KEY, text TEXT NOT NULL);

using (var connection = Database.GetConnection())
{
    new Evolve(connection) { Locations = [Assets.Root + "migrations"] }.Migrate();
}

return Inline.Create()
             .Get("notes", () =>
             {
                 using var db = new Notes(Database.GetConnection());

                 return db.Entries.OrderBy(n => n.Id).Select(n => n.Text).ToList();
             })
             .Post("notes", (NoteInput input) =>
             {
                 using var db = new Notes(Database.GetConnection());

                 db.Entries.Add(new Note { Text = input.Text });

                 return db.SaveChanges();
             });

record NoteInput(string Text);

class Note
{
    public long Id { get; set; }

    public string Text { get; set; }
}

// maps the table the migration made, on the connection it is handed
class Notes(SqliteConnection connection) : DbContext
{
    public DbSet<Note> Entries => Set<Note>();

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlite(connection, contextOwnsConnection: true);

    protected override void OnModelCreating(ModelBuilder model)
        => model.Entity<Note>().ToTable("notes");
}

Ses tables sont créées par des migrations : des fichiers SQL livrés avec la version dans migrations/, appliqués dans l’ordre par Evolve au démarrage de la lambda – chacun une seule fois, si bien qu’une nouvelle version n’exécute que ce qui est nouveau. Ne modifiez jamais une migration déjà appliquée ; un changement de table, c’est le fichier suivant.

Comme toutes les données, la base est partagée par toutes les versions, laissée intacte par les déploiements et les retours en arrière, et un brouillon travaille sur une copie. Sous Données, vous voyez ses tables et leur contenu – la vue simple les appelle des entrées. Télécharger en projet .NET l’emporte sous forme de simple fichier SQLite.

Créez un contexte là où vous en avez besoin, libérez-le ensuite, et utilisez-le de façon synchrone : ToList et SaveChanges, pas ToListAsync et SaveChangesAsync. Les tables sont créées par les migrations, jamais par Entity Framework. La démo demo-crud fait tout cela.

Garder des fichiers

Workspace est un dossier privé que votre lambda peut lire et écrire : l’endroit pour les fichiers – les images que quelqu’un importe, un document qu’elle produit, un modèle qu’elle charge. Les enregistrements ont leur place dans la base de données, et ce que l’on sait d’un fichier – qui l’a importé, quand – est aussi un enregistrement.

// uploads go into a folder of their own, served as they are
Workspace.CreateFolder("photos");

return Layout.Create()
             .Add("photos", Workspace.Files("photos"))
             .Add("upload", Inline.Create().Post(async (Stream body) =>
             {
                 using var content = new MemoryStream();

                 await body.CopyToAsync(content);

                 Workspace.WriteBytes($"photos/{Guid.NewGuid():N}.jpg", content.ToArray());
             }));

Il y a aussi ReadBytes, WriteBytes, Delete, List, CreateFolder, et Tree/Files/App pour le servir. Rien d’autre du système de fichiers n’est accessible.

Clés et mots de passe

Une clé d’API, un mot de passe ou un jeton a sa place dans les secrets, pas dans le code – où chaque version, chaque téléchargement et chaque lecteur de l’historique l’aurait. Le code lit un secret par son nom :

var weather = new System.Net.Http.HttpClient();

weather.DefaultRequestHeaders.Add("X-Api-Key", Secret.Read("WEATHER_API_KEY"));

return Inline.Create()
             .Get("today", async () => await weather.GetStringAsync("https://weather.example/today"));

Activez les secrets sous Données et définissez-y la valeur. Une fois enregistrée, elle n’est plus jamais affichée – ni à vous, ni à un agent ; vous pouvez seulement la remplacer. La liste indique quels noms le code lit sans qu’une valeur soit définie, et la vue d’ensemble les demande. Secret.Exists indique si un secret est défini, pour du code qui s’en passe. Comme toutes les données, les secrets sont partagés par toutes les versions, et un brouillon travaille sur une copie.

Ils sont stockés chiffrés, avec une clé qui ne se trouve pas dans la base de données. Dans un projet téléchargé, Secret.Read("NAME") lit la variable d’environnement NAME – les valeurs elles-mêmes restent ici.

WebSockets

Pris en charge, et pas à moitié. La démo demo-game forme des paires de joueurs et fait tourner chaque partie sur le serveur. La forme la plus simple tient en trois callbacks :

var room = new ConcurrentDictionary<IReactiveConnection, string>();

var socket = Websocket.Functional()
                      .OnOpen(c => { room[c] = "someone"; return ValueTask.CompletedTask; })
                      .OnMessage(async (c, text) =>
                      {
                          foreach (var other in room.Keys)
                          {
                              await other.WritePayloadAsync(text);
                          }
                      })
                      .OnClose((c, _) => { room.TryRemove(c, out _); return ValueTask.CompletedTask; });

return Layout.Create().Add("chat", socket);

Quand la page se contente d’écouter (un compteur, un fil d’actualité, un tableau des scores), les événements envoyés par le serveur sont plus simples : une seule longue réponse dans laquelle le serveur continue d’écrire, et que le navigateur rouvre tout seul en cas de coupure. La démo demo-live envoie ainsi chaque vote à tous ceux qui regardent. Dans les deux cas, c’est le serveur qui pousse ce qui a changé. Une page qui redemande toutes les quelques secondes envoie une requête à chaque fois, que quelque chose ait changé ou non, et reste malgré tout en retard.

Un piège où tout le monde tombe : un navigateur ne peut pas définir d’en-têtes lors du handshake WebSocket. Passez ce dont le handler a besoin dans la query string, où il le lit via connection.Request.Header.Query, ou envoyez les secrets dans le premier message.

Ce que vous ne pouvez pas faire

Votre code tourne sur un serveur partagé. Une partie de C# est donc refusée avant même la compilation : lancer des processus, ouvrir vos propres sockets, charger des assemblies, accéder au système de fichiers en dehors de votre workspace, et la réflexion utilisée pour contourner tout ça. De même pour l’attente d’une tâche avec .Result ou .Wait() au lieu de await : les requêtes s’exécutent sur un thread par cœur, et la tâche devrait se terminer sur le thread même qui l’attend.

Tout le reste est là, y compris toute l’API des modules GenHTTP. Si quelque chose est refusé, on vous dit quelle ligne et pourquoi, pas simplement que ça a échoué.

Repartir avec votre code

Dans l’éditeur, Télécharger en projet .NET vous donne le tout : une solution que vous pouvez ouvrir, lancer avec dotnet run, et garder. Elle n’a besoin que du package GenHTTP, et fournit un Dockerfile pour la construire et l’exécuter en conteneur.

Votre snippet devient Project.cs, et Program.cs sert ce qu’il renvoie. Vos autres fichiers sont repris exactement tels quels. Workspace et Assets deviennent deux dossiers à côté du programme, avec les mêmes méthodes, à part dans un dossier Platform : rien à changer dans votre code. Secret y lit les variables d’environnement du même nom ; les valeurs restent ici. La documentation et les tests suivent dans docs et tests. Database ouvre database/database.db, que le téléchargement contient avec les enregistrements gardés par votre application.

Bon à savoir avant de construire quoi que ce soit ici : ce que vous écrivez vous appartient, et repart avec vous en entier. Le faire tourner sur cette machine ne vous enferme pas sur cette machine.

Publier le code

Si ce que vous avez construit peut servir à d’autres, publiez son code : ouvrez Open source dans le tableau de bord, choisissez une licence – MIT, sauf si vous en voulez une autre – et activez la publication. Le code obtient sa propre page parmi les apps open source, où n’importe qui peut le lire, lui donner une étoile et télécharger n’importe quelle version sous la forme du même projet que Télécharger en projet .NET, licence comprise.

Chaque version est publiée, les plus anciennes aussi, avec sa documentation, ses tests et la modification qu’elle a apportée. Ce que garde l’application n’est jamais publié – ses enregistrements, les fichiers qu’elle a enregistrés, les valeurs de ses clés et mots de passe –, pas plus que ce que vous avez demandé avec vos propres mots, ni qui utilise l’application. Désactivez la publication et la page disparaît ; ses étoiles sont conservées pour le jour où vous publierez à nouveau le code.

Tout ce qui est dans le code devient public, les versions précédentes comprises. Une clé ou un mot de passe a sa place avec les clés et mots de passe, sous Données, jamais dans le code – publié ou non.

Laisser faire un agent

Un endpoint MCP est disponible sur /mcp. Branchez-y un agent, et il peut faire tout ce que fait l’éditeur : lire le guide, lire une démo en entier, écrire des fichiers, les compiler et déployer. C’est la même API en dessous.

Il explique ses choix au fur et à mesure (write_code prend la spécification et le changement) et il peut regarder ce qu’il a déployé : read_logs renvoie les requêtes récentes de la lambda, ce qu’elle a affiché, et la stack trace de chaque exception levée. C’est comme ça qu’un agent vérifie que son code marche, au lieu de le supposer. Vous voyez la même chose dans le tableau de bord. Il rédige aussi la documentation et les tests, les lit avant de modifier quoi que ce soit, et lance les tests sur l’adresse d’un brouillon avant de le mettre en ligne. Une page destinée à être trouvée reçoit un titre, une description, une icône et un aperçu qui s’affiche quand on partage son lien. Au pied des pages qu’il construit, il ajoute une petite ligne indiquant qu’elles ont été réalisées avec GenHTTP Lambda : dites-lui si vous préférez ne pas l’avoir, et il la retire.

En savoir plus →