FastAPI - Internal Server Error

I am encountering an error when trying to login on my FastAPI app:

(scratch-fastapi2) nyck33@lenovo-gtx1650:~/projects/scratch-debugger-backend$ python app/main.py ./python-flazk-server-id.json Python-Flazk-Service
asp2024scratch
Starting server on OpenZiti overlay
INFO:     Started server process [60209]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
2024-03-22 11:03:27,820:ERROR - Exception in callback BaseSelectorEventLoop._accept_connection(<function Ser...x7f0835a241f0>, <openziti.dec...27.0.0.1', 0)>, None, <Server socke...0.0.1', 0)>,)>, 2048, None)
handle: <Handle BaseSelectorEventLoop._accept_connection(<function Ser...x7f0835a241f0>, <openziti.dec...27.0.0.1', 0)>, None, <Server socke...0.0.1', 0)>,)>, 2048, None)>
Traceback (most recent call last):
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/asyncio/selector_events.py", line 159, in _accept_connection
    conn, addr = sock.accept()
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitisock.py", line 119, in accept
    fd, peer = zitilib.accept(self.fileno())
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitilib.py", line 276, in accept
    check_error(clt)
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitilib.py", line 219, in check_error
    raise Exception(err, msg)
Exception: (11, 'unexpected error')
2024-03-22 11:03:28,093:ERROR - Exception in callback BaseSelectorEventLoop._accept_connection(<function Ser...x7f0835a241f0>, <openziti.dec...27.0.0.1', 0)>, None, <Server socke...0.0.1', 0)>,)>, 2048, None)
handle: <Handle BaseSelectorEventLoop._accept_connection(<function Ser...x7f0835a241f0>, <openziti.dec...27.0.0.1', 0)>, None, <Server socke...0.0.1', 0)>,)>, 2048, None)>
Traceback (most recent call last):
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/asyncio/selector_events.py", line 159, in _accept_connection
    conn, addr = sock.accept()
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitisock.py", line 119, in accept
    fd, peer = zitilib.accept(self.fileno())
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitilib.py", line 276, in accept
    check_error(clt)
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitilib.py", line 219, in check_error
    raise Exception(err, msg)
Exception: (11, 'unexpected error')
INFO:      - "GET / HTTP/1.1" 200 OK
2024-03-22 11:03:46,157:ERROR - Exception in callback BaseSelectorEventLoop._accept_connection(<function Ser...x7f0835a241f0>, <openziti.dec...27.0.0.1', 0)>, None, <Server socke...0.0.1', 0)>,)>, 2048, None)
handle: <Handle BaseSelectorEventLoop._accept_connection(<function Ser...x7f0835a241f0>, <openziti.dec...27.0.0.1', 0)>, None, <Server socke...0.0.1', 0)>,)>, 2048, None)>
Traceback (most recent call last):
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/asyncio/selector_events.py", line 159, in _accept_connection
    conn, addr = sock.accept()
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitisock.py", line 119, in accept
    fd, peer = zitilib.accept(self.fileno())
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitilib.py", line 276, in accept
    check_error(clt)
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitilib.py", line 219, in check_error
    raise Exception(err, msg)
Exception: (11, 'unexpected error')
2024-03-22 11:03:46,526:ERROR - Exception in callback BaseSelectorEventLoop._accept_connection(<function Ser...x7f0835a241f0>, <openziti.dec...27.0.0.1', 0)>, None, <Server socke...0.0.1', 0)>,)>, 2048, None)
handle: <Handle BaseSelectorEventLoop._accept_connection(<function Ser...x7f0835a241f0>, <openziti.dec...27.0.0.1', 0)>, None, <Server socke...0.0.1', 0)>,)>, 2048, None)>
Traceback (most recent call last):
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/asyncio/selector_events.py", line 159, in _accept_connection
    conn, addr = sock.accept()
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitisock.py", line 119, in accept
    fd, peer = zitilib.accept(self.fileno())
    check_error(clt)
  File "/home/nyck33/miniconda3/envs/scratch-fastapi2/lib/python3.9/site-packages/openziti/zitilib.py", line 219, in check_error
    raise Exception(err, msg)
Exception: (11, 'unexpected error')
2024-03-22 11:03:46,529:ERROR - Exception during login: a bytes-like object is required, not 'tuple'
INFO:      - "POST /api/login HTTP/1.1" 500 Internal Server Error

I have the app running locally wired up through nfconsole.io and can access the login page from my smartphone with a Ziti identity but that error message is cryptic. Please tell me what is happening and how to fix it. @TheLumberjack, that occurs after I type in my login and password and press the button to submit (not a form).

Hi @nyck33 (moved to a new topic)

