- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
181 lines
5.2 KiB
Plaintext
181 lines
5.2 KiB
Plaintext
// Five Example: HTTP REST API Server
|
|
//
|
|
// PRG handles business logic (customer data, search)
|
|
// Go handles HTTP serving, JSON, concurrency
|
|
//
|
|
// Usage: five run go_httpserver.prg
|
|
// curl http://localhost:8080/api/customers
|
|
// curl http://localhost:8080/api/customers/search?name=John
|
|
|
|
PROCEDURE Main()
|
|
LOCAL cPort
|
|
|
|
cPort := "8080"
|
|
|
|
? "=== Five REST API Server ==="
|
|
? "Powered by Harbour data + Go net/http"
|
|
?
|
|
? "Starting server on port " + cPort + "..."
|
|
? "Endpoints:"
|
|
? " GET /api/customers - list all customers"
|
|
? " GET /api/customers/search - search by name (?name=xxx)"
|
|
? " POST /api/customers - add customer (JSON body)"
|
|
? " GET /api/stats - server statistics"
|
|
? " GET /health - health check"
|
|
?
|
|
? "Press Ctrl+C to stop"
|
|
|
|
GoHttpServe(cPort)
|
|
|
|
RETURN
|
|
|
|
FUNCTION GetCustomers()
|
|
LOCAL aResult
|
|
|
|
aResult := {}
|
|
AAdd(aResult, { "id" => 1, "name" => "Charles Kwon", "city" => "Seoul", "balance" => 15000.50 })
|
|
AAdd(aResult, { "id" => 2, "name" => "John Smith", "city" => "New York", "balance" => 8200.00 })
|
|
AAdd(aResult, { "id" => 3, "name" => "Maria Garcia", "city" => "Madrid", "balance" => 12300.75 })
|
|
AAdd(aResult, { "id" => 4, "name" => "Yuki Tanaka", "city" => "Tokyo", "balance" => 9800.25 })
|
|
AAdd(aResult, { "id" => 5, "name" => "Hans Mueller", "city" => "Berlin", "balance" => 6500.00 })
|
|
|
|
RETURN aResult
|
|
|
|
FUNCTION SearchCustomers(cSearch)
|
|
LOCAL aAll, aResult, i
|
|
|
|
aAll := GetCustomers()
|
|
aResult := {}
|
|
|
|
FOR i := 1 TO Len(aAll)
|
|
IF Upper(cSearch) $ Upper(aAll[i]["name"])
|
|
AAdd(aResult, aAll[i])
|
|
ENDIF
|
|
NEXT
|
|
|
|
RETURN aResult
|
|
|
|
#pragma BEGINDUMP
|
|
|
|
import (
|
|
"encoding/json"
|
|
"five/hbrt"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
var requestCount int64
|
|
var startTime time.Time
|
|
|
|
func init() {
|
|
hbrt.HB_FUNC("GOHTTPSERVE", goHttpServe)
|
|
}
|
|
|
|
func goHttpServe(ctx *hbrt.HBContext) {
|
|
port := ctx.ParC(1)
|
|
if port == "" {
|
|
port = "8080"
|
|
}
|
|
startTime = time.Now()
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/health", handleHealth)
|
|
mux.HandleFunc("/api/stats", handleStats)
|
|
mux.HandleFunc("/api/customers", handleCustomers)
|
|
mux.HandleFunc("/api/customers/search", handleSearch)
|
|
|
|
server := &http.Server{
|
|
Addr: ":" + port,
|
|
Handler: withLogging(mux),
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 15 * time.Second,
|
|
}
|
|
|
|
if err := server.ListenAndServe(); err != nil {
|
|
ctx.RetC("Error: " + err.Error())
|
|
}
|
|
}
|
|
|
|
func withLogging(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
atomic.AddInt64(&requestCount, 1)
|
|
start := time.Now()
|
|
next.ServeHTTP(w, r)
|
|
fmt.Printf(" %s %s %s [%v]\n", r.Method, r.URL.Path, r.RemoteAddr, time.Since(start))
|
|
})
|
|
}
|
|
|
|
func handleHealth(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"status": "healthy",
|
|
"uptime": time.Since(startTime).String(),
|
|
})
|
|
}
|
|
|
|
func handleStats(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"requests": atomic.LoadInt64(&requestCount),
|
|
"uptime_ms": time.Since(startTime).Milliseconds(),
|
|
"engine": "Five (Harbour + Go)",
|
|
})
|
|
}
|
|
|
|
func handleCustomers(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
customers := []map[string]interface{}{
|
|
{"id": 1, "name": "Charles Kwon", "city": "Seoul", "balance": 15000.50},
|
|
{"id": 2, "name": "John Smith", "city": "New York", "balance": 8200.00},
|
|
{"id": 3, "name": "Maria Garcia", "city": "Madrid", "balance": 12300.75},
|
|
{"id": 4, "name": "Yuki Tanaka", "city": "Tokyo", "balance": 9800.25},
|
|
{"id": 5, "name": "Hans Mueller", "city": "Berlin", "balance": 6500.00},
|
|
}
|
|
|
|
if r.Method == "POST" {
|
|
var newCustomer map[string]interface{}
|
|
if err := json.NewDecoder(r.Body).Decode(&newCustomer); err != nil {
|
|
http.Error(w, `{"error": "invalid JSON"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
newCustomer["id"] = len(customers) + 1
|
|
customers = append(customers, newCustomer)
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(newCustomer)
|
|
return
|
|
}
|
|
|
|
json.NewEncoder(w).Encode(customers)
|
|
}
|
|
|
|
func handleSearch(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
query := strings.ToLower(r.URL.Query().Get("name"))
|
|
if query == "" {
|
|
http.Error(w, `{"error": "name parameter required"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
customers := []map[string]interface{}{
|
|
{"id": 1, "name": "Charles Kwon", "city": "Seoul", "balance": 15000.50},
|
|
{"id": 2, "name": "John Smith", "city": "New York", "balance": 8200.00},
|
|
{"id": 3, "name": "Maria Garcia", "city": "Madrid", "balance": 12300.75},
|
|
{"id": 4, "name": "Yuki Tanaka", "city": "Tokyo", "balance": 9800.25},
|
|
{"id": 5, "name": "Hans Mueller", "city": "Berlin", "balance": 6500.00},
|
|
}
|
|
|
|
var results []map[string]interface{}
|
|
for _, c := range customers {
|
|
if strings.Contains(strings.ToLower(c["name"].(string)), query) {
|
|
results = append(results, c)
|
|
}
|
|
}
|
|
json.NewEncoder(w).Encode(results)
|
|
}
|
|
|
|
#pragma ENDDUMP
|