pythonc#jsonrequestfastapi

How to send Base64 Image encoded as JSON from C# client to Python FastAPI server?


I've managed to merge two codes using FastAPI in Python. The challenge now is sending an image in Base64 format via JSON for interpretation. However, I'm encountering issues, as C# returns System.Net.WebException: 'The remote server returned an error: (500) Internal Server Error'.

Any ideas?

Here are my codes:
Python

import tensorflow as tf
from fastapi import FastAPI
import json
import base64
from PIL import Image
import io
#from flask import request
from fastapi import Request

app = FastAPI()

# Load the saved model
cnn = tf.keras.models.load_model('modelo_cnn.h5')

# Test functions to verify the connection
# @app.get('/prueba0/')
# def prueba0():
#     return "Hello, I'm connecting..."

# Test function to sum two numbers
@app.get('/prueba1/{a}/{b}')
def prueba1(a: int, b: int):
    return a + b

# Test function to display a message
@app.get('/prueba2/{text}')
def prueba2(text: str):
    return "Hello, your message was... " + text

#########################################################################################

# Overlap identification function
@app.post('/traslape/')
def traslape(request: Request):
    global cnn
    
    # Get data from the request body
    body = request.body()
        
    # Decode JSON data
    data = json.loads(body)
    
    # # # Open the JSON file (image)
    # with open(image) as f:
    #      img = json.load(f)
    
    # # Decode the image
    # image = base64.b64decode(img["image"])
    
    # # Open the image from bytes using Pillow
    # image = Image.open(io.BytesIO(image))
    
    # # Concatenate images horizontally
    # #imagen_completa = tf.concat([imagen_i, imagen_d], axis=1)
    
    # # Apply gamma correction to the image
    # gamma = tf.convert_to_tensor(0.6)
    # gamma_corrected = tf.pow(imagen / 255.0, gamma) * 255.0 # imagen_completa
    # image_bw = tf.cast(gamma_corrected, tf.uint8)
    
    # # Convert the image to grayscale
    # grayscale_image = tf.image.rgb_to_grayscale(image_bw)
    
    # # Define new dimensions
    # new_height = 360
    # new_width = 500

    # # Resize the image
    # imagen_completa_resize = tf.image.resize(grayscale_image, [new_height, new_width])
      
    # # Perform classification using the loaded model
    # result = cnn.predict(imagen_completa_resize)
     
    # if result[0][0] > result[0][1]:
    #     result = False # No mask
    # else:
    #     result = True # With mask

    return True

c#

using System;
using System.IO;
using System.Net;
using System.Text;

namespace comunica_api
{
    class Program
    {
        static void Main(string[] args)
        {
            // Path to the image in your local file system
            string imagePath = @"C:\Users\VirtualImages[00]20240418_124751_028.jpg";

            try
            {
                // Read the bytes of the image from the file
                byte[] imageBytes = File.ReadAllBytes(imagePath);

                // Convert the bytes to a Base64 formatted string
                string base64String = Convert.ToBase64String(imageBytes);

                // URL of the API
                string url = "http://localhost:8000/traslape/";

                // Data to send
                string json = "{\"image\": \"" + base64String + "\"}";

                // Create the HTTP request
                var request = (HttpWebRequest)WebRequest.Create(url);
                request.Method = "POST"; // Use the POST method 
                request.ContentType = "application/json"; // Set content type as JSON
                request.ContentLength = json.Length;

                // Convert JSON string to bytes
                byte[] jsonBytes = Encoding.UTF8.GetBytes(json);

                // Print the request content before sending it
                Console.WriteLine("Request:");
                Console.WriteLine("URL: " + url);
                Console.WriteLine("Method: " + request.Method);
                Console.WriteLine("Headers:");
                foreach (var header in request.Headers)
                {
                    Console.WriteLine(header.ToString());
                }
                Console.WriteLine("Body:");
                Console.WriteLine(json);


                // Write bytes into the request body using StreamWriter
                using (Stream requestStream = request.GetRequestStream())
                using (StreamWriter writer = new StreamWriter(requestStream))
                {
                    // Write JSON string into the request body
                    writer.Write(json);
                }

                // Send the request and get the response
                
                // HERE IS THE ERROR
                using (var response = (HttpWebResponse)request.GetResponse()) 
                //
                
                {
                    // Read the response from the server
                    using (var streamReader = new StreamReader(response.GetResponseStream()))
                    {
                        // Read the response as a string and display it in the console
                        string responseText = streamReader.ReadToEnd();
                        Console.WriteLine("API Response:");
                        Console.WriteLine(responseText);
                    }
                }
            }
            catch (FileNotFoundException)
            {
                Console.WriteLine("The specified image could not be found.");
            }
            catch (WebException ex)
            {
                // Handle any communication error with the API
                Console.WriteLine("API Communication Error:");
                Console.WriteLine(ex.Message);
            }

            // Wait for the user to press Enter before exiting the program
            Console.ReadLine();
        }
    }
}

Solution

  • The issue is with the way you are reading the request body on server side. The request.body() method returns a coroutine, and thus, it should be awaited, meaning that the endpoint should be defined with async def as well—see this answer for more details on def vs async def endpoints and how FastAPI handles them. Example:

    @app.post('/traslape/')
    async def traslape(request: Request):
        body = await request.body()
        data = json.loads(body)
        return "whatever"
    

    Note that since you are converting the body into JSON, you could directly do that using the following instead:

    data = await request.json()
    

    Retrieving the body on your own and then converting it into JSON is useful when you would like using other (maybe faster) JSON encoders, such as orjson, as demnonstrated in this answer, as well as this answer and this answer (you might find this helpful as well, when it comes to returning JSON data in FastAPI).

    If you would like having the endpoint defined with def instead and still be able to get the raw body of a POST request, solutions could be found in the following answer:

    Using FastAPI in a sync way, how can I get the raw body of a POST request?

    Related posts demonstrating how to upload/deal with base64 images in FastAPI could be found here, as well as here and here. Note that you don't necessarily need to send an image as a base64 string encoded as application/json or application/x-www-form-urlencoded in the request body; you could instead upload it encoded as multipart/form-data, as shown in this answer and the relevant included references in that answer.