It looks like you're getting a 500 error back. That makes me think you're getting an HTTP request through to the other side. Does your server see an incoming connection? I'm not sure I have a full/complete picture here. Was it working before but stopped working?

That's what is being output in the terminal I'm running my fastapi server so I'm guessing it sees it. I can show you my app/main.py but I tried to add the openziti python sdk as I did for my toy example in the other thread:

from fastapi import FastAPI, HTTPException, Response, UploadFile, Form, Depends, Request
from fastapi.responses import JSONResponse
import json
import zipfile
import io
from dotenv import load_dotenv
import os
from supabase import create_client
from schemas import AuthDetailsLogin, AuthDetailsSignUp, DebugQuery, DebugResponse
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
import logging
from pathlib import Path
import openziti
import sys
import uvicorn

logger = logging.getLogger(__name__)
# Set the logging level to ERROR
logger.setLevel(logging.ERROR)

# Create a console handler and set its level to ERROR
c_handler = logging.StreamHandler()
c_handler.setLevel(logging.ERROR)

# Create a formatter and attach it to the handler
c_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
c_handler.setFormatter(c_format)

# Add the handler to the logger
logger.addHandler(c_handler)
# Load environment variables
load_dotenv()###

invite_code_check = os.getenv("INVITATION_CODE")
print(invite_code_check)

# Supabase configuration
url: str = os.getenv("SUPABASE_URL")
key: str = os.getenv("SUPABASE_ANON_KEY")

# Initialize FastAPI app and Supabase client
app = FastAPI(debug=True)
bind_opts = {}

static_files = StaticFiles(directory="frontend")#content_type='application/javascript')
frontend_dir = Path(__file__).parent.parent / "frontend"

app.mount("/frontend", StaticFiles(directory=frontend_dir), name="frontend")

@app.middleware("http")
async def log_requests(request: Request, call_next):
    logger.info(f"Request: {request.method} {request.url}")
    response = await call_next(request)
    return response

origins = [
    "http://localhost",
    "http://localhost:8601/signup",
    "http://localhost:8601/login",
    "http://localhost:8601/logout",
    'http://localhost:8601',
    "http://localhost:8601/",
    "http://localhost:5000",
    "http://localhost:8000",
    "https://localhost",
    "https://localhost:3000",
    "https://localhost:5000",
    "https://localhost:8601",
    "https://localhost:8601/signup",
    "https://localhost:8601/login",
    "https://localhost:8601/logout",
    "https://localhost:8601",
    "https://localhost:8601/",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["GET","POST","OPTIONS"],
    allow_headers=["*"],
)

supabase = create_client(url, key)

# Setup basic configuration for logging
logging.basicConfig(level=logging.ERROR)

@app.post("/api/signup")
async def signup(auth_details: AuthDetailsSignUp):
    # check invitation code first and if it's invalid or missing then return 400 and appropriate message
    if auth_details.invitation_code is None:
        # use logging.error don't raise exception and return JSONResponse with status_code=400 and appropriate message
        logging.error("Invitation code is required")
        return JSONResponse(status_code=400, content={"message": "Invitation code is required"})
    # Check if the invitation code is valid, loading the actual INVITATION_CODE from environment variables
    if auth_details.invitation_code != os.getenv("INVITATION_CODE"):
        logging.error("Invalid invitation code")
        return JSONResponse(status_code=400, content={"message": "Invalid invitation code"})
    try:
        result = supabase.auth.sign_up({
            "email": auth_details.email,
            "password": auth_details.password
        })
        if result.user:
            return JSONResponse(status_code=201, content={"message": "User created successfully"})
        else:
            # Log the error if result.error is present
            if result.error:
                logging.error(f"Signup error: {result.error.message}")
                raise HTTPException(status_code=500, detail=result.error.message)
            raise HTTPException(status_code=500, detail="Unexpected error during signup")
    except Exception as e:
        error_message = getattr(e, 'message', str(e))
        logging.error(f"Exception during signup: {error_message}")  # Log the error
        status_code = 400 if isinstance(e, HTTPException) else 500
        return JSONResponse(status_code=status_code, content={"message": error_message})

@app.post("/api/login")
async def login(auth_details: AuthDetailsLogin, response: Response):
    try:
        result = supabase.auth.sign_in_with_password({
            "email": auth_details.email,
            "password": auth_details.password
        })
        if result.session:
            response.set_cookie(key="jwt", value=result.session.access_token, httponly=True)
            return JSONResponse(status_code=200, content={"message": "Login successful"})
        else:
            # Log the error if result.error is present
            if result.error:
                logging.error(f"Login error: {result.error.message}")
                raise HTTPException(status_code=401, detail=result.error.message)
            raise HTTPException(status_code=401, detail="Invalid email or password")
    except Exception as e:
        error_message = getattr(e, 'message', str(e))
        logging.error(f"Exception during login: {error_message}")  # Log the error
        status_code = 400 if isinstance(e, HTTPException) else 500
        return JSONResponse(status_code=status_code, content={"message": error_message})

