// ServerHandle allows stopping a service started via StartBackground.
type ServerHandle struct {
service *{{ service_name }}
}
// Stop gracefully shuts down the server.
func (h *ServerHandle) Stop() error {
if h.service == nil {
return errors.New("service already stopped")
}
h.service.Close()
h.service = nil
return nil
}
// StartBackground starts the service in a background goroutine and returns a handle.
// It blocks until the TCP socket is bound, so the server is guaranteed to be accepting
// connections when this call returns.
func (s *{{ service_name }}) StartBackground(host string, port uint16) (*ServerHandle, error) {
// Configure the service with the provided host and port.
hostCopy := host
portCopy := port
workers := uint(1)
maxBodySize := uint(10 * 1024 * 1024)
enableReqID := false
gracefulShutdown := true
shutdownTimeout := uint64(30)
enableHTTPTrace := false
maxQueue := uint(1024)
maxConcurrent := uint(128)
drainTimeout := uint64(30)
config := &ServerConfig{
Host: &hostCopy,
Port: &portCopy,
Workers: &workers,
EnableRequestID: enableReqID,
MaxBodySize: &maxBodySize,
GracefulShutdown: &gracefulShutdown,
ShutdownTimeout: &shutdownTimeout,
EnableHTTPTrace: enableHTTPTrace,
BackgroundTasks: BackgroundTaskConfig{
MaxQueueSize: &maxQueue,
MaxConcurrentTasks: &maxConcurrent,
DrainTimeoutSecs: &drainTimeout,
},
}
// Ensure static_files is present in JSON even if empty (for Rust compatibility).
c_configJSON, _ := json.Marshal(config)
var m map[string]interface{}
json.Unmarshal(c_configJSON, &m)
if _, has := m["static_files"]; !has {
m["static_files"] = []interface{}{}
}
c_configJSON, _ = json.Marshal(m)
c_str := C.CString(string(c_configJSON))
defer C.free(unsafe.Pointer(c_str))
c_config := C.{{ ffi_prefix }}server_config_from_json(c_str)
if c_config == nil {
errCode := C.{{ ffi_prefix }}last_error_code()
errMsg := C.GoString(C.{{ ffi_prefix }}last_error_context())
return nil, fmt.Errorf("configure host/port: ServerConfig config failed: error code %d: %s", errCode, errMsg)
}
new_owner := C.{{ ffi_prefix }}app_config((*C.{{ ffi_prefix | upper }}AppOpaque)(s.owner), c_config)
if new_owner == nil {
return nil, errors.New("configure host/port: app config failed")
}
s.owner = unsafe.Pointer(new_owner)
// Lock to check ownership and then spawn Run in a goroutine.
s.mu.Lock()
if s.owner == nil {
s.mu.Unlock()
return nil, errors.New("service is closed")
}
s.mu.Unlock()
// Spawn Run in a goroutine.
go func() {
_ = s.Run()
}()
// Poll TCP socket until it's bound or timeout (5 seconds).
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), 100*time.Millisecond)
if err == nil {
conn.Close()
break
}
time.Sleep(50 * time.Millisecond)
}
// Return a handle for shutdown.
return &ServerHandle{service: s}, nil
}