Ubuntu TechHive
from-networks-to-http-and-apis.md
From Networks To Http And Apis
article.detalle

From Networks To Http And Apis

reading.progreso 21 min de lectura

Description of From Networks To Http And Apis

From Networks To Http And Apis

Setup

[tool.poetry]
name = "from-networks-to-http-apis"
version = "0.1.0"
description = "From Networks To Http And Apis"
authors = ["user "]
license = "MIT"
readme = "README.md"
#packages = [{include = "devops_from_scratch_manual_process"}]

[tool.poetry.dependencies]
#python = "^3.10.6"
python = "^3.11"
sortedcontainers = "^2.4.0"
httpx = "^0.23.0"
pyzmq = "^25.1.0"
fastapi = "^0.97.0"
uvicorn = "^0.22.0"
requests = "^2.31.0"
openai = "^0.27.8"
whitenoise = "^6.2.0"
fontawesomefree = "^6.4.0"
typer = { version = "^0.9.0", extras = ["all"] }

#[tool.poetry.group.dev.dependencies]
#black = "^23.3.0"
#pytest = "7.3.1"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Network Topologies

Pub / Sub

graph LR

sub1((Sub 1)) -- Subscribe --> TopicA((Message Broker))
sub2((Sub 2)) -- Subscribe --> TopicB((Message Broker))
sub3((Sub 3)) -- Subscribe --> TopicA((Message Broker))
sub4((Sub 4)) -- Subscribe --> TopicC((Message Broker))

pub1((Pub 1)) -- Publish --> TopicA((Message Broker))
pub2((Pub 2)) -- Publish --> TopicB((Message Broker))
pub3((Pub 3)) -- Publish --> TopicC((Message Broker))

TopicA((Message Broker)) -- Publish --> sub1((Sub 1))
TopicA((Message Broker)) -- Publish --> sub3((Sub 3))
TopicB((Message Broker)) -- Publish --> sub2((Sub 2))
TopicC((Message Broker)) -- Publish --> sub4((Sub 4))

English

PUB/SUB (Publish/Subscribe) is a messaging pattern commonly used in distributed systems to enable communication between multiple publishers and multiple subscribers. It provides a way for publishers to send messages without having knowledge of the subscribers, and for subscribers to receive messages from multiple publishers without direct interaction with the publishers.

In the PUB/SUB pattern, publishers send messages, called "publications," to a specific topic or channel. Subscribers express their interest in receiving messages by subscribing to specific topics. The subscribers will then receive all publications that are sent to the topics they have subscribed to.

Here's a breakdown of the key components in PUB/SUB:

  1. Publisher: The entity responsible for sending messages or publications to a specific topic. It is unaware of the subscribers and their identities. Publishers can publish messages to one or more topics.
  2. Subscriber: The entity interested in receiving messages or publications on specific topics. Subscribers subscribe to one or more topics to indicate their interest. They will receive messages published to the topics they have subscribed to.
  3. Topic: A logical channel or category to which messages or publications are associated. Publishers send messages to specific topics, and subscribers receive messages from topics they have subscribed to. Topics allow subscribers to filter the messages they receive and focus on the ones relevant to their interests.
  4. Message: The information being sent by the publisher and received by the subscriber. It can be any data structure or payload that is meaningful to the applications involved.
  5. Message Broker: The intermediary component responsible for routing messages from publishers to subscribers. It manages the distribution of messages based on the topic subscriptions of subscribers.

In a PUB/SUB system, publishers and subscribers operate asynchronously. Publishers can send messages at any time without knowing which subscribers, if any, are currently active. Subscribers can join or leave the system at any time without affecting the publishers.

PUB/SUB messaging pattern is commonly used in various scenarios, such as real-time data streaming, event-driven architectures, chat applications, and distributed systems where decoupling publishers and subscribers is desired.

PyZMQ, the Python binding for ZeroMQ, provides a convenient way to implement PUB/SUB communication in Python applications.

Français

PUB/SUB (Publier/Abonner) est un modèle de messagerie couramment utilisé dans les systèmes distribués pour permettre la communication entre plusieurs éditeurs (publishers) et plusieurs abonnés (subscribers). Il offre un moyen aux éditeurs d'envoyer des messages sans avoir connaissance des abonnés, et aux abonnés de recevoir des messages de plusieurs éditeurs sans interaction directe avec les éditeurs.