@app.post("/api/logout")
async def logout(response: Response):
    # This endpoint does not interact with Supabase directly, so no changes needed here
    response.delete_cookie(key="jwt")
    return JSONResponse(status_code=200, content={"message": "Logout successful"})

# Debug Endpoint
@app.post("/debug", response_model=DebugResponse)
async def debug(file: UploadFile, query: DebugQuery = Depends()):
    # Handle file upload and process as needed
    # [Remaining implementation of /debug endpoint]
    pass

# need catch all route for frontend so React Router can handle routing
@app.get("/{catchall:path}")
async def read_index(catchall: str):
    if catchall:
        full_path = frontend_dir / catchall
        if full_path.is_file():
            return FileResponse(full_path)
    return FileResponse(frontend_dir / "index.html")

def process_project_json_to_graph(project_data):
    # [Insert your logic here]
    pass

def interact_with_cloud_llm(graph, query):
    # [Insert your LLM interaction logic here]
    return "Processed response based on the graph and query"


# At the bottom of your app/main.py file

@openziti.zitify(bindings={':8000': bind_opts})
def runApp():
    print("Starting server on OpenZiti overlay")
    # FastAPI uses Uvicorn as the ASGI server, so we start it with Uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

if __name__ == '__main__':
    # python app/main.py ./python-flazk-server-id.json Python-Flazk-Service
    # Assuming the Ziti context and service name are passed as command-line arguments
    bind_opts['ztx'] = sys.argv[1]
    bind_opts['service'] = sys.argv[2]
    runApp()

None of the functions related to the endpoints were changed at all from my working version that does not have any of the OpenZiti sdk code.
I run it with

python app/main.py ./python-flazk-server-id.json Python-Flazk-Service

I apologize for the wall there.

@nyck33 thanks for the report and the sample code

I'm looking into it.

@ekoby could you tell me what you found?

I've also had similar trouble with FastAPI over in Using OpenZiti in distributed surveillance system.

The function at /login might be provided with the underlying ASGI interface tuple when zitified, maybe because openziti changes something in a way that FastAPI no longer extracts the recieve (Request) part directly, without the function explicitly defining it as parameter?
That's just my guess, but adding request: Request as a parameter for functions worked for me.

Could you try adding request as a parameter to your URL-path(?) functions?
i.e. something like

@app.post("/api/login")
async def login(request: Request, auth_details: AuthDetailsLogin, response: Response):

Disclaimer: I haven't run your code on my machine.

there is an issue with zitilib server-side sockets that I have on the backburner for now (behind some other work). Once that work is complete it should work much better with all frameworks.

2 Likes

I just saw this but will try it out.

what do you pass there as an argument for that request parameter?

I'm not sure how but the server is running now on a Ziti service

(zitipy) nyck33@lenovo-gtx1650:/mnt/d/TeachCoding/scratch-debugger-backend$ python app/main.py ./py-server-id.json juiceshop-service
Starting server on OpenZiti overlay
INFO:     Started server process [96998]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)

code at FastAPI server zitified · GitHub

server is running on WSL2, then have an identity registered to Ziti Edge Desktop on Windows and one to my Android phone.

bind policy:

dial policy:

My network diagram:

But I can't access http://js-cloud-service:18080/docs which should get me Swagger docs for a FastAPI app from my smartphone with the Ziti Edge running with identity activated.

Can anyone tell me where to start debugging?

I also tried switching out my main.py to the hello world example that I confirmed works in another post:

from fastapi import FastAPI
import uvicorn
import openziti
import sys

app = FastAPI()
bind_opts = {}  # populated in main

@openziti.zitify(bindings={':8000': bind_opts})
def runApp():
    print("starting server on OpenZiti overlay")
    # FastAPI uses Uvicorn as the ASGI server, so we start it with Uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

@app.get('/')
def hello_world():
    print("received a request to /")
    return 'Have some Ziti!'

if __name__ == '__main__':
    bind_opts['ztx'] = sys.argv[1]
    bind_opts['service'] = sys.argv[2]
    runApp()

but cannot reach http://js-cloud-service:18080 either to get a "Have some Ziti!" message.

Everything that needs to be online looks online.


i

I don't need a local ephemeral network in this case is that correct? Since I created the service on nfconsole.io and downloaded identities from there.