TVclRestAgent makes a running Delphi / VCL
application answer questions over HTTP: which controls exist, what they contain,
whether a button is enabled. Drop it on a data module, set
Active := True, and UI testing stops being guesswork.
One unit, 6,800 lines, 309 tests. Nothing outside Indy.
Formerly CAWRestRemoteControl — same component, standalone name.
TVclRestAgent lässt eine laufende Delphi-/VCL-Anwendung
über HTTP Auskunft geben: welche Steuerelemente es gibt, was darin steht, ob eine
Schaltfläche bedienbar ist. Auf ein Datenmodul legen,
Active := True setzen — und UI-Tests hören auf,
Raterei zu sein.
Eine Unit, 6.800 Zeilen, 309 Tests. Nichts außer Indy.
Früher CAWRestRemoteControl — dieselbe Komponente, eigenständiger Name.
Automating a desktop UI from the outside means working from pictures. That is fragile for a human, and it is worse for an AI.
The usual approach is a screenshot and a pair of coordinates. It works until the window moves, until the display scaling changes, until a caption gets one word longer — and then it fails silently, clicking whatever happens to sit at 530 / 342 now.
For an AI agent the gap is wider still. It can be handed a screenshot, but it cannot know whether the button it sees is enabled, whether the value in that field was actually committed, or whether the screen has finished rebuilding. It infers, and inference is exactly what a test must not do.
This project ran into that wall repeatedly: clicks on custom-drawn buttons that silently did nothing, a grid that would not take keyboard focus, scroll attempts that moved nothing. Every one of those cost time and produced a test result nobody could trust.
Eine Desktop-Oberfläche von außen zu automatisieren heißt, mit Bildern zu arbeiten. Das ist für Menschen brüchig — für eine KI erst recht.
Der übliche Weg ist ein Bildschirmfoto und ein Koordinatenpaar. Das funktioniert, bis das Fenster verschoben wird, bis sich die Skalierung ändert, bis eine Beschriftung ein Wort länger wird — und dann scheitert es lautlos und klickt, was jetzt zufällig bei 530 / 342 liegt.
Für eine KI ist die Lücke noch größer. Man kann ihr ein Bild geben, aber sie kann nicht wissen, ob die Schaltfläche darauf bedienbar ist, ob der Wert im Feld wirklich übernommen wurde, ob der Bildschirm fertig aufgebaut ist. Sie schließt daraus — und Schließen ist genau das, was ein Test nicht tun darf.
In diesem Projekt lief das wiederholt gegen die Wand: Klicks auf selbst gezeichnete Schaltflächen, die stillschweigend nichts taten; ein Grid, das den Tastaturfokus nicht annahm; Scrollversuche, die nichts bewegten. Jedes Mal kostete es Zeit und lieferte ein Ergebnis, dem niemand trauen konnte.
The application knows everything about itself. It just had no way to say so.
Die Anwendung weiß alles über sich selbst. Sie hatte nur keine Möglichkeit, es zu sagen.
A VCL application holds its own component tree, with names, classes, properties and state. The component opens that up over HTTP and hands it out as JSON — and takes commands the same way. Nothing is inferred from pixels; every answer comes from the object itself.
Eine VCL-Anwendung trägt ihren Komponentenbaum in sich, mit Namen, Klassen, Eigenschaften und Zustand. Die Komponente öffnet ihn über HTTP und gibt ihn als JSON heraus — und nimmt auf demselben Weg Befehle entgegen. Nichts wird aus Pixeln geschlossen; jede Antwort kommt vom Objekt selbst.
This is what it was actually made for. Remote-controlling a UI over REST is the mechanism; giving a machine a dependable grip on that UI is the point.
Dafür ist es entstanden. Eine Oberfläche per REST fernzusteuern ist der Mechanismus — einer Maschine verlässlichen Zugriff darauf zu geben, ist der Zweck.
The usual fallback is sleep(3) and hope. Instead there is
/wait: wait until a form appears, until a control becomes enabled,
until it is gone, or until the main thread goes quiet. It answers 200 when the
condition held and 408 when it never did — and reports how long it actually
took.
Der übliche Notbehelf ist sleep(3) und Hoffen. Stattdessen
gibt es /wait: warten, bis ein Formular erscheint, bis ein Element
bedienbar ist, bis es verschwunden ist, oder bis der Hauptthread zur Ruhe kommt.
Antwort 200, wenn die Bedingung eintrat, 408, wenn nicht — samt der
tatsächlich vergangenen Zeit.
Triggering a disabled action returns 409, not a cheerful „done“. A test that asks for something impossible learns that it was impossible — the single most valuable property when nobody is watching the screen.
Eine gesperrte Aktion auslösen ergibt 409, kein fröhliches „erledigt“. Ein Test, der Unmögliches verlangt, erfährt, dass es unmöglich war — die wertvollste Eigenschaft überhaupt, wenn niemand auf den Bildschirm sieht.
An action that opens a modal dialog would never return. With
async=1 it is queued and answered immediately with
202 — checked first, so a disabled action is still refused.
Message boxes can be answered in advance and are logged with their text, which is
often worth more than the click.
Eine Aktion, die einen modalen Dialog öffnet, käme nie
zurück. Mit async=1 wird sie vorgemerkt und sofort mit
202 beantwortet — vorher geprüft, eine gesperrte
Aktion wird weiterhin abgelehnt. Meldungsdialoge lassen sich im Voraus beantworten
und werden mit ihrem Text protokolliert, was oft mehr wert ist als der Klick.
Setting a property answers with the value read back from the object, not with a confirmation. Some properties clamp or reformat what you give them, and that is precisely what the caller needs to know.
Eine Eigenschaft zu setzen liefert den vom Objekt zurückgelesenen Wert, keine Bestätigung. Manche Eigenschaften begrenzen oder formatieren, was man ihnen gibt — und genau das muss der Aufrufer erfahren.
/objects lists every component the application holds —
including the ones that never appear on screen: datasets, timers, action
lists, whole data modules. Each with the path that addresses it
unambiguously, even when two modules use the same name.
class=TDataSet&derived=1 narrows it to every dataset,
whatever the application happens to use. Catalogue first, then drive
— without ever having seen the source.
/objects listet jede Komponente der Anwendung — auch
die, die nie auf dem Bildschirm auftauchen: Datenmengen, Timer,
Aktionslisten, ganze Datenmodule. Jede mit dem Pfad, der sie eindeutig
anspricht, auch wenn zwei Module denselben Namen benutzen.
class=TDataSet&derived=1 engt auf alle Datenmengen ein,
gleich welche die Anwendung verwendet. Erst katalogisieren, dann steuern
— ohne den Quelltext je gesehen zu haben.
A dataset row comes back typed: numbers as numbers, booleans as
true, dates as 2026-12-24, an empty field as
null. Exactly what goes back in when you write it again — no
conversion by hand, and no dependence on the Windows locale of whichever
machine the test happens to run on.
Eine Datensatzzeile kommt typisiert zurück: Zahlen als Zahl,
Wahrheitswerte als true, Datumsangaben als
2026-12-24, ein leeres Feld als null. Genau das, was
beim Zurückschreiben wieder hineingeht — ohne Umrechnen von Hand und
ohne Abhängigkeit von den Ländereinstellungen der Maschine, auf der
der Test gerade läuft.
Controls are addressed by name or by path (frmMain.pnlLeft.btnSave).
Clicking goes through the control’s own Click method, so it
works while the window is covered or in the background — and hits the
intended element every time.
Steuerelemente werden über Namen oder Pfad angesprochen
(frmMain.pnlLeft.btnSave). Der Klick geht über die
Click-Methode des Elements und wirkt auch bei verdecktem oder im
Hintergrund liegendem Fenster — und trifft immer das gemeinte.
/key?control=edtTotal&text=12,50 sends the keystrokes
straight to that element — in the background, covered, whatever. And the
answer only goes out once the control has processed them, so reading the value
back is not a race.
Real system input stays available for Ctrl+S and the like. But
it goes wherever Windows thinks the focus is, so if the application is not in
front, that call refuses rather than typing into a stranger’s
window.
/key?control=edtTotal&text=12,50 schickt die Eingabe direkt
an das Element — im Hintergrund, verdeckt, egal. Und die Antwort geht erst
hinaus, wenn das Steuerelement sie verarbeitet hat: Zurücklesen ist damit
kein Wettlauf.
Echte Systemeingabe bleibt für Strg+S und Verwandtes. Die
geht aber dorthin, wo Windows den Fokus sieht — liegt die Anwendung nicht
vorn, sagt dieser Aufruf ab, statt in ein fremdes Fenster zu
schreiben.
For the genuinely visual checks — colours, layout, a rendered label
— /screenshot captures a window through
PrintWindow, so the image is right even when the window is covered.
The picture complements the facts; it no longer replaces them.
Für wirklich Visuelles — Farben, Layout, ein gerendertes Etikett
— nimmt /screenshot ein Fenster über
PrintWindow auf; das Bild stimmt auch bei Verdeckung. Es
ergänzt die Fakten, statt sie zu ersetzen.
An agent was the reason it was built. It is not the condition for using it.
Everything behind the interface is plain HTTP and JSON: a shell script, a
scheduled task, a test runner in any language, a colleague with
curl — all of them can ask the same questions and trigger
the same actions. Nothing about it assumes an AI on the other end.
What that opens up is automation for applications that were never built with it in mind:
The permission areas matter most in exactly these cases. A smoke test needs nothing but information, so everything else stays closed; a scripted run that has to type needs Control and nothing more. And in every case it belongs in a sealed-off environment: the default binding is the loopback address, and it should stay there.
Eine KI war der Anlass. Sie ist keine Voraussetzung.
Hinter der Schnittstelle steckt nichts als HTTP und JSON: ein Shellskript,
eine geplante Aufgabe, ein Testrunner in irgendeiner Sprache, ein Kollege mit
curl — alle können dieselben Fragen stellen und
dieselben Aktionen auslösen. Nichts daran setzt eine KI am anderen Ende
voraus.
Das eröffnet Automatisierung für Anwendungen, die nie dafür gebaut wurden:
Gerade hier zahlen sich die Rechtebereiche aus. Ein Rauchtest braucht nur Auskunft, also bleibt alles andere zu; ein Skriptlauf, der tippen muss, braucht Bedienung und sonst nichts. Und in jedem Fall gehört es in eine abgeschottete Umgebung: Voreingestellt ist die Loopback-Adresse, und dabei sollte es bleiben.
Indy serves each request on its own thread, but the VCL is not thread-safe. Every access to forms, controls, actions or datasets is therefore marshalled onto the main thread; exceptions raised there are caught and re-raised on the server thread, so they reach the caller as an HTTP 500 with a message instead of disappearing.
A side effect that is deliberate: if the application is busy, the request waits. The answer then reflects the real state rather than a convenient fiction.
Indy bedient jede Anfrage in einem eigenen Thread, die VCL ist aber nicht threadsicher. Jeder Zugriff auf Formulare, Steuerelemente, Aktionen oder Datenmengen läuft deshalb über den Hauptthread; Ausnahmen von dort werden eingefangen und im Serverthread erneut ausgelöst, damit sie als HTTP 500 mit Meldung ankommen, statt zu verschwinden.
Ein gewollter Nebeneffekt: Ist die Anwendung beschäftigt, wartet die Anfrage. Die Antwort spiegelt dann den tatsächlichen Zustand statt einer bequemen Fiktion.
/nopnothing but 200 — does the path and the login worknichts außer 200 — stimmen Pfad und Anmeldung/pingapplication name, main form, form countAnwendungsname, Hauptformular, Anzahl Formulare/timelocal and UTC, offset, time zone, tick countOrtszeit und UTC, Versatz, Zeitzone, Tickzähler/machineCPU, memory, OS, monitors, network adaptersProzessor, Speicher, System, Bildschirme, Netzwerkkarten/openapi.jsonwhat this installation can do, as OpenAPIwas diese Installation kann, als OpenAPI/componentsthe control tree, optionally with propertiesSteuerelementbaum, optional mit Eigenschaften/componentone component, all published propertieseine Komponente, alle published Properties/objectsevery component there is — datasets, timers, data modulesalle Komponenten — Datenmengen, Timer, Datenmodule/actionsevery action with caption and statealle Aktionen mit Beschriftung und Zustand/screenshota window, a control, or the whole screenein Fenster, ein Element oder der Bildschirm/waitwait for a form, a control state, or idleauf Formular, Zustand oder Ruhe warten/propertyread a value — or POST to set itWert lesen — oder per POST setzen/focuswhere the focus is, and where it actually landedwo der Fokus steht — und wo er wirklich landete/actionexecute one, optionally without waitingeine ausführen, auf Wunsch ohne Warten/clicktrigger a control’s ClickClick eines Steuerelements auslösen/keytype text or send a key with modifiersText tippen oder Taste mit Zusatztasten/mouseposition, or move and clickPosition, oder bewegen und klicken/dialogsanswer message boxes in advance, read the logMeldungen vorab beantworten, Protokoll lesen/windowstate and bounds — and whether the app is in frontZustand und Lage — und ob die Anwendung vorn liegt/datasetfields and rows — or just the current recordFelder und Zeilen — oder nur der aktuelle Satz/dataset POSTnavigate, locate, insert, edit, delete, refreshnavigieren, suchen, anlegen, ändern, löschen/fileslist a directoryVerzeichnis auflisten/fileread, write, append, delete — UTF-8, ANSI or Base64lesen, schreiben, anhängen, löschen/directorycreate and remove directoriesVerzeichnisse anlegen und entfernen/renamerename a file or a directoryDatei oder Verzeichnis umbenennen/exceptionswhat went wrong, as a ring bufferwas schiefging, als Ringpuffer/terminateshut the application downAnwendung beenden/restartshut it down and start it againbeenden und neu startenThe built-in endpoints know nothing about any particular program. Everything that needs application knowledge goes through one event.
Die eingebauten Endpunkte wissen nichts über ein bestimmtes Programm. Alles, wofür Anwendungswissen nötig ist, läuft über ein Ereignis.
Any path under /custom/ is handed to OnCustomRequest with
method, path, parameters and body. The handler runs on the main thread already,
so it may touch datasets and the UI directly, and answers with JSON and an HTTP status.
Leave it unhandled and the component replies 404 by itself.
In the application this was built for, that event exposes a query through the existing database layer and the current connection state. Other obvious uses: seed a fixture before a test, reset to a known state afterwards, or read an internal counter that no control displays.
Jeder Pfad unter /custom/ geht an OnCustomRequest, mit
Verfahren, Pfad, Parametern und Rumpf. Der Handler läuft bereits im
Hauptthread, darf also direkt auf Datenmengen und Oberfläche zugreifen, und
antwortet mit JSON und HTTP-Status. Bleibt er unbehandelt, antwortet die Komponente
selbst mit 404.
In der Anwendung, für die das entstand, stellt dieses Ereignis eine Abfrage über die vorhandene Datenbankschicht bereit sowie den Verbindungszustand. Weitere naheliegende Fälle: vor einem Test einen Datenbestand herstellen, danach auf einen bekannten Stand zurücksetzen, oder einen internen Zähler auslesen, den kein Steuerelement anzeigt.
// Anything under /custom/ lands here. APath is already stripped
// of the prefix: "/custom/sql" arrives as "sql".
procedure TdmMain.RestCustomRequest(Sender: TObject;
const AMethod, APath: string; AParams: TStrings;
const ABody: string; var AResponse: string;
var AStatusCode: Integer; var AHandled: Boolean);
begin
if SameText(APath, 'sql') then
begin
AResponse := QueryToJson(AParams.Values['sql']);
AHandled := True;
end;
end;
// Alles unter /custom/ landet hier. APath ist bereits um das
// Praefix gekuerzt: "/custom/sql" kommt als "sql" an.
procedure TdmMain.RestCustomRequest(Sender: TObject;
const AMethod, APath: string; AParams: TStrings;
const ABody: string; var AResponse: string;
var AStatusCode: Integer; var AHandled: Boolean);
begin
if SameText(APath, 'sql') then
begin
AResponse := QueryToJson(AParams.Values['sql']);
AHandled := True;
end;
end;
There is also Execute, which runs a request straight through the same
dispatcher without any network involved — for cases where the transport is
somewhere else entirely, or for testing the endpoints themselves.
Daneben gibt es Execute: Es schickt eine Anfrage direkt durch denselben
Verteiler, ganz ohne Netz — für Fälle, in denen der Transport woanders
liegt, oder um die Endpunkte selbst zu prüfen.
Not a rewrite, not a framework — a tool that was missing, so it got built between two other tasks.
The commit history is blunt about it: first version at 01:19, the last of the
round at 11:34 the same morning. In between: the component itself, a rename after the
compiler pointed out that Dispatch already means something on
TObject, the endpoints for waiting and writing, the dialog handling
— and 59 tests.
That pace is only possible because the loop was closed. Build, run the tests,
read the failure, fix, run again — without a human copying anything between
windows. Several things were only found because the tests ran every single time: a
/ping that crashed in an application without a main form, because
IfThen evaluates both branches; a test of mine that asserted a guarantee
the component never made.
Every endpoint was then exercised against the live application before it counted as done — including the one that took 46 ms to answer instead of blocking on a modal dialog, and the dialog watcher that closed and logged a real dialog in 62 ms.
What is in the repository now is the second round. A review of the whole unit turned up what a day of building had left behind: a screenshot parameter that wrote wherever it was pointed, methods that were named but never enforced, dataset values that came out as text and had to go back in as numbers. Four parallel tables became one, from which the dispatcher, the permission gate and the OpenAPI description are all fed — and the tests grew from 59 to 270, because most of those defects could have been caught by one.
Keine Neuentwicklung, kein Framework — ein Werkzeug, das fehlte, also wurde es zwischen zwei anderen Aufgaben gebaut.
Die Commit-Historie ist da unmissverständlich: erste Fassung um 01:19, der
letzte Commit der Runde um 11:34 desselben Vormittags. Dazwischen: die Komponente
selbst, eine Umbenennung, nachdem der Compiler darauf hinwies, dass
Dispatch bei TObject bereits etwas bedeutet, die Endpunkte
zum Warten und Schreiben, die Dialogbehandlung — und 59 Tests.
Dieses Tempo geht nur, weil der Kreis geschlossen ist. Bauen, Tests laufen lassen,
Fehlschlag lesen, reparieren, erneut laufen lassen — ohne dass jemand etwas
zwischen Fenstern kopiert. Mehreres kam nur heraus, weil die Tests wirklich jedes Mal
liefen: ein /ping, das in einer Anwendung ohne Hauptformular abstürzte,
weil IfThen beide Zweige auswertet; ein Test von mir, der eine Zusage
prüfte, die die Komponente nie gemacht hat.
Danach wurde jeder Endpunkt an der laufenden Anwendung ausprobiert, bevor er als fertig galt — auch der, der nach 46 ms antwortet, statt an einem modalen Dialog hängen zu bleiben, und die Dialogüberwachung, die einen echten Dialog in 62 ms geschlossen und protokolliert hat.
Im Repository steht inzwischen die zweite Runde. Ein Review der ganzen Unit förderte zutage, was ein Tag Bauen liegen gelassen hatte: ein Screenshot-Parameter, der schrieb, wohin man zeigte; Methoden, die genannt, aber nie durchgesetzt wurden; Feldwerte, die als Text herauskamen und als Zahl wieder hineinmussten. Aus vier parallelen Tabellen wurde eine, aus der sich Verteiler, Rechte-Riegel und OpenAPI-Beschreibung gleichermaßen speisen — und aus 59 Tests wurden 270, weil die meisten dieser Fehler sich mit einem hätten finden lassen.
The assistant sat inside the IDE for all of this, and that shaped the result — so here is where it actually helped.
What changes when the AI sits inside RAD Studio is not that it writes code faster. It is that the loop closes: write, compile, read the real compiler message, fix, compile again — without a human carrying text between two windows. A twenty-second round trip instead of a context switch, and it stays that way at the two-hundredth repetition.
Three from this project, each found in one of those round trips: a
} inside a { } comment, which ends it early and makes
the error surface three lines further down looking like something else
entirely; a type name left behind after renaming across 6,500 lines, which the
compiler pointed at immediately; a unit removed from the uses as
unused that turned out to hold four constants. Each of them is a minute with
the loop closed, and a much worse afternoon without.
The same applies to the 309 tests. They run after every change because running them costs nothing — and that is the only reason a review of this size was worth doing at all. Half the defects it turned up would have been invisible to reading alone.
Kai gives an AI a grip on the source: the editor, the compiler, the project. What it cannot give it is a grip on the running program — whether the button it just wired up is actually enabled, whether the value in that field was committed, whether the screen has finished rebuilding.
That is the gap this component fills. Kai writes the code; TVclRestAgent lets the same assistant check that the running application does what the code says. Either half moves development along considerably on its own — together they are a different level altogether.
Die KI saß bei alldem in der IDE, und das hat das Ergebnis geprägt — also: wo sie tatsächlich geholfen hat.
Was sich ändert, wenn die KI in RAD Studio sitzt, ist nicht, dass sie schneller Code schreibt. Es ist, dass sich der Kreis schließt: schreiben, übersetzen, die echte Compilermeldung lesen, reparieren, wieder übersetzen — ohne dass jemand Text zwischen zwei Fenstern hin und her trägt. Zwanzig Sekunden statt eines Kontextwechsels, und das bleibt auch beim zweihundertsten Mal so.
Drei Beispiele aus diesem Projekt, jedes in genau so einem Durchlauf
gefunden: eine } in einem { }-Kommentar, die ihn
vorzeitig beendet und den Fehler drei Zeilen weiter auftauchen lässt, wo er
nach etwas ganz anderem aussieht; ein Typname, der nach dem Umbenennen quer
durch 6.500 Zeilen liegen geblieben war und auf den der Compiler sofort zeigte;
eine Unit, die als ungenutzt aus der uses geflogen war und dann
doch vier Konstanten hielt. Jedes davon ist mit geschlossenem Kreis eine Minute
— und ohne ihn ein deutlich schlechterer Nachmittag.
Für die 309 Tests gilt dasselbe. Sie laufen nach jeder Änderung, weil sie laufen zu lassen nichts kostet — und nur deshalb hat sich ein Review dieser Größe überhaupt gelohnt. Die Hälfte der Befunde wäre beim bloßen Lesen unsichtbar geblieben.
Kai gibt einer KI Zugriff auf den Quelltext: Editor, Compiler, Projekt. Was es ihr nicht geben kann, ist Zugriff auf das laufende Programm — ob die Schaltfläche, die sie gerade verdrahtet hat, wirklich bedienbar ist, ob der Wert im Feld übernommen wurde, ob der Bildschirm fertig aufgebaut ist.
Genau diese Lücke füllt diese Komponente. Kai schreibt den Code; TVclRestAgent lässt dieselbe KI nachsehen, ob die laufende Anwendung das tut, was im Code steht. Jede der beiden Hälften bringt die Entwicklung für sich schon erheblich voran — zusammen sind sie noch einmal eine andere Liga.
The endpoints are grouped into areas, and each area has one of three levels. Nothing has to be secured call by call.
Die Endpunkte sind in Bereiche gruppiert, jeder Bereich hat eine von drei Stufen. Kein einzelner Aufruf muss abgesichert werden.
The area follows the subject, the level follows the effect. That is why
/property sits under Control and not under Inspect: writing changes
the interface, while reading stays possible at acReadOnly. A new
endpoint is one row in one table, and the area is a field of it — the
compiler asks for it, so an endpoint cannot quietly slip past the permissions.
ReadOnly is a cap on top of all of it: it pulls every area down to
information only, without overwriting the setting. Switch it off and what was
configured applies again. A denied area stays denied — the cap never opens
anything.
Der Bereich richtet sich nach dem Gegenstand, die Stufe nach der Wirkung.
Deshalb liegt /property bei Bedienung und nicht bei Auskunft:
Schreiben verändert die Oberfläche, Lesen bleibt bei
acReadOnly trotzdem möglich. Ein neuer Endpunkt ist eine Zeile in
einer Tabelle, und der Bereich ist ein Feld davon — der Compiler fragt danach,
an den Rechten vorbei kommt also keiner.
ReadOnly liegt als Deckel darüber: Es zieht jeden Bereich auf
Auskunft herunter, ohne die Einstellung zu überschreiben. Nach dem Abschalten
gilt wieder, was eingestellt war. Ein gesperrter Bereich bleibt gesperrt —
der Deckel öffnet nie etwas.
| PropertyEigenschaft | EndpointsEndpunkte | DefaultVorgabe | |
|---|---|---|---|
InspectAccess |
/components /component /actions /screenshot /wait /machine | acFull | Looking, without touching anythingAnsehen, ohne etwas anzufassen |
ControlAccess |
/property /focus /action /click /key /mouse /dialogs | acFull | At acReadOnly: read properties, trigger nothingBei acReadOnly: Eigenschaften lesen, nichts auslösen |
DatasetAccess |
/dataset | acFull | At acReadOnly: read and stay putBei acReadOnly: lesen, ohne die Anzeige zu verschieben |
DiagnosticAccess |
/exceptions | acFull | What went wrong, and clearing the logWas schiefging, und das Protokoll leeren |
CustomAccess |
/custom/… | acFull | Your own endpoints. What happens behind the event cannot be judged from here — the handler asks ReadOnly itself.Die eigenen Endpunkte. Was hinter dem Ereignis geschieht, ist von hier aus nicht zu beurteilen — die Behandlung fragt ReadOnly selbst ab. |
FileAccess |
/files /file /directory /rename | acDenied | Denied by default, and the root directory is empty too. One of the two is the one you forget.In der Vorgabe gesperrt, und das Wurzelverzeichnis ist ebenfalls leer. Eine der beiden Sperren vergisst man. |
LifecycleAccess |
/terminate /restart | acDenied | Shutting the application down from outside has to be something you asked for.Die Anwendung von außen zu beenden muss man ausdrücklich gewollt haben. |
/nop, /ping, /time and
/openapi.json answer even when everything else is denied —
otherwise you could not tell a sealed-off installation from a dead one. They
still sit behind authentication: with a token or basic credentials configured,
/nop needs them too.
/machine is deliberately not among them. Network
adapters and addresses are reconnaissance material, not a pleasantry.
/nop, /ping, /time und
/openapi.json antworten auch dann, wenn alles andere gesperrt ist
— sonst ließe sich eine zugeschnürte Installation nicht von
einer toten unterscheiden. Hinter der Anmeldung liegen sie trotzdem: Wer Token
oder Basic eingestellt hat, kommt auch an /nop nur mit
Zugangsdaten.
/machine gehört bewusst nicht dazu. Netzwerkkarten
und Adressen sind Aufklärungsmaterial, keine Nettigkeit.
Every endpoint declares the methods it answers to; anything else is a
405 with an Allow header naming what is left —
with ReadOnly already folded into that list.
This is not pedantry about HTTP. Without it, one
<img src="http://127.0.0.1:8377/terminate"> on any web page
would shut the application down, and a browser’s preflight would trigger an
action. The loopback address is no protection there — the browser runs on
the same machine. For the same reason no
Access-Control-Allow-Origin header is sent unless you name an
origin yourself.
Jeder Endpunkt nennt die Methoden, die er beantwortet; alles andere ist ein
405 mit Allow-Kopfzeile, die sagt, was bleibt —
ReadOnly in dieser Liste schon eingerechnet.
Das ist keine HTTP-Pedanterie. Ohne sie würde ein einziges
<img src="http://127.0.0.1:8377/terminate"> auf einer
beliebigen Webseite die Anwendung herunterfahren, und ein Preflight des Browsers
löste eine Aktion aus. Die Loopback-Adresse schützt davor nicht —
der Browser läuft auf derselben Maschine. Aus demselben Grund geht keine
Access-Control-Allow-Origin-Kopfzeile hinaus, solange man nicht
selbst eine Herkunft benennt.
One request and a caller knows what this installation can do — including what is switched off.
Ein Aufruf, und ein Client weiß, was diese Installation kann — auch, was abgeschaltet ist.
GET /openapi.json returns an OpenAPI 3.0.3 document
that is generated, not shipped. It names the actual address, the
configured base path, the authentication scheme in force, and — per
endpoint — whether that area is open here at all.
What is denied is listed and marked deprecated rather
than omitted: a caller should be able to tell „does not exist here“
from „is closed here“. Two extra fields carry the detail:
x-vcl-area and x-vcl-access, the latter with
ReadOnly already folded in.
An agent asks once and knows what it may attempt, instead of walking into a wall of 403s. A file shipped alongside could never say this — it would describe what the component could do, not what this machine allows.
GET /openapi.json liefert ein OpenAPI-3.0.3-Dokument, das
erzeugt und nicht mitgeliefert wird. Es nennt die tatsächliche
Adresse, den eingestellten Pfad-Präfix, das verlangte
Anmeldeverfahren und je Endpunkt, ob dieser Bereich hier überhaupt
offen ist.
Gesperrtes wird genannt und als deprecated markiert,
statt es wegzulassen: Ein Aufrufer soll „gibt es hier nicht“ von
„ist hier zu“ unterscheiden können. Zwei eigene Felder tragen
die Einzelheit: x-vcl-area und x-vcl-access,
letzteres mit bereits eingerechnetem ReadOnly.
Eine KI fragt einmal und weiß, was sie versuchen darf, statt gegen 403er zu laufen. Eine mitgelieferte Datei könnte das nie sagen — sie beschriebe, was die Komponente könnte, nicht was diese Maschine erlaubt.
Paths, methods, parameters, security and the response envelope are
covered. The fields below data differ per endpoint and are
not described there — that is what the readme is for.
Pfade, Methoden, Parameter, Sicherheit und die Antwortform sind
abgedeckt. Die Felder unterhalb von data sind je Endpunkt
verschieden und stehen dort nicht — dafür gibt es die Readme.
Markdown, about 1000 lines: setup, every endpoint with
its parameters, the permission areas, and the traps that cost time
— dialogs that block, datasets left in
dsEdit.
Markdown auf Englisch, rund 1000 Zeilen: Einrichtung, jeder Endpunkt
mit seinen Parametern, die Rechtebereiche und die Fallen, die Zeit
gekostet haben — blockierende Dialoge,
Datenmengen, die in dsEdit stehen bleiben.
This is a testing and diagnostics tool. It can drive the application and read its
data, and it is not built to withstand an attacker. It binds to
127.0.0.1 by default, a bearer token or HTTP basic credentials can be
required, and in
the application it was written for it stays switched off unless it is enabled
deliberately.
Binding it to anything other than the loopback address belongs in a sealed-off test environment — nowhere else. On a production machine it should not be enabled at all.
Das ist ein Werkzeug zum Testen und zur Fehlersuche. Es kann die Anwendung
fernsteuern und ihre Daten auslesen, und es ist nicht darauf ausgelegt, einem
Angreifer standzuhalten. Es bindet per Vorgabe an 127.0.0.1, ein
Bearer-Token oder HTTP-Basic lässt sich verlangen, und in der Anwendung, für die
es entstand, bleibt es aus, solange es nicht ausdrücklich
eingeschaltet wird.
Eine Bindung an etwas anderes als die Loopback-Adresse gehört in eine abgeschottete Testumgebung — sonst nirgendwohin. Auf einem Produktivrechner sollte es gar nicht erst aktiviert sein.
It reads the VCL component tree. Application.MessageBox is a plain
Win32 window without a VCL form and therefore out of reach; so is anything drawn
by a control that keeps its state to itself.
Gelesen wird der VCL-Komponentenbaum. Application.MessageBox ist
ein reines Win32-Fenster ohne VCL-Formular und damit nicht erreichbar —
ebenso wenig, was ein Steuerelement nur für sich selbst zeichnet.
A property set through the interface does not raise the same events as a real
keystroke. Where validation hangs off OnChange, use
/key instead — the component documents the difference rather
than hiding it.
Eine gesetzte Eigenschaft löst nicht dieselben Ereignisse aus wie eine
echte Eingabe. Wo die Prüfung an OnChange hängt, führt
/key zum Ziel — die Komponente dokumentiert den Unterschied,
statt ihn zu verbergen.
If the main thread is busy, so is the interface. That is deliberate — but it means the component cannot be used to inspect an application that has hung.
Ist der Hauptthread beschäftigt, wartet auch die Schnittstelle. Das ist gewollt — heißt aber, dass sich eine hängende Anwendung damit nicht untersuchen lässt.