From a03f6a70a47d22424d6549ba124961c62e319b45 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Mon, 29 Dec 2025 15:58:33 -0500 Subject: [PATCH] actors: Add pre-made HTTP actors --- actors/http.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 actors/http.go diff --git a/actors/http.go b/actors/http.go new file mode 100644 index 0000000..3fd924f --- /dev/null +++ b/actors/http.go @@ -0,0 +1,133 @@ +package http + +import "fmt" +import "log" +import "errors" +import "context" +import "net/http" +import cf "git.tebibyte.media/sashakoshka/camfish" + +var _ cf.Actor = new(HTTP) +var _ cf.RunShutdownable = new(HTTP) +var _ cf.Configurable = new(HTTP) +var _ cf.Initializable = new(HTTP) + +// HTTP is an actor providing an HTTP server. Its configuration options are +// as follows: +// +// - http.address: The host:port to serve on +// - http.cert-file: The location of the public TLS/SSL certificate +// - http.key-file: The location of the private TLS/SSL key +// +// If neither cert-file nor key-file are specified, the actor will serve +// over HTTPS instead of HTTP. +type HTTP struct { + // Typ determines the actor's type. If empty, it defaults to "http". + // Once the actor has been used in any way at all, this field must + // never be modified. + Typ string + // Handler is used to handle HTTP requests. If nil, the server will + // respond with a "404 Not found" error. + // Once the actor has been used in any way at all, this field must + // never be modified. + Handler http.Handler + // DefaultAddr specifies the default address to serve on if none is + // specified. This itself defaults to localhost:8080. + DefaultAddr + + server *http.Server + address string + certFile string + keyFile string +} + +// Type returns "http", or this.Typ if specified. +func (this *HTTP) Type() string { + if this.Typ == "" { + return "http" + } + return this.Typ +} + +// Configure configures the actor. +func (this *HTTP) Configure(conf cf.Config) error { + { + value := conf.Get(this.Type() + ".address") + if value == "" { + if this.DefaultAddr == "" { + value = "localhost:8080" + } else { + value = this.DefaultAddr + } + this.address = value + } + { + certFile := this.Type() + "cert-file" + keyFile := this.Type() + "key-file" + this.certFile = conf.Get(certFile) + this.keyFile = conf.Get(keyFile) + if this.certFile == "" && this.keyFile != "" || + this.certFile != "" && this.keyFile == "" { + return fmt.Errorf( + "both %s and %s http.key-file must be specified, or neither", + this.certFile, + this.keyFile) + } + } + return nil +} + +// Init initializes the actor. +func (this *HTTP) Init(ctx context.Context) error { + this.server = &http.Server { + Addr: this.address, + Handler: this.Handler, + } + return ctx.Err() +} + +// Run runs the actor. +func (this *HTTP) Run() error { + log.Printf("(i) [%s] listening on %s", this.Type(), this.address) + err := this.server.ListenAndServe() + if errors.Is(err, http.ErrServerClosed) { return nil } + return err +} + +// Shutdown shuts down the actor. +func (this *HTTP) Shutdown(ctx context.Context) error { + return this.server.Shutdown(ctx) +} + +var _ cf.Actor = new(HTTPServer) +var _ cf.RunShutdownable = new(HTTPServer) +var _ cf.Initializable = new(HTTPServer) + +// HTTPServer lets you use a pre-existing [http.Server] as an actor. It has +// no configuration or anything. +type HTTPServer struct { + // Server must be non-nil. + *HTTP.Server + + // Typ determines the actor's type. If empty, it defaults to "http". + // Once the actor has been used in any way at all, this field must + // never be modified. + Typ string +} + + +// Type returns "http", or this.Typ if specified. +func (this *HTTPServer) Type() string { + if this.Typ == "" { + return "http" + } + return this.Typ +} + +// Run runs the actor. +func (this *HTTPServer) Run() error { + log.Printf("(i) [%s] listening on %s", this.Type(), this.Addr) + err := this.server.ListenAndServe() + if errors.Is(err, http.ErrServerClosed) { return nil } + return err +} \ No newline at end of file