Eerste commit
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# De SocketServer installeren als Windows-service
|
||||
|
||||
`MijnBesluit.SocketServer.exe` is twee dingen tegelijk, net als je in WinDev gewend bent:
|
||||
|
||||
* start **Windows** hem, dan draait de server;
|
||||
* start **jij** hem, dan verschijnt een beheerschermpje.
|
||||
|
||||
Eén exe om uit te leveren dus, en geen los installatieprogramma.
|
||||
|
||||
## Bouwen
|
||||
|
||||
```powershell
|
||||
D:\MijnBesluit\tools\SocketServer-publiceren.ps1
|
||||
```
|
||||
|
||||
Dat levert `D:\publish\mijnbesluit-socketserver.zip`. Of met de hand:
|
||||
|
||||
```
|
||||
dotnet publish src\MijnBesluit.SocketServer -c Release -r win-x64 --self-contained false -o D:\MijnBesluit\publish
|
||||
```
|
||||
|
||||
Het project staat vast op `x64`; er komt dus geen 32-bits versie uit, ook niet op een machine
|
||||
waar dat de standaard zou zijn. Wil je dat er op de doelmachine helemaal geen .NET geïnstalleerd
|
||||
hoeft te zijn, gebruik dan `--self-contained true`; de map wordt dan een stuk groter maar draait
|
||||
op zichzelf.
|
||||
|
||||
## Op mijnbesluit.nl, naast de website
|
||||
|
||||
De SocketServer draait náást IIS op dezelfde machine en praat met dezelfde database. Drie dingen
|
||||
moeten daarbij kloppen, anders krijgt de app straks een lege of onleesbare kluis te zien.
|
||||
|
||||
**1. Dezelfde geheimen.** `appsettings.json` wijst met `ExtraConfigFile` naar
|
||||
`C:\MijnBesluitData\appsettings.secrets.json` — precies het bestand dat de website al gebruikt.
|
||||
Eén plek dus voor de databasegegevens en de SMTP-instellingen, en ze overleven een nieuwe
|
||||
publicatie. Ontbreekt dat bestand, dan stopt de service meteen met een duidelijke melding in
|
||||
plaats van een onbegrijpelijke "Access denied" van de database.
|
||||
|
||||
**2. Hetzelfde sleutelbestand.** `Security:MasterKeyFile` staat op
|
||||
`C:\MijnBesluitData\keys\masterkey.json`, hetzelfde bestand als de website gebruikt. Met een
|
||||
ánder sleutelbestand is geen enkel kluisitem te openen dat via de website is opgeslagen. Ook
|
||||
`Storage:Path` moet dezelfde map zijn.
|
||||
|
||||
Het serviceaccount **LocalSystem** moet dat sleutelbestand kunnen lezen.
|
||||
|
||||
**3. Een certificaat.** Zonder certificaat luistert de server onversleuteld, en daar gaan
|
||||
wachtwoorden en pincodes overheen. Het certificaat van mijnbesluit.nl uit IIS is te hergebruiken:
|
||||
|
||||
De snelste weg is IIS vragen welk certificaat hij voor de site gebruikt:
|
||||
|
||||
```powershell
|
||||
Import-Module WebAdministration
|
||||
Get-ChildItem IIS:\SslBindings | Select-Object IPAddress, Port, Host, Thumbprint, Store
|
||||
```
|
||||
|
||||
Staat er geen regel met `mijnbesluit.nl`, dan gebruikt die site de regel met IP-adres
|
||||
`0.0.0.0` — dat is het certificaat voor alle sites zonder eigen regel.
|
||||
|
||||
Om te zien welke namen zo'n certificaat dekt:
|
||||
|
||||
```powershell
|
||||
Get-ChildItem Cert:\LocalMachine\WebHosting |
|
||||
Select-Object Subject, @{n='Namen';e={$_.DnsNameList -join ', '}}, Thumbprint, NotAfter, HasPrivateKey
|
||||
```
|
||||
|
||||
Zet de vingerafdruk in `Socket:CertificateThumbprint`. De server kijkt in de opslagen **My** én
|
||||
**WebHosting**; die laatste is waar hostingpanelen en Let's Encrypt-tools hun certificaten
|
||||
neerzetten. Spaties maken niet uit. Let op de
|
||||
vervaldatum: wordt het certificaat vernieuwd, dan verandert de vingerafdruk en moet deze
|
||||
instelling mee — een herstart van de service is dan nodig.
|
||||
|
||||
**Poort 5200 open.** In de firewall van de server:
|
||||
|
||||
```powershell
|
||||
New-NetFirewallRule -DisplayName "MijnBesluit SocketServer" -Direction Inbound `
|
||||
-Protocol TCP -LocalPort 5200 -Action Allow
|
||||
```
|
||||
|
||||
En bij je hoster, als daar nog een firewall voor staat. Controleer het daarna vanaf je eigen pc
|
||||
met `Test-NetConnection mijnbesluit.nl -Port 5200`.
|
||||
|
||||
## Installeren
|
||||
|
||||
1. Klik met de **rechtermuisknop** op `MijnBesluit.SocketServer.exe` en kies
|
||||
**Als administrator uitvoeren**.
|
||||
2. Het schermpje verschijnt met de status *Niet geïnstalleerd*.
|
||||
3. Vul het poortnummer in (standaard 5200). Onder het veld zie je meteen of die poort vrij is.
|
||||
4. Klik op **Install**.
|
||||
|
||||
Wat er dan gebeurt:
|
||||
|
||||
* het poortnummer wordt in `appsettings.json` naast de exe gezet — dat bestand leest de service
|
||||
straks zelf;
|
||||
* de service wordt aangemaakt onder de naam `MijnBesluitSocket`, met weergavenaam
|
||||
*MijnBesluit SocketServer*, opstarttype **Automatisch** en account **LocalSystem**;
|
||||
* Windows krijgt de opdracht de service zelf opnieuw te starten als hij onverwacht stopt: na
|
||||
10 seconden, dan na 30, en daarna elke 2 minuten;
|
||||
* de service wordt meteen gestart.
|
||||
|
||||
## Verwijderen
|
||||
|
||||
Rechtermuisknop → **Als administrator uitvoeren**. Het schermpje laat nu *Draait* of
|
||||
*Geïnstalleerd, gestopt* zien, en de knop heet **UnInstall**. Er wordt eerst gevraagd of je het
|
||||
zeker weet; daarna wordt de service gestopt en verwijderd.
|
||||
|
||||
Zolang de service bestaat kun je de poort niet wijzigen — dat staat er ook bij. Wil je een andere
|
||||
poort: verwijderen, poort aanpassen, opnieuw installeren.
|
||||
|
||||
## Zonder beheerdersrechten
|
||||
|
||||
Start je de exe gewoon met een dubbelklik, dan verschijnt hetzelfde schermpje, maar zijn de
|
||||
knoppen uitgeschakeld en staat er een knop **Opnieuw starten als administrator**. Die start
|
||||
dezelfde exe opnieuw met de UAC-vraag. Klik je die weg, dan gebeurt er niets.
|
||||
|
||||
Het programma vraagt dus niet uit zichzelf om rechten bij het starten: alleen als je iets wilt
|
||||
doen waarvoor ze nodig zijn.
|
||||
|
||||
## Waar de service zijn spullen zoekt
|
||||
|
||||
Windows start een service met `C:\Windows\System32` als werkmap. Alle relatieve paden in
|
||||
`appsettings.json` worden daarom omgezet naar paden **naast de exe**:
|
||||
|
||||
| Instelling | Standaard | Wordt |
|
||||
|---|---|---|
|
||||
| `Security:MasterKeyFile` | `keys/masterkey.json` | `<publish-map>\keys\masterkey.json` |
|
||||
| `Storage:Path` | `kluisbestanden` | `<publish-map>\kluisbestanden` |
|
||||
| `Smtp:DropFolder` | `mail-uitgaand` | `<publish-map>\mail-uitgaand` |
|
||||
|
||||
Zet je er een volledig pad neer (`D:\MijnBesluit\keys\masterkey.json`), dan wordt dat gebruikt
|
||||
zoals het er staat. Voor het sleutelbestand is dat vaak handiger: dan overleeft het een nieuwe
|
||||
publish.
|
||||
|
||||
Het serviceaccount **LocalSystem** moet het sleutelbestand kunnen lezen en in de map met
|
||||
kluisbestanden kunnen schrijven.
|
||||
|
||||
## Hoe de server met verbindingen omgaat
|
||||
|
||||
* Eén thread accepteert verbindingen en doet verder niets.
|
||||
* Elke verbinding krijgt zijn **eigen thread**, met een blokkerende leeslus. Die thread heet
|
||||
`MijnBesluit-verbinding-<nummer>`, dus in een dump of in de debugger zie je meteen welke
|
||||
verbinding waar hangt.
|
||||
* Elk binnengekomen verzoek krijgt een **eigen thread**. De leeslus gaat meteen door met lezen,
|
||||
dus een trage databaseactie houdt de volgende verzoeken van diezelfde app niet op. Per
|
||||
verbinding lopen er maximaal `Socket:MaxVerzoekenPerVerbinding` tegelijk (standaard 8) en over
|
||||
de hele server `Socket:MaxVerzoekenTotaal` (standaard 200); daarboven wordt er van die
|
||||
verbinding even niet gelezen, wat vanzelf tegendruk geeft.
|
||||
|
||||
Bewust een echte thread en niet de threadpool: de afhandeling blokkeert op de database en op
|
||||
bestanden, en dat zou de pool uithongeren — die groeit maar langzaam, waardoor verzoeken
|
||||
alsnog achter elkaar zouden gaan lopen in plaats van naast elkaar. Een verzoekthread krijgt
|
||||
512 kB stapelruimte (`Socket` hoeft daar niets voor te weten; zie `RequestThreadStackBytes`).
|
||||
* Het wegschrijven van antwoorden is geserialiseerd: er kunnen nooit twee antwoorden door elkaar
|
||||
over de lijn gaan. Antwoorden kunnen wél in een andere volgorde terugkomen dan de verzoeken
|
||||
binnenkwamen — de app koppelt ze op `requestId`.
|
||||
* Bij afsluiten wacht de server tot de lopende verzoeken van een verbinding klaar zijn, zodat er
|
||||
niet naar een al opgeruimde verbinding wordt geschreven.
|
||||
|
||||
Omdat elk verzoek op zijn eigen thread loopt, krijgt het ook zijn eigen databasecontext: een
|
||||
EF Core-context is niet bedoeld om door meerdere threads tegelijk gebruikt te worden.
|
||||
|
||||
## Instellingen
|
||||
|
||||
```json
|
||||
"Socket": {
|
||||
"Address": "0.0.0.0",
|
||||
"Port": 5200,
|
||||
"MaxVerbindingen": 500,
|
||||
"MaxVerzoekenPerVerbinding": 8,
|
||||
"MaxVerzoekenTotaal": 200,
|
||||
"CertificateFile": "D:\\MijnBesluit\\certificaat.pfx",
|
||||
"CertificatePassword": "...",
|
||||
"CertificateThumbprint": ""
|
||||
}
|
||||
```
|
||||
|
||||
`CertificateThumbprint` pakt een certificaat uit de machine-certificaatopslag van Windows —
|
||||
handig als je er al een hebt dat automatisch vernieuwd wordt. Spaties in de vingerafdruk maken
|
||||
niet uit; die worden eruit gehaald.
|
||||
|
||||
## Als er iets misgaat
|
||||
|
||||
* **Je wilt meekijken terwijl hij draait.** Stop de service en start dezelfde exe zelf met
|
||||
`--service`:
|
||||
|
||||
```
|
||||
net stop MijnBesluitSocket
|
||||
cd C:\MijnBesluitSocket
|
||||
MijnBesluit.SocketServer.exe --service
|
||||
```
|
||||
|
||||
Er verschijnt dan een venster met het logboek erin; daar komt ook langs waarom een verzoek
|
||||
misging (`Fout bij '<actie>': …`). Afsluiten met Ctrl+C, en daarna `net start MijnBesluitSocket`
|
||||
niet vergeten. Liever een bestand? Zet er `> log.txt 2>&1` achter; dan blijft het venster weg
|
||||
en vult dat bestand zich terwijl hij draait.
|
||||
|
||||
* **De service start niet.** Kijk in *Logboeken → Windows-logboeken → Toepassing*. De meest
|
||||
voorkomende oorzaak is dat de poort al bezet is; dat staat er dan letterlijk.
|
||||
* **"De service is verwijderd maar Windows is er nog niet mee klaar."** Er staat nog een venster
|
||||
*Services* open met de service geselecteerd. Sluit dat en probeer het over een halve minuut
|
||||
opnieuw.
|
||||
* **Het schermpje zegt dat de poort bezet is.** Met `netstat -ano | findstr :5200` zie je welk
|
||||
proces erop luistert.
|
||||
Reference in New Issue
Block a user