http

package
v0.40.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 13, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package http provides the scriptling.runtime.http library: route, static file and WebSocket registration for the scriptling server, plus response helpers. It links gorilla/websocket for the server-side WebSocket wrapper.

Registrations are recorded in State, which shares runtime.RuntimeState's lock with the server lifecycle in the core runtime package: start_server() snapshots the routes (RuntimeState.ServerCollect) under that lock, so a route is either in the snapshot or warned about as registered too late.

Index

Constants

View Source
const LibraryName = "scriptling.runtime.http"

LibraryName is the name scripts import the library by.

View Source
const MaxWebSocketMessage = 8 << 20 // 8 MiB

MaxWebSocketMessage bounds a single inbound frame set. Without it a client can pin as much memory as it cares to send.

Variables

View Source
var RequestClass = &object.Class{
	Name: "Request",
	Methods: map[string]object.Object{
		"path_param": requestFieldGetter("path_params", "path_param", false, `path_param(name, default=None) - Get a path parameter captured from a route wildcard

Route patterns like "/api/users/{id}" capture the matching request path
segments. Returns the captured value, default, or None.

Parameters:
  name (string): Path parameter name
  default (any, optional): Value returned when the parameter is absent`),
		"query_param": requestFieldGetter("query", "query_param", false, `query_param(name, default=None) - Get a query parameter

Returns the first value of the query parameter, default, or None.

Parameters:
  name (string): Query parameter name
  default (any, optional): Value returned when the parameter is absent`),
		"header": requestFieldGetter("headers", "header", true, `header(name, default=None) - Get a request header

Header names are case-insensitive. Returns the header value, default, or None.

Parameters:
  name (string): Header name
  default (any, optional): Value returned when the header is absent`),
		"json": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("json() called on non-Request object")
				}

				body, err := instance.Field("body").AsString()
				if err != nil {
					return err
				}

				if body == "" {
					return &object.Null{}
				}

				return conversion.MustParseJSON(body)
			},
			HelpText: `json() - Parse request body as JSON

Returns the parsed JSON as a dict or list, or None if body is empty.`,
		},
	},
}

RequestClass is the class for Request objects passed to handlers

View Source
var State = struct {
	// HTTP routes, keyed "METHOD path"
	Routes          map[string]*RouteInfo
	Middleware      string
	NotFoundHandler string

	// WebSocket routes and connections
	WebSocketRoutes      map[string]*WebSocketRouteInfo
	WebSocketConnections map[string]*WebSocketServerConn
}{
	Routes:               make(map[string]*RouteInfo),
	WebSocketRoutes:      make(map[string]*WebSocketRouteInfo),
	WebSocketConnections: make(map[string]*WebSocketServerConn),
}

State holds the registrations made through scriptling.runtime.http. It is guarded by runtime.RuntimeState's lock (not a lock of its own) so that start_server() can snapshot it atomically with the server lifecycle; hold runtime.RuntimeState.Lock (or RLock to read) while touching it. runtime.ResetRuntime clears it and closes the open WebSocket connections.

View Source
var WebSocketClientClass = &object.Class{
	Name: "WebSocketClient",
	Methods: map[string]object.Object{
		"connected": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("connected() called on non-WebSocketClient object")
				}

				conn := getWSConnFromInstance(instance)
				if conn == nil {
					return object.NewBoolean(false)
				}
				return object.NewBoolean(conn.IsConnected())
			},
			HelpText: `connected() - Check if the WebSocket connection is still open

Returns True if connected, False otherwise.`,
		},
		"receive": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 1); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("receive() called on non-WebSocketClient object")
				}

				timeout := 30.0
				if t := kwargs.Get("timeout"); t != nil {
					if timeoutFloat, e := t.AsFloat(); e == nil {
						timeout = timeoutFloat
					}
				}

				conn := getWSConnFromInstance(instance)
				if conn == nil {
					return &object.Null{}
				}

				msgType, data, err := conn.ReadWithTimeout(time.Duration(timeout * float64(time.Second)))
				if err != nil || data == nil {
					return &object.Null{}
				}

				if msgType == websocket.TextMessage {
					return object.NewString(string(data))
				}

				elements := make([]object.Object, len(data))
				for i, b := range data {
					elements[i] = object.NewInteger(int64(b))
				}
				return &object.List{Elements: elements}
			},
			HelpText: `receive(timeout=30) - Receive a message from the WebSocket

Parameters:
  timeout (number, optional): Timeout in seconds (default: 30)

Returns:
  string for text messages, list of bytes for binary, or None on timeout/disconnect`,
		},
		"send": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 2); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("send() called on non-WebSocketClient object")
				}

				msg := args[1]
				var data []byte

				if dict, ok := msg.(*object.Dict); ok {
					jsonData, jsonErr := json.Marshal(conversion.ToGo(dict))
					if jsonErr != nil {
						return errors.NewError("failed to encode JSON: %s", jsonErr.Error())
					}
					data = jsonData
				} else if str, ok := msg.(*object.String); ok {
					data = []byte(str.StringValue())
				} else {
					strVal, coerceErr := object.CoerceWireString(msg)
					if coerceErr != nil {
						return errors.NewError("message must be string or dict")
					}
					data = []byte(strVal)
				}

				conn := getWSConnFromInstance(instance)
				if conn == nil {
					return errors.NewError("connection closed")
				}

				if writeErr := conn.WriteMessage(websocket.TextMessage, data); writeErr != nil {
					return errors.NewError("send failed: %s", writeErr.Error())
				}
				return &object.Null{}
			},
			HelpText: `send(message) - Send a message to the WebSocket client

Parameters:
  message (string or dict): Message to send. Dicts are automatically JSON encoded.`,
		},
		"send_binary": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.MinArgs(args, 2); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("send_binary() called on non-WebSocketClient object")
				}

				list, ok := args[1].(*object.List)
				if !ok {
					return errors.NewError("send_binary requires a list of bytes")
				}

				data := make([]byte, len(list.Elements))
				for i, elem := range list.Elements {
					b, e := elem.AsInt()
					if e != nil || b < 0 || b > 255 {
						return errors.NewError("send_binary requires list of bytes (0-255)")
					}
					data[i] = byte(b)
				}

				conn := getWSConnFromInstance(instance)
				if conn == nil {
					return errors.NewError("connection closed")
				}

				if writeErr := conn.WriteMessage(websocket.BinaryMessage, data); writeErr != nil {
					return errors.NewError("send_binary failed: %s", writeErr.Error())
				}
				return &object.Null{}
			},
			HelpText: `send_binary(data) - Send binary data to the WebSocket client

Parameters:
  data (list): List of byte values (0-255)`,
		},
		"close": &object.Builtin{
			Fn: func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
				if err := errors.ExactArgs(args, 1); err != nil {
					return err
				}
				instance, ok := args[0].(*object.Instance)
				if !ok {
					return errors.NewError("close() called on non-WebSocketClient object")
				}

				conn := getWSConnFromInstance(instance)
				if conn != nil {
					conn.Close()
				}
				return &object.Null{}
			},
			HelpText: `close() - Close the WebSocket connection`,
		},
	},
}

WebSocketClientClass is the class for WebSocket client objects passed to handlers

Functions

func CreateRequestInstance

func CreateRequestInstance(method, path, body string, headers, query, pathParams map[string]string, remoteAddr string) *object.Instance

CreateRequestInstance creates a new Request instance with the given data. pathParams holds values captured from route wildcards ("{id}" and "{path...}") and remoteAddr the client address; both are exposed as request fields (path_params, remote_addr). The context field starts as an empty dict for middleware to populate — see WithRequestContext.

func CreateWebSocketClientInstance

func CreateWebSocketClientInstance(conn *WebSocketServerConn) *object.Instance

CreateWebSocketClientInstance creates a new WebSocketClient instance

func Register

func Register(registrar object.LibraryRegistrar, allowedPaths []string)

Register registers the scriptling.runtime.http library. allowedPaths restricts the directories static() may serve, as it restricts the filesystem libraries: nil leaves them unrestricted, an empty slice denies every one. A directory outside the allowed paths is refused with a PermissionError that a script cannot catch, and the server serves only files that resolve inside the allowed paths.

When scriptling.runtime is also registered with the same registrar the library is reachable as an attribute of it (`rt.http`) too.

Types

type RouteInfo

type RouteInfo struct {
	Methods   []string
	Handler   string
	Static    bool
	StaticDir string
	// StaticAllowed is the --allowed-paths set in force when the static route
	// was registered (absolute); nil means unrestricted. The server serves
	// only files that resolve inside it, and hides dotfiles and listings.
	StaticAllowed []string
}

RouteInfo stores information about a registered route

type WebSocketRouteInfo

type WebSocketRouteInfo struct {
	Handler string // "library.function" to call for each connection
}

WebSocketRouteInfo stores information about a registered WebSocket route

type WebSocketServerConn

type WebSocketServerConn struct {
	// contains filtered or unexported fields
}

WebSocketServerConn wraps a server-side WebSocket connection.

Locking: mu guards state, readMu serializes readers, and writeMu serializes writers. Neither I/O lock is needed by Close, so closing the transport can interrupt a blocked read or write while preserving Gorilla's one-reader / one-writer rules.

func NewWebSocketServerConn

func NewWebSocketServerConn(conn *websocket.Conn, id string) *WebSocketServerConn

NewWebSocketServerConn creates a new server WebSocket connection wrapper

func (*WebSocketServerConn) Close

func (c *WebSocketServerConn) Close() error

Close closes the connection

func (*WebSocketServerConn) ClosedChan

func (c *WebSocketServerConn) ClosedChan() <-chan struct{}

ClosedChan returns a channel that closes when the connection closes

func (*WebSocketServerConn) ID

func (c *WebSocketServerConn) ID() string

ID returns the connection ID

func (*WebSocketServerConn) IsConnected

func (c *WebSocketServerConn) IsConnected() bool

IsConnected returns whether the connection is still open

func (*WebSocketServerConn) ReadWithTimeout

func (c *WebSocketServerConn) ReadWithTimeout(timeout time.Duration) (int, []byte, error)

ReadWithTimeout reads a message with timeout. Returns messageType, data, error. On timeout, returns 0, nil, nil

func (*WebSocketServerConn) RemoteAddr

func (c *WebSocketServerConn) RemoteAddr() string

RemoteAddr returns the remote address

func (*WebSocketServerConn) WriteMessage

func (c *WebSocketServerConn) WriteMessage(msgType int, data []byte) error

WriteMessage sends a message

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL