Simple HTTP Server Example with Cellframe Node

The Cellframe Node python extensions provide classes and functions for implementing a simple HTTP server and handling HTTP requests. This example demonstrates how to set up an HTTP server, define request handlers, and make self-requests to the server for testing purposes.

Prerequisites

To allow the Cellframe Node to accept HTTP requests, you need to enable the HTTP server in the config file. Set enabled=true and specify listen_port_tcp and listen_address values under the [[Cellframe Node General Config#Section [notify_server]|notify_server]] section.

Overview

In this example, we will:

  1. Define handler functions for processing HTTP requests.
  2. Set up the server and register the handler functions.
  3. Make GET and POST requests to the server using the ClientHTTP class.
  4. Log the responses and any errors.

Handler Functions

We define two handler functions for processing HTTP requests:

  • example_1_handler(request: HttpSimple, httpCode: HttpCode): Processes the request and returns a predefined response.
  • example_2_handler(request: HttpSimple, httpCode: HttpCode): Processes the request, including headers and body, and returns the information in a formatted response.

Initialization and Making Requests

In the init function, we:

  1. Initialize the server and register the handler functions.
  2. Make GET and POST requests to the server using the ClientHTTP class.
  3. Log the responses and any errors.

Warning

You should never use blocking and time-heavy methods in plugins init() function

from DAP.Core import logIt, AppContext
from DAP.Network import HttpSimple, Server, ClientHTTP, HttpHeader, HttpCode
from DAP import configGetItem
from urllib.parse import unquote
import time
 
# Constants for URI paths WITHOUT prefix "/"
EXAMPLE_1_URI = "example_1"
EXAMPLE_2_URI = "example_2"
 
# Fetch configuration settings
SERVER_PORT = int(configGetItem("server", "listen_port_tcp"))
SERVER_ADDRESS = configGetItem("notify_server", "listen_address")
 
HTTP_REPLY_SIZE_MAX = 10 * 1024 * 102
 
# Handler for Example 1 URI
def example_1_handler(request: HttpSimple, httpCode: HttpCode):
    """
    Handles requests to the /example_1 endpoint.
 
    Args:
        request (HttpSimple): The incoming HTTP request.
        httpCode (HttpCode): The HTTP status code to set in the response.
    """
    logIt.notice("Handling request for /example_1")
 
    # Log request headers
    for header in request.requestHeader:
        logIt.notice(str(header))
 
    # Prepare response
    response_message = "Hello world! This is API for test plugin"
    request.replyAdd(response_message.encode('utf-8'))
 
    # Set a custom response header
    custom_header = HttpHeader("Response-Test", "OK")
    request.setResponseHeader(custom_header)
 
    # Set the HTTP status code
    httpCode.set(200)
    return
 
 
# Handler for Example 2 URI
def example_2_handler(request: HttpSimple, httpCode: HttpCode):
    """
    Handles requests to the /example_2 endpoint.
 
    Args:
        request (HttpSimple): The incoming HTTP request.
        httpCode (HttpCode): The HTTP status code to set in the response.
    """
    logIt.notice("Handling request for /example_2")
 
    # Set a custom response header
    custom_response_header = HttpHeader("Custom-Response-Header", "Custom-Value")
    request.setResponseHeader(custom_response_header)
 
    # Get request details
    url = request.urlPath
    method = request.action
    body = request.request
    query = request.query
    request_headers = request.requestHeader
    response_headers = request.getResponseHeader()
 
    # Decode the body if it's not None
    decoded_body = unquote(body) if body is not None else 'None'
 
    # Create response content
    response_content = [
        "Greetings from the Node!",
        f"URL = {url}",
        f"Method = {method}",
        f"Query = [{query}]",
        f"Body = {decoded_body}",
        "Request Headers:"
    ]
 
    # Add request headers to response content
    for header in request_headers:
        response_content.append(str(header))
 
    response_content.append("Response Headers:")
    response_content.append(f"{response_headers.name}: {response_headers.value}")
 
    # Join all parts into a single response body and encode
    response_body = "\n".join(response_content).encode("utf-8")
 
    # Add the response to the request
    request.replyAdd(response_body)
 
    # Set the HTTP status code
    httpCode.set(200)
    return
 
 
# Function to log the HTTP response
def log_http_response(data, args):
    """
    Logs the HTTP response.
 
    Args:
        data (bytes): The response data.
        argv (Any): Additional arguments.
    """
    logIt.notice("Received Response:")
    logIt.notice(f"Arguments: {args}")
 
    # Decode response data and log each line
    decoded_data = data.decode('utf-8')
    for line in decoded_data.split("\n"):
        logIt.notice(line)
    return
 
 
# Function to log HTTP errors
def log_http_error(code_err, args):
    """
    Logs HTTP errors.
 
    Args:
        code_err (int): The error code.
        argv (Any): Additional arguments.
    """
    logIt.notice("Error:")
    logIt.notice(f"Arguments: {args}")
    logIt.notice(f"Error Code: {code_err}")
    return
 
 
def init():
    # Create server and set it up with the application context
    server_instance = Server()
    AppContext.getServer(server_instance)
 
    # Register handlers for the URIs
    HttpSimple.addProc(server_instance,
                       f"/{EXAMPLE_1_URI}", HTTP_REPLY_SIZE_MAX, example_1_handler)
    HttpSimple.addProc(server_instance,
                       f"/{EXAMPLE_2_URI}", HTTP_REPLY_SIZE_MAX, example_2_handler)
 
    # Custom headers for requests
    custom_headers = ["Custom-Request-Header: Custom-Value"]
 
    # Message body for POST request
    post_request_body = "Hello, World!".encode("utf-8")
 
    # Send a GET request to the /example_1 endpoint
    ClientHTTP(SERVER_ADDRESS, SERVER_PORT, "GET", "application/json",
               EXAMPLE_1_URI, None, "", log_http_response, log_http_error,
               None, custom_headers, False)
 
    # Send a POST request to the /example_2 endpoint
    ClientHTTP(SERVER_ADDRESS, SERVER_PORT, "POST", "application/json",
               EXAMPLE_2_URI, post_request_body, "", log_http_response,
               log_http_error, None, custom_headers, False)
 
    return 0
 

Note

  • Ensure that the Cellframe Node configuration is set up correctly to allow the server to accept requests.
  • Use the ClientHTTP class or standard python urllib to send requests to the server for testing purposes.

This example demonstrates a basic implementation of an HTTP server and client in the Cellframe framework, showcasing how to handle HTTP requests and responses effectively.