Goal & Transport
HTTP/1.1 (or HTTP/2) JSON UTF-8 Content-Type: application/json
- Goal. Serve DBF/CDX/FPT operations with correct file/record locking, high performance, and clear, standard errors.
- Versioning. Prefix with
/v1/…
. OptionalX-DBFMS-Capabilities
header for features. - Auth. Optional bearer/API key (out of scope for v0.1).
Locking semantics: write ops perform a short
FLOCK()
, re-check business rule inside the lock, write, DBCOMMIT()
, UNLOCK()
.Conventions
- Paths: local server filesystem (e.g.,
D:/DATAWIN/KUNDEN.dbf
). UNC allowed but discouraged. - Dates:
YYYY-MM-DD
; hotel availability uses[start,end)
. - Strings: Server converts DBF codepage → UTF-8.
- Numbers/Booleans: JSON native types.
Status Codes & Error Schema
200 OK
400 Bad request
404 Not found (seek/record)
409 Conflict (business rule)
412 Precondition failed (ETag)
423 Locked (file/record)
502/504 Backend down/timeout
Error body
{
"success": false,
"error": "E_LOCK|E_CONFLICT|E_NET|E_ARGS|E_COMMIT|E_NOREC|E_COLUMN",
"message": "human readable",
"details": { }
}
Minimal Endpoints (v0.1)
0) Health
GET /v1/health
200 {"ok":true,"version":"1.0.0","pid":5140,"uptime_sec":12345}
1) Read one record
POST /v1/readrecord
{"databasePath":"D:/DATAWIN/KUNDEN.dbf","recordId":23278,"fields":["NAME","PLZ","ORT"]}
200 {"success":true,"recordId":23278,"record":{"NAME":"Otto","PLZ":"9920","ORT":"Sillian"}}
2) Read range / page
POST /v1/readrange
{"databasePath":"D:/DATAWIN/KUNDEN.dbf","offset":1,"limit":50,"fields":["NAME","PLZ","ORT"]}
3) Seek (by tag/key)
POST /v1/seek
{"databasePath":"D:/DATAWIN/KUNDEN.dbf","tag":"B_NAME","key":"ATZWANGER","soft":true}
4) Update fields (atomic)
POST /v1/updaterecordfields
{
"databasePath":"D:/DATAWIN/BELEGUNG.dbf",
"recordId":23278,
"fields":{"PLANNAME":"Otto","ANREISE":"2025-09-10","ABREISE":"2025-09-12"},
"ifMatch":"\"etag-23278-abc\""
}
200 {"success":true,"updated":3,"etag":"\"etag-23278-def\""}
412 {"success":false,"error":"E_PRECONDITION","message":"ETag mismatch"}
423 {"success":false,"error":"E_LOCK","message":"file is locked"}
5) Append
POST /v1/append
{"databasePath":"D:/DATAWIN/BELEGUNG.dbf","fields":{"ZIMMERNR":"101","PLANNAME":"Muster","ANREISE":"2025-09-01","ABREISE":"2025-09-05"}}
6) Delete
POST /v1/deleterecord
{"databasePath":"D:/DATAWIN/BELEGUNG.dbf","recordId":24567}
7) Lock / Unlock (optional)
POST /v1/lock
{"databasePath":"D:/DATAWIN/BELEGUNG.dbf","type":"file"}
POST /v1/unlock
{"databasePath":"D:/DATAWIN/BELEGUNG.dbf","type":"file"}
8) Indexes & Order
GET /v1/indexes?databasePath=D:/DATAWIN/BELEGUNG.dbf
200 {"success":true,"indexes":[{"tag":"B_ANREISE","desc":false},{"tag":"B_ABREISE","desc":true}]}
POST /v1/order
{"databasePath":"D:/DATAWIN/BELEGUNG.dbf","tag":"B_ANREISE"}
9) Business hook: availability
POST /v1/isfree
{
"databasePath":"D:/DATAWIN/BELEGUNG.dbf",
"room":"101","start":"2025-09-10","end":"2025-09-14","ignoreBookingId":23278
}
200 {"success":true,"free":true}
409 {"success":false,"error":"E_CONFLICT","message":"overlap",
"conflict":{"buchungsnr":99123,"anreise":"2025-09-12","abreise":"2025-09-15","planname":"Müller"}}
Logging (SHOULD)
Log one JSON line per request: timestamp, method, path, ms, bytes_in/out, pid, fileIndex (if available), databasePath, recno, clientId, result.
Implementation Notes (Server)
- Open files locally (e.g.,
D:\…
), not via UNC. - Write ops:
FLOCK()
→ re-check → write →DBCOMMIT()
→UNLOCK()
. - Fallback to
DBRLOCK()
with short retry where file lock unavailable. - Return clear HTTP codes (409/423) + error JSON.
- Expose
X-DBFMS-Server: name/version
header.
Client Wrapper (Harbour/Xbase++)
// Harbour/XPP helpers (v0.1)
dbf_read( cPath, nRec, aFields ) // -> hRecord | error
dbf_update( cPath, nRec, hFields ) // -> .T./.F. with error
dbf_seek( cPath, cTag, uKey, lSoft ) // -> nRec | NIL
dbf_append( cPath, hFields ) // -> nRec
dbf_isfree( cPath, cRoom, dStart, dEnd, nIgnore ) // -> .T./.F. (+conflict)
Appendix: Posting + Diagrams
Short Posting (EN)
SMB vs. Socket – why a microservice is more stable for DBF
SMB (network drives) is chatty: every seek/lock crosses the network and
deals with oplocks/leases/AV. With a socket service, one server process
opens DBFs locally (D:\...), all clients send compact requests via TCP/HTTP,
locking is clean and serialized, performance is typically 5–10× faster.
Short Posting (ES)
SMB vs. Socket – por qué un microservicio es más estable para DBF
Con SMB (unidad de red) cada operación cruza la red y negocia bloqueos.
Un microservicio en sockets abre los DBF localmente (D:\...), los clientes
envían peticiones compactas y el locking se gestiona de forma centralizada,
normalmente 5–10× más rápido.
ASCII Diagrams
SMB (Network Drive)
[Client A] -----\
\
[Client B] -------> [SMB Redirector] -> [Network] -> [Server FS + NTFS]
/
[Client C] -----/
- Each client opens DBF via SMB
- Many roundtrips per DBF op
- Locks negotiated over the network
Socket Microservice
[Client A] ----\
\
[Client B] -------> [Socket Service @Server] -> [NTFS local]
/
[Client C] ----/
- Only service holds DBFs locally (D:\…)
- Clients send compact requests (seek/update)
- Few roundtrips, clean locking, 5–10× faster