WSBLib, make your own framework.

Learn how to use the WSBLib library with the complete documentation.

View the Project on GitHub firlast/wsblib

WSBLib - How to use

Learn how to create a web server using the WSBLib library.

Understanding the request cycle

First, the entire request cycle is handled by you. You manipulate the data sent by the client, process the request and send the response, all this using the WSBLib classes, which facilitate this whole cycle. A request cycle using WSBLib looks like this:

  1. Receive client data (specifically a raw HTTP message);
  2. Process client request;
  3. Get the requested route response;
  4. Send response to customer.

If you were to create this from scratch, without using WSBLib, you’d have to do all of this:

  1. Accept client connection;
  2. Receive client data (specifically a raw HTTP message);
  3. Format HTTP request data;
  4. Check which route was called;
  5. Check if the HTTP method is accepted by the route;
  6. Generate error responses if the route is not found or the method is not accepted
  7. Get the requested route response;
  8. Format HTTP response data;
  9. Send response to customer.

We’ve shortened all these steps to just 4, making it easy to develop your framework or web server.

Structure of a WSBLib server

Next, we will show the basic structure of a WSBLib server and explain the function of each of the classes, methods and others. Here’s the code for this simple server:

from wsblib.route import Route
from wsblib.server import Client, Server
from wsblib.request import ProcessRequest
from wsblib.request import RequestData

import http_pyparser


def index():
    return 'Hello World!'


index_route = Route(index, '/')
request = ProcessRequest((index_route,))

server = Server()
server.start('127.0.0.1', 2808)

print('Server running in http://127.0.0.1:2808')

while True:
    client = server.wait_client()
    processed_request = request.process(client)

    if processed_request:
        response = processed_request.get_response()
        http = http_pyparser.response.make_response(response)
        client.send_message(http)
        client.destroy()

This example server does not use threads, that is, it can only serve another request after the current request is completed. You can rearrange this code so that threads are used.

Creating routes

In the code snippet below, we create a route on the / path, which will call the index_route function to get a response and return it to the client:

def index():
    return 'Hello World!'

index_route = Route(index, '/')

This class is used to manipulate and obtain data from the route requested by the user, being able to check methods, obtain parameters (for dynamic routes) and others. To create an instance of this class, the required arguments are:

  1. callback: Function to be called when the route is requested (FunctionType);
  2. path: Route path (str);
  3. methods: Route allowed methods, the allowed method by default is “GET” (tuple, optional).

If we want, for example, to create a route that creates, reads, updates and deletes data (CRUD), the arguments passed would be like this:

profile_route = Route(profile, '/profile', methods=('GET', 'POST', 'PUT', 'DELETE'))

Dynamic routes

There is a special condition for the path argument. If you want to create a dynamic route, where you can access the data that was placed in the path, the path contains “variables” between “<” and “>” signs, see an example:

dynamic_route = Route(users, '/user/<username>')

For the users function to be called, the <username> parameter must be filled in, for example: /users/firlast. Otherwise, a 404 (Not Found) error will be returned to the client. You can access the dynamic route parameter later through the request data, getting the value of the parameter through its name, for example:

def users(request):
    return request.parameters.get('username')

Methods of the Route class

There are some methods of the Route class that can be accessed to check and get data. The methods are:

Preparing for processing requests

There is a class called ProcessRequest that processes the request and returns an object ready, with data from the request and the response of the requested route. To create an instance of this class, it is necessary to pass a tuple (or list) containing all the routes of the application, that is, all instances of the Route class, see:

def index():
    return 'Hello World!'

# create a route in "/" path
index_route = Route(index, '/')
# instantiating the class to process requests
request = ProcessRequest((index_route,))

In the example above, we are passing the index route (in the path “/”) as an argument to the ProcessRequest class inside a tuple.

The ProcessRequest class has a single public method, the process method, which processes a client request. This method will be responsible for verifying whether the requested route exists in the application and whether the requested method is accepted by the route.

Initializing the server

The Server class is responsible for manipulating the socket of the application, receiving connections from clients and manipulating this data. In the code snippet below, we create an instance of the Server class and use the Server.start method to start the server, passing the host and port to be used:

server = Server()
# listening for client connections
server.start('127.0.0.1', 2808)

With that, the host is ready to receive requests, but still won’t respond to any connection, because the server simply still doesn’t accept connections. To receive requests, the Server.wait_client method is used. This method will wait for a request, after receiving it, an instance of the Client class is returned, storing the client data and the raw HTTP message.

The Client class will very rarely need to be used by you, it will only be used by the ProcessRequest.process method to process the request.

Processing the request

We finally got to accepting connections and processing requests. See the code below, we will explain each detail below:

while True:
    client = server.wait_client()
    processed_request = request.process(client)

    if processed_request:
        response = processed_request.get_response()
        http = http_pyparser.response.make_response(response)
        client.send_message(http)
        client.destroy()

As you can see, the entire processing part of the request is within a while loop, that is, after a request is processed, the server can already receive a new request from another client.

  1. Accepting connections: in the code snippet below, we wait for a request using the Server.wait_client method. This method will return an instance of the Client class, which will be processed right after.
client = server.wait_client()
  1. Processing the client request: now, we use the instance of the ProcessRequest class and the process method, passing the instance of the Client class returned by the server.wait_client method. The process method will return an object called RequestProcessed, which contains data from the request and response to the client.
processed_request = request.process(client)
  1. Getting request response: the condition just checks if it’s a valid request, here we get the request response. We use the RequestProcessed.get_response method to obtain the response, either from an error (404 Not Found or 405 Method Not Allowed, for example) or the response from the route that was requested. This method returns a Response object, from the HTTPPyParser library, containing all the response data.
if processed_request:
    response = processed_request.get_response()
  1. Make HTTP response: We use the HTTPPyParser library to generate the response in raw HTTP. Just pass a Response object (also from the library) to the http_pyparser.response.make_response method, which returns a string.
    http = http_pyparser.response.make_response(response)
  1. Sending response to client: Finally, we send the HTTP message stored in the http variable using the send_message method of the Client class. After that, we use the destroy() method to close the connection with the client.
    client.send_message(http)
    client.destroy()

After these 05 steps, the request was processed and the response was sent to the client.