Dans le modèle PUB/SUB, les éditeurs envoient des messages, appelés "publications", à un sujet (topic) spécifique. Les abonnés expriment leur intérêt à recevoir des messages en s'abonnant à des sujets spécifiques. Les abonnés recevront ensuite toutes les publications qui sont envoyées aux sujets auxquels ils se sont abonnés.

Voici les principaux éléments du modèle PUB/SUB :

  1. Éditeur (Publisher) : L'entité responsable de l'envoi de messages ou de publications à un sujet spécifique. Il ignore l'existence des abonnés et leurs identités. Les éditeurs peuvent publier des messages sur un ou plusieurs sujets.
  2. Abonné (Subscriber) : L'entité intéressée à recevoir des messages ou des publications sur des sujets spécifiques. Les abonnés s'abonnent à un ou plusieurs sujets pour indiquer leur intérêt. Ils recevront les messages publiés sur les sujets auxquels ils se sont abonnés.
  3. Sujet (Topic) : Un canal ou une catégorie logique auxquels les messages ou les publications sont associés. Les éditeurs envoient des messages à des sujets spécifiques, et les abonnés reçoivent les messages des sujets auxquels ils se sont abonnés. Les sujets permettent aux abonnés de filtrer les messages qu'ils reçoivent et de se concentrer sur ceux qui sont pertinents pour leurs intérêts.
  4. Message : Les informations envoyées par l'éditeur et reçues par l'abonné. Il peut s'agir de n'importe quelle structure de données ou charge utile (payload) qui a une signification pour les applications concernées.
  5. Courtier de messages (Message Broker) : Le composant intermédiaire responsable du routage des messages des éditeurs vers les abonnés. Il gère la distribution des messages en fonction des abonnements aux sujets des abonnés.

Dans un système PUB/SUB, les éditeurs et les abonnés fonctionnent de manière asynchrone. Les éditeurs peuvent envoyer des messages à tout moment sans savoir quels abonnés, le cas échéant, sont actuellement actifs. Les abonnés peuvent rejoindre ou quitter le système à tout moment sans affecter les éditeurs.

Le modèle de messagerie PUB/SUB est couramment utilisé dans divers scénarios tels que le streaming de données en temps réel, les architectures orientées événements, les applications de chat et les systèmes distribués où la dissociation des éditeurs et des abonnés est souhaitée.

PyZMQ, la liaison Python pour ZeroMQ, offre un moyen pratique de mettre en œuvre la communication PUB/SUB dans les applications Python.

Code

import zmq

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5555")

while True:
    topic = input("Enter topic: ")
    message = input("Enter message: ")
    socket.send_multipart([topic.encode(), message.encode()])
import zmq

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://localhost:5555")
socket.setsockopt_string(zmq.SUBSCRIBE, '')

while True:
    topic, message = socket.recv_multipart()
    print(f"Received: {topic.decode()} - {message.decode()}")

Push / Pull with Queue

graph TB

subgraph Pusher
  P((Pusher))
end

subgraph Device
  F[(Frontend)]
  B[(Backend)]
  D(Device)
end

subgraph Puller
  Q((Puller))
end

P -- Sends Messages --> F
F -- Forwards Messages --> B
B --> Q

English

This network configuration consists of three components: a Pusher, a Puller, and a Device. These components utilize PyZMQ (Python binding for ZeroMQ) to establish a messaging system based on the PUSH/PULL pattern.

  1. Pusher: The Pusher is a component responsible for sending messages to the network. In the provided code (pusher.py), a PUSH socket is created using PyZMQ. The socket is bound to the TCP address tcp://*:5555, allowing it to listen for incoming connections on port 5555. The Pusher repeatedly prompts the user for a message, and each message is sent through the socket using socket.send_string(message).
  2. Puller: The Puller is a component that receives messages from the network. In the provided code (puller.py), a PULL socket is created using PyZMQ. Similar to the Pusher, the socket is bound to the TCP address tcp://*:5556, enabling it to listen for incoming connections on port 5556. The Puller continuously waits to receive a message through the socket using socket.recv_string(). Once a message is received, it is printed to the console.
  3. Device: The Device acts as an intermediary component between the Pusher and Puller. It connects the Pusher's output to the Puller's input, allowing messages to flow from one component to another. In the provided code (device.py), the Device creates two ZMQ sockets: a PULL socket called "frontend" and a PUSH socket called "backend." The frontend socket is bound to tcp://*:5555, while the backend socket is bound to tcp://*:5556. The ZMQ device function (zmq.device) connects these two sockets, acting as a message queue. Any message received by the Device's frontend socket is automatically forwarded to the backend socket, which then delivers the message to the Puller.

By running the Pusher, Puller, and Device scripts simultaneously, you create a network configuration where the Pusher sends messages to the Device's frontend socket, and the Puller receives messages from the Device's backend socket. The Device acts as a message queue, ensuring the delivery of messages from the Pusher to the Puller. This configuration allows for decoupling the Pusher and Puller, enabling asynchronous message exchange.

Français

Cette configuration réseau comprend trois composants : un émetteur (Pusher), un récepteur (Puller) et un dispositif (Device). Ces composants utilisent PyZMQ (liaison Python pour ZeroMQ) pour établir un système de messagerie basé sur le modèle PUSH/PULL.

  1. Émetteur (Pusher) : L'émetteur est le composant chargé d'envoyer des messages sur le réseau. Dans le code fourni (pusher.py), un socket PUSH est créé en utilisant PyZMQ. Le socket est lié à l'adresse TCP tcp://*:5555, ce qui lui permet d'écouter les connexions entrantes sur le port 5555. L'émetteur demande continuellement à l'utilisateur de saisir un message, et chaque message est envoyé à travers le socket à l'aide de socket.send_string(message).
  2. Récepteur (Puller) : Le récepteur est le composant qui reçoit les messages du réseau. Dans le code fourni (puller.py), un socket PULL est créé en utilisant PyZMQ. De manière similaire à l'émetteur, le socket est lié à l'adresse TCP tcp://*:5556, ce qui lui permet d'écouter les connexions entrantes sur le port 5556. Le récepteur attend continuellement de recevoir un message à travers le socket en utilisant socket.recv_string(). Une fois qu'un message est reçu, il est affiché dans la console.
  3. Dispositif (Device) : Le dispositif agit comme un composant intermédiaire entre l'émetteur et le récepteur. Il connecte la sortie de l'émetteur à l'entrée du récepteur, permettant aux messages de circuler d'un composant à l'autre. Dans le code fourni (device.py), le dispositif crée deux sockets ZMQ : un socket PULL appelé "frontend" et un socket PUSH appelé "backend". Le socket frontend est lié à tcp://*:5555, tandis que le socket backend est lié à tcp://*:5556. La fonction ZMQ device (zmq.device) connecte ces deux sockets, agissant comme une file d'attente de messages. Tout message reçu par le socket frontend du dispositif est automatiquement transmis au socket backend, qui le transmet ensuite au récepteur.

En exécutant simultanément les scripts de l'émetteur, du récepteur et du dispositif, vous créez une configuration réseau où l'émetteur envoie des messages au socket frontend du dispositif, et le récepteur reçoit les messages à partir du socket backend du dispositif. Le dispositif agit comme une file d'attente de messages, assurant la livraison des messages de l'émetteur au récepteur. Cette configuration permet de découpler l'émetteur et le récepteur, permettant ainsi un échange de messages asynchrone.

Code

import zmq

context = zmq.Context()
socket = context.socket(zmq.PUSH)
socket.connect("tcp://*:5555")

while True:
    message = input("Enter message: ")
    socket.send_string(message)
import zmq

context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.connect("tcp://*:5556")

while True:
    message = socket.recv_string()
    print(f"Received: {message}")
import zmq

context = zmq.Context()
frontend = context.socket(zmq.PULL)
backend = context.socket(zmq.PUSH)
frontend.bind("tcp://*:5555")
backend.bind("tcp://*:5556")

zmq.device(zmq.QUEUE, frontend, backend)

Request / Reply

graph TD
  A[Requester] -->|Request| B((REQ Socket))
  B -->|Request| C((Server))
  C -->|Reply| B
  B -->|Reply| A

English

REQ/REPLY (Request/Reply) is a messaging pattern commonly used in distributed systems to enable synchronous communication between a client (requester) and a server (responder). It establishes a simple one-to-one communication pattern where the client sends a request, and the server responds with a reply.

In the REQ/REPLY pattern:

  1. Requester (REQ): The requester is the client component that sends requests to the server. It creates a REQ socket using PyZMQ and connects to the server's address using `socket.connect()`. The requester can send multiple requests sequentially by using `socket.send_string()` to send the request message.
  2. Responder (REP): The responder is the server component that receives requests from the client and provides replies. It creates a REP socket using PyZMQ and binds it to a specific address using `socket.bind()`. The responder uses `socket.recv_string()` to receive the request message. Once a request is received, it processes the request and formulates a reply. The reply is then sent back to the requester using `socket.send_string()`.

The REQ/REPLY pattern follows a synchronous request-response model. The client (requester) sends a request and waits for a corresponding reply from the server (responder) before proceeding. It ensures that each request is paired with a specific reply, enabling reliable and sequential communication.

The pattern is useful in scenarios where the client requires a specific response from the server, such as in client-server architectures, RPC (Remote Procedure Call), or synchronous distributed systems.

It's important to note that the REQ/REPLY pattern is a blocking pattern, meaning the requester will be blocked until it receives a reply. If multiple requests are sent without waiting for replies, the server will process them sequentially, and the requester will wait for each reply before sending the next request.

PyZMQ provides an easy way to implement the REQ/REPLY pattern in Python applications, allowing for synchronous request-response communication between clients and servers.

Français

REQ/REPLY (Requête/Réponse) est un modèle de messagerie couramment utilisé dans les systèmes distribués pour permettre une communication synchrone entre un client (demandeur) et un serveur (répondeur). Il établit un modèle de communication simple un-à-un où le client envoie une requête et le serveur répond avec une réponse.

Dans le modèle REQ/REPLY :

  1. Demandeur (REQ) : Le demandeur est le composant client qui envoie des requêtes au serveur. Il crée un socket REQ en utilisant PyZMQ et se connecte à l'adresse du serveur en utilisant `socket.connect()`. Le demandeur peut envoyer plusieurs requêtes de manière séquentielle en utilisant `socket.send_string()` pour envoyer le message de requête.
  2. Répondeur (REP) : Le répondeur est le composant serveur qui reçoit les requêtes du client et fournit des réponses. Il crée un socket REP en utilisant PyZMQ et le lie à une adresse spécifique en utilisant `socket.bind()`. Le répondeur utilise `socket.recv_string()` pour recevoir le message de requête. Une fois une requête reçue, elle est traitée et une réponse est formulée. La réponse est ensuite renvoyée au demandeur en utilisant `socket.send_string()`.

Le modèle REQ/REPLY suit un modèle de demande-réponse synchrone. Le client (demandeur) envoie une requête et attend une réponse correspondante du serveur (répondeur) avant de continuer. Cela garantit que chaque requête est associée à une réponse spécifique, permettant une communication fiable et séquentielle.

Ce modèle est utile dans les scénarios où le client nécessite une réponse spécifique du serveur, comme dans les architectures client-serveur, les appels de procédure à distance (RPC) ou les systèmes distribués synchrones.

Il est important de noter que le modèle REQ/REPLY est un modèle bloquant, ce qui signifie que le demandeur sera bloqué jusqu'à ce qu'il reçoive une réponse. Si plusieurs requêtes sont envoyées sans attendre de réponses, le serveur les traitera séquentiellement et le demandeur attendra chaque réponse avant d'envoyer la requête suivante.

PyZMQ offre une façon simple de mettre en œuvre le modèle REQ/REPLY dans les applications Python, permettant une communication de demande-réponse synchrone entre les clients et les serveurs.

Code

import zmq

context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:5555")

while True:
    message = input("Enter message: ")
    socket.send_string(message)

    reply = socket.recv_string()
    print(f"Received reply: {reply}")
import zmq

context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")

while True:
    message = socket.recv_string()
    print(f"Received request: {message}")

    reply_message = input("Enter reply: ")
    socket.send_string(reply_message)

Fundamentals of HTTP 1.1 protocol

The Spec

Request / Response objects

English

Sure! I'll explain the basics of the HTTP 1.1 specification in a beginner-friendly manner.

HTTP stands for Hypertext Transfer Protocol, and it is the foundation of communication on the World Wide Web. HTTP 1.1 is a version of this protocol that introduced several improvements over its predecessor, HTTP 1.0. Here are some key concepts:

  1. Client-Server Communication: HTTP 1.1 follows a client-server model. The client (usually a web browser) sends a request to a server, and the server responds with a corresponding response.
  2. Request Structure: A client request consists of several components:

    • Method: It represents the action the client wants to perform on the server, such as GET, POST, PUT, DELETE, etc.
    • URL/URI: It specifies the location of the resource on the server that the client wants to interact with.
    • Headers: They provide additional information about the request, such as the type of content the client can accept, caching directives, authentication credentials, and more.
    • Body: It contains optional data that the client can send to the server, often used with POST or PUT requests to submit form data or update a resource.
  3. Response Structure: A server response also consists of several components:

    • Status Code: It indicates the outcome of the request, such as success (200), redirection (3xx), client error (4xx), or server error (5xx).
    • Headers: They provide additional information about the response, such as the content type, length, caching directives, and more.
    • Body: It contains the actual data or resource requested by the client, such as an HTML page, an image, or JSON data.
  4. Persistent Connections: HTTP 1.1 introduced the concept of persistent connections, where multiple requests and responses can be sent over a single connection. This helps reduce the overhead of establishing a new connection for each request, improving performance.
  5. Caching: HTTP 1.1 added enhanced caching mechanisms to allow clients and intermediaries (like proxy servers) to store and reuse previously fetched resources. This reduces the need to fetch the same resource repeatedly, resulting in improved efficiency.
  6. Content Negotiation: HTTP 1.1 provides the ability for clients and servers to negotiate the best representation of a resource based on the client's capabilities and preferences. This allows for delivering content in different languages or formats based on the client's needs.
  7. Security: Although HTTP 1.1 does not provide inherent encryption, it can be used in conjunction with secure protocols like HTTPS to establish encrypted connections and ensure data confidentiality and integrity.

These are some of the fundamental aspects of HTTP 1.1. It is a protocol that enables communication between clients (such as web browsers) and servers, allowing the exchange of resources, data, and information over the internet.

Remember, this is just a simplified overview, and there is much more to learn about HTTP and its intricacies. However, I hope this provides you with a basic understanding of HTTP 1.1 and its role in web communication.

Français

Bien sûr ! Voici une explication simplifiée des bases de la spécification HTTP 1.1 adaptée à un public débutant peu technique :

HTTP signifie Hypertext Transfer Protocol (protocole de transfert hypertexte), et il constitue le fondement des communications sur le World Wide Web. HTTP 1.1 est une version de ce protocole qui a introduit plusieurs améliorations par rapport à son prédécesseur, HTTP 1.0. Voici quelques concepts clés :

  1. Communication Client-Serveur : HTTP 1.1 suit un modèle client-serveur. Le client (généralement un navigateur Web) envoie une requête à un serveur, et le serveur répond avec une réponse correspondante.
  2. Structure de la requête : Une requête client est composée de plusieurs éléments :

    • Méthode : Elle représente l'action que le client souhaite effectuer sur le serveur, telle que GET, POST, PUT, DELETE, etc.
    • URL/URI : Elle spécifie l'emplacement de la ressource sur le serveur avec laquelle le client souhaite interagir.
    • En-têtes : Ils fournissent des informations supplémentaires sur la requête, telles que le type de contenu que le client peut accepter, les directives de mise en cache, les informations d'authentification, et plus encore.
    • Corps : Il contient des données facultatives que le client peut envoyer au serveur, souvent utilisées avec les requêtes POST ou PUT pour soumettre des données de formulaire ou mettre à jour une ressource.
  3. Structure de la réponse : Une réponse du serveur est également composée de plusieurs éléments :

    • Code d'état : Il indique le résultat de la requête, tel que succès (200), redirection (3xx), erreur du client (4xx) ou erreur du serveur (5xx).
    • En-têtes : Ils fournissent des informations supplémentaires sur la réponse, telles que le type de contenu, la longueur, les directives de mise en cache, et plus encore.
    • Corps : Il contient les données réelles ou la ressource demandée par le client, telles qu'une page HTML, une image ou des données JSON.
  4. Connexions persistantes : HTTP 1.1 a introduit le concept de connexions persistantes, où plusieurs requêtes et réponses peuvent être envoyées sur une seule connexion. Cela permet de réduire la surcharge de l'établissement d'une nouvelle connexion pour chaque requête, améliorant ainsi les performances.
  5. Mise en cache : HTTP 1.1 a ajouté des mécanismes de mise en cache améliorés pour permettre aux clients et aux intermédiaires (comme les serveurs proxy) de stocker et de réutiliser les ressources récupérées précédemment. Cela réduit la nécessité de récupérer la même ressource à plusieurs reprises, ce qui améliore l'efficacité.
  6. Négociation de contenu : HTTP 1.1 permet aux clients et aux serveurs de négocier la meilleure représentation d'une ressource en fonction des capacités et des préférences du client. Cela permet de fournir du contenu dans différentes langues ou formats en fonction des besoins du client.
  7. Sécurité : Bien que HTTP 1.1 ne fournisse pas de chiffrement intégré, il peut être utilisé en conjonction avec des protocoles sécurisés tels que HTTPS pour établir des connexions chiffrées et garantir la confidentialité et l'intégrité des données.

Ce sont là quelques aspects fondamentaux de HTTP 1.1. Il s'agit d'un protocole qui permet la communication entre les clients (comme les navigateurs Web) et les serveurs, facilitant l'échange de ressources, de données et d'informations sur Internet.

Gardez à l'esprit qu'il s'agit simplement d'un aperçu simplifié, et qu'il y a beaucoup plus à apprendre sur HTTP et ses subtilités. Toutefois, j'espère que cela vous donne une compréhension de base de HTTP 1.1 et de son rôle dans les communications Web.

N'hésitez pas à me faire part de vos questions supplémentaires !

Code

def parse_http_request(request):
    parsed_request = {}

    lines = request.split("\r\n")
    method, path, protocol = lines[0].split(" ")

    parsed_request["method"] = method
    parsed_request["path"] = path
    parsed_request["protocol"] = protocol

    headers_end_index = lines.index("")
    headers = lines[1:headers_end_index]

    parsed_request["headers"] = {}
    for header in headers:
        key, value = header.split(": ", 1)
        parsed_request["headers"][key] = value

    body = lines[headers_end_index + 1:]

    if parsed_request["method"] == "POST":
        body = body[-1]  # Only consider the last line for POST request body
        if "=" in body:
            data_pairs = body.split("&")
            parsed_request["data"] = {}
            for pair in data_pairs:
                key, value = pair.split("=")
                parsed_request["data"][key] = value

    return parsed_request


# Example tests

# GET request
get_request = """GET /path HTTP/1.1\r\nHost: fintech.com\r\nAccept: text/html\r\n\r\n"""
parsed_get_request = parse_http_request(get_request)
print(parsed_get_request["method"])  # Output: GET
print(parsed_get_request["path"])  # Output: /path
print(parsed_get_request["protocol"])  # Output: HTTP/1.1
print(parsed_get_request["headers"])  # Output: {'Host': 'example.com', 'Accept': 'text/html'}
print(parsed_get_request.get("data"))  # Output: None

# POST request
post_request = """POST /login HTTP/1.1\r\nHost: fintech.com\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nemail=blash@gmail.com&password=value2"""
parsed_post_request = parse_http_request(post_request)
print(parsed_post_request["method"])  # Output: POST
print(parsed_post_request["path"])  # Output: /submit
print(parsed_post_request["protocol"])  # Output: HTTP/1.1
print(parsed_post_request["headers"])  # Output: {'Host': 'example.com', 'Content-Type': 'application/x-www-form-urlencoded'}
print(parsed_post_request["data"])  # Output: {'key1': 'value1', 'key2': 'value2'}
class HTTPRequest:
    def __init__(self, method, path, protocol):
        self.method = method
        self.path = path
        self.protocol = protocol
        self.headers = {}
        self.data = {}


def parse_http_request(request):
    lines = request.split("\r\n")
    method, path, protocol = lines[0].split(" ")

    http_request = HTTPRequest(method, path, protocol)

    headers_end_index = lines.index("")
    headers = lines[1:headers_end_index]

    for header in headers:
        key, value = header.split(": ", 1)
        http_request.headers[key] = value

    body = lines[headers_end_index + 1:]

    if http_request.method == "POST":
        body = body[-1]  # Only consider the last line for POST request body
        if "=" in body:
            data_pairs = body.split("&")
            for pair in data_pairs:
                key, value = pair.split("=")
                http_request.data[key] = value

    return http_request


# Example tests

# GET request
get_request = """GET /path HTTP/1.1\r\nHost: example.com\r\nAccept: text/html\r\n\r\n"""
parsed_get_request = parse_http_request(get_request)
print(parsed_get_request.method)  # Output: GET
print(parsed_get_request.path)  # Output: /path
print(parsed_get_request.protocol)  # Output: HTTP/1.1
print(parsed_get_request.headers)  # Output: {'Host': 'example.com', 'Accept': 'text/html'}
print(parsed_get_request.data)  # Output: {}

# POST request
post_request = """POST /submit HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nkey1=value1&key2=value2"""
parsed_post_request = parse_http_request(post_request)
print(parsed_post_request.method)  # Output: POST
print(parsed_post_request.path)  # Output: /submit
print(parsed_post_request.protocol)  # Output: HTTP/1.1
print(parsed_post_request.headers)  # Output: {'Host': 'example.com', 'Content-Type': 'application/x-www-form-urlencoded'}
print(parsed_post_request.data)  # Output: {'key1': 'value1', 'key2': 'value2'}

APIs: Server and Client

FastAPI

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Msg(BaseModel):
    txt: str

@app.get("/hello")
def get_hello():
    return {"message": "Hello, GET request!"}

@app.post("/hello")
def post_hello(msg: Msg):
    print(f"Msg: {msg}")
    return {"message": f"Received: {msg.txt}"}
uvicorn server:app --reload
import requests

# GET request
get_response = requests.get("http://localhost:8000/hello")
print(get_response.json())  # Output: {'message': 'Hello, GET request!'}

# POST request
post_response = requests.post("http://localhost:8000/hello", json=dict(txt="Lambert"))
print(post_response.json())  # Output: {'message': 'Hello, POST request!'}

import requests
import json

url = "http://35.203.26.163/graphql"

payload = "{\"query\":\"query {\\n  pageByName(page: \\\"register-page\\\") {\\n    data {\\n      id\\n      attributes {\\n        name\\n        page\\n        title\\n        slug \\n        body {\\n        ... on ComponentFormRegisterForm {\\n          id\\n          form {\\n            data {\\n              id\\n              attributes {\\n                name\\n                title\\n                formHeaderText\\n                formEntry {\\n                  data {\\n                    attributes {\\n                      name\\n                      formFields {\\n                        id\\n                        name\\n                        type\\n                        value\\n                        defaultValue\\n                        isRequired\\n                        errorText\\n                        errorIcon {\\n                          data {\\n                            attributes {\\n                              name\\n                              alternativeText\\n                            }\\n                          }\\n                        }\\n                      }\\n                    }\\n                  }\\n                }\\n                formFooterText\\n                formErrorText\\n                showFormErrorsOnTop\\n                showFormAsPopup\\n                formErrorIcon {\\n                  data {\\n                    attributes {\\n                      name\\n                      alternativeText\\n                    }\\n                  }\\n                }\\n              }\\n            }            \\n          }\\n        }\\n        }\\n        subHeader {\\n          data {\\n            id\\n            attributes {\\n              subHeader {\\n                id\\n                title\\n                tabs {\\n                  id \\n                  name\\n                  url\\n                }\\n              }\\n            }\\n          }\\n        }      \\n      }\\n    }\\n  }\\n}\",\"variables\":{}}"
headers = {
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

Requests

APIs as Contracts

JSON Schema

Prompt LLMs by Contract