Files
five/examples/go_httpserver.prg
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

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