Handler Architecture

Audience: SDK developers who want to understand how ActingWeb handlers work internally.

This document explains the architecture of ActingWeb’s HTTP handlers and how they interact with the developer API.

Overview

ActingWeb handlers are the bridge between HTTP requests and business logic. They:

  1. Parse incoming HTTP requests

  2. Authenticate the caller

  3. Validate permissions

  4. Delegate to the developer API (ActorInterface, PropertyStore, etc.)

  5. Format and return responses

Handler Hierarchy

All handlers inherit from BaseHandler:

BaseHandler (base_handler.py)
├── PropertiesHandler (properties.py)
├── TrustHandler (trust.py)
├── SubscriptionHandler (subscription.py)
├── CallbacksHandler (callbacks.py)
├── MethodsHandler (methods.py)
├── ActionsHandler (actions.py)
├── FactoryHandler (factory.py)
├── OAuthHandler (oauth.py)
├── OAuth2CallbackHandler (oauth2_callback.py)
├── MetaHandler (meta.py)
├── WwwHandler (www.py)
└── DevtestHandler (devtest.py)

BaseHandler

BaseHandler provides common functionality:

class BaseHandler:
    def __init__(self, webobj, config, hooks=None):
        self.webobj = webobj      # AWWebObj instance
        self.config = config      # ActingWeb configuration
        self.hooks = hooks        # Hook registry

    # Authentication helpers
    def _authenticate(self) -> AuthResult: ...
    def _basic_auth(self) -> Optional[Tuple[str, str]]: ...
    def _bearer_auth(self) -> Optional[str]: ...

    # Response helpers
    def _json_response(self, data, status=200): ...
    def _error_response(self, message, status=400): ...
    def _not_found(self): ...
    def _forbidden(self, message=""): ...
    def _unauthorized(self, message=""): ...

    # Actor helpers
    def _get_actor(self, actor_id) -> Optional[Actor]: ...
    def _get_actor_interface(self, actor) -> ActorInterface: ...
    def _get_authenticated_view(self, actor, auth_result): ...

Handler Lifecycle

Each HTTP request follows this lifecycle:

1. Framework routes request to handler
2. Handler.__init__() with webobj, config, hooks
3. Handler.method() called (get, post, put, delete)
   a. Parse request parameters
   b. Authenticate caller
   c. Load actor
   d. Check permissions
   e. Execute business logic via developer API
   f. Format response
4. Framework sends response

Example: Properties GET

class PropertiesHandler(BaseHandler):
    def get(self, actor_id: str, path: str = ""):
        # 1. Load actor
        actor = self._get_actor(actor_id)
        if not actor:
            return self._not_found()

        # 2. Authenticate
        auth_result = self._authenticate()

        # 3. Get authenticated view
        actor_interface = self._get_actor_interface(actor)
        auth_view = self._get_authenticated_view(actor_interface, auth_result)

        # 4. Access properties (permissions enforced if auth_view)
        if auth_view:
            props = auth_view.properties
        else:
            props = actor_interface.properties

        # 5. Return response
        if path:
            value = props.get(path)
            if value is None:
                return self._not_found()
            return self._json_response({path: value})
        else:
            return self._json_response(props.to_dict())

Handler Responsibilities

Each handler has specific responsibilities:

PropertiesHandler

  • GET/PUT/DELETE single properties

  • POST bulk property updates

  • Property list operations (items, metadata)

  • register_diffs for subscription notifications

TrustHandler

  • Trust relationship CRUD

  • Verification protocol (handshake)

  • Permission management

  • Lifecycle hooks (trust_approved, trust_deleted)

SubscriptionHandler

  • Subscription CRUD

  • Diff retrieval and clearing

  • Remote subscription management

CallbacksHandler

  • Receive callbacks from peers

  • Route to appropriate subscription

  • Execute callback hooks

MethodsHandler / ActionsHandler

  • Route to registered hooks

  • Execute custom business logic

  • Return hook results

FactoryHandler

  • Create new actors

  • Handle OAuth2 redirect for web creation

  • Content negotiation (JSON vs HTML)

Developer API Integration

Handlers use the developer API exclusively for business logic:

Before Refactoring (Direct Actor Access)

# OLD: Direct actor method calls
def post(self, actor_id):
    actor = self._get_actor(actor_id)
    actor.create_subscription(peer_id, ...)  # Direct call

After Refactoring (Developer API)

# NEW: Via developer API
def post(self, actor_id):
    actor = self._get_actor(actor_id)
    actor_interface = self._get_actor_interface(actor)
    actor_interface.subscriptions.create_local_subscription(...)  # Developer API

Benefits

  1. Consistent interface - Same API in handlers and hooks

  2. Permission enforcement - AuthenticatedActorView integration

  3. Lifecycle hooks - Automatic hook execution

  4. Change notifications - Automatic diff generation

  5. Testability - Easy to mock developer API

Hook Execution

Handlers delegate to hooks for custom business logic:

class MethodsHandler(BaseHandler):
    def post(self, actor_id: str, name: str):
        actor = self._get_actor(actor_id)
        actor_interface = self._get_actor_interface(actor)

        # Parse request body
        data = self._parse_json_body()

        # Execute method hook
        result = self.hooks.execute_method_hooks(
            actor_interface,
            name,
            data
        )

        if result is None:
            return self._not_found()

        return self._json_response(result)

Authentication Flow

Handlers support multiple authentication methods:

def _authenticate(self) -> AuthResult:
    """Authenticate the request."""

    # Try bearer token (OAuth2)
    token = self._bearer_auth()
    if token:
        return self._validate_oauth2_token(token)

    # Try basic auth
    credentials = self._basic_auth()
    if credentials:
        return self._validate_basic_auth(credentials)

    # Try passphrase (query param)
    passphrase = self.webobj.request.get("passphrase")
    if passphrase:
        return self._validate_passphrase(passphrase)

    # No authentication
    return AuthResult(authenticated=False)

Error Handling

Handlers use consistent error responses:

# 400 Bad Request
self._error_response("Invalid JSON", 400)

# 401 Unauthorized
self._unauthorized("Authentication required")

# 403 Forbidden
self._forbidden("Not authorized for this resource")

# 404 Not Found
self._not_found()

# 405 Method Not Allowed
self._method_not_allowed()

# 500 Internal Server Error
self._server_error("Unexpected error")

Testing Handlers

Handlers can be tested by mocking the webobj:

def test_properties_get():
    # Create mock webobj
    webobj = MockWebObj()
    webobj.request.method = "GET"

    # Create handler
    handler = PropertiesHandler(
        webobj=webobj,
        config=test_config,
        hooks=test_hooks
    )

    # Execute
    handler.get(actor_id="test123", path="status")

    # Assert
    assert webobj.response.status_code == 200
    assert "status" in json.loads(webobj.response._body)

See Also