Send JSON requests

On some occasions, I needed to send Python objects to another computer for processing. This could be simply storing and image, do object recognition on it with specific hardware, or offloading resources.

The most difficulty in trying to accomplish this, was inserting and extracting the object in the (web) request.

But I found a way to do this in a reliable and scalable way.

You can download the examples at GitHub using the link below.

https://github.com/krakkus/krakkus_python_examples/tree/main/send_json_requests

Client

This client sends two numbers to the server to have it multiplied, then the result is send back to the client.

import json
import random
import requests


# This is the location and port of the server
SERVER = '127.0.0.1'
PORT = 9000

# And this is the request url, including the path /multiply
# to indicate what we want from the server, usually this is 
# called an endpoint
URL = f'http://{SERVER}:{PORT}/multiply'

# This is a proxy function, it does not do the calculating
# itself, but sends it to the server to do is for us
def proxy_multiply(x, y):
    print(f'CLIENT: x = {x}, y = {y}')
    print(f'CLIENT: Make request to {URL}')

    # Take both parameters and store them in a single object
    data = {'x': x,
            'y': y}

    # This is an onliner that does three things, json.dumps 
    # takes the data object and turns in into a serialized
    # and binary string, this string is then attached to
    # the data part of the web request and then the actual
    # web request is made, the data received from the 
    # server is then stored in response
    response = requests.post(URL, data=json.dumps(data))
    
    # This line does the opposite, the data received in 
    # response.content is deserialized back into a Python 
    # object called reponse
    response = json.loads(response.content)

    print(f'CLIENT: z = {response}')

    return response


def main():
    # Make two random ints for the server to multiply
    x = random.randint(1, 10)
    y = random.randint(1, 10)

    # Call our proxy function
    z = proxy_multiply(x, y)


if __name__ == '__main__':
    main()

Server

The server listens for incoming connections. When a connection is made, it looks at the request’s path to determine which endpoint/function should handle the request. In this case, the function performs a multiplication on two numbers and sends the result back to the client.

import json

from http.server import SimpleHTTPRequestHandler
from urllib.parse import urlparse
from socketserver import TCPServer


# The port on which the server is listening
PORT = 9000


# This function does the actual calculation for the client
def multiply(x, y):
    print(f'SERVER: Multiplying {x} with {y}')
    z = x * y

    print(f'SERVER: Returning {z}')
    return x * y


# This is a pretty basis webserver
class GetHandler(SimpleHTTPRequestHandler):

    # A function to set the correct content type for json
    def _set_headers(self):
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.end_headers()

    # This part of the web server handles POST requests,
    # It needs to be POST because we are sending data to
    # the server
    def do_POST(self):
        u = urlparse(self.path)

        print(f'SERVER: Requested path = {u.path}')

        # The path part of the request determines the endpoint
        # being called. If it is our function, then we should
        # handle it
        if u.path == '/multiply':
            # This is a custom part to get the serialized and
            # binarry data out of the request
            content_len = int(self.headers.get('Content-Length'))
            post_body = self.rfile.read(content_len)

            # Reconstruct the object from the binary data
            data = json.loads(post_body)

            # Call the actual function with the requested
            # parameters to get a result
            result = multiply(data['x'],
                              data['y'])

            # The result object again is binary serialize,
            # and encoded in 'utf-8', this is only need for
            # sending data back to the client, but I do not
            # know why.
            result = json.dumps(result).encode('utf-8')

            # Set the proper headers for JSON format
            self._set_headers()

            # And send everything back to the client
            self.wfile.write(result)


def main():
    print(f'SERVER: Start listening on port {PORT}')

    # Our very basis webserver (handler) is used to create
    # and start an actual webserver
    httpd = TCPServer(("", PORT), GetHandler)
    httpd.serve_forever()


if __name__ == '__main__':
    main()

Leave a Reply

Your email address will not be published. Required fields are marked *