I'm trying to write a HTTP server that sends a HTTP request and returns the content to client.
Here is the code:
import asynchttpserver, asyncdispatch
import httpClient
let client = newHttpClient()
var server = newAsyncHttpServer()
proc cb(req: Request) {.async.} =
let content = client.getContent("http://google.com")
await req.respond(Http200, content)
waitFor server.serve(Port(8080), cb)
However, I obtain the following compile error message (nim v1.0.0):
Error: type mismatch: got <AsyncHttpServer, Port, proc (req: Request): Future[system.void]{.locks: <unknown>.}>
but expected one of:
proc serve(server: AsyncHttpServer; port: Port;
callback: proc (request: Request): Future[void] {.closure, gcsafe.};
address = ""): owned(Future[void])
first type mismatch at position: 3
required type for callback: proc (request: Request): Future[system.void]{.closure, gcsafe.}
but expression 'cb' is of type: proc (req: Request): Future[system.void]{.locks: <unknown>.}
This expression is not GC-safe. Annotate the proc with {.gcsafe.} to get extended error information.
expression: serve(server, Port(8080), cb)
The serve function expects another expression but do not know how to fix it.
Surprisingly, the code compiles perfectly fine when I remove the HTTP request from the server callback "cb". Does this mean that the serve function expects different callback expressions depending on the callback body ?
OK the problem is that the HttpClient is a global variable and is used in the callback function "cb". As a result the callback function is not GC safe.
So it is enough to instantiate the HttpClient within the callback function:
import asynchttpserver, asyncdispatch
import httpClient
var server = newAsyncHttpServer()
proc cb(req: Request) {.async.} =
let client = newHttpClient()
let content = client.getContent("https://google.com")
await req.respond(Http200, content)
waitFor server.serve(Port(8080), cb)
Related
I want to be able to create a custom WebSocket object rather than using Starlette's so that I can add some more things in the constructor and add some more methods. In FastAPI, you're able to subclass the APIRoute and pass in your own Request object. How would I do the same for the WebSocket router?
As you say, there doesn't seem to be an easy way to set the websocket route class (short of a lot of subclassing and rewriting). I think the simplest way to do this would be to define your own wrapper class around the websocket, taking whatever extra data you want, and then define the methods you need. Then you can inject that as a dependency, either with a separate function, or use the class itself as a dependency, see the documentation for details, which is what I'm doing below.
I've put together a minimal example, where the URL parameter name is passed to the wrapper class:
# main.py
from fastapi import Depends, FastAPI, WebSocket
app = FastAPI()
class WsWrapper:
def __init__(self, websocket: WebSocket, name: str) -> None:
self.name = name
self.websocket = websocket
# You can define all your custom logic here, I'm just adding a print
async def receive_json(self, mode: str = "text"):
print(f"Hello from {self.name}", flush=True)
return await self.websocket.receive_json(mode)
#app.websocket("/{name}")
async def websocket(ws: WsWrapper = Depends()):
await ws.websocket.accept()
while True:
data = await ws.receive_json()
print(data, flush=True)
You can test it by running uvicorn main:app and connecting to ws://localhost:8000/test, and it should print "Hello from test" when receiving JSON.
Ended up just monkeypatching the modules. Track this PR for when monkeypatching isn't necessary: https://github.com/tiangolo/fastapi/pull/4968
from typing import Callable
from fastapi import routing as fastapi_routing
from starlette._utils import is_async_callable
from starlette.concurrency import run_in_threadpool
from starlette.requests import Request as StarletteRequest
from starlette.websockets import WebSocket as StarletteWebSocket
from starlette.types import ASGIApp, Receive, Scope, Send
class Request(StarletteRequest):
pass
class WebSocket(StarletteWebSocket):
pass
def request_response(func: Callable) -> ASGIApp:
"""
Takes a function or coroutine `func(request) -> response`,
and returns an ASGI application.
"""
is_coroutine = is_async_callable(func)
async def app(scope: Scope, receive: Receive, send: Send) -> None:
request = Request(scope, receive=receive, send=send)
# Force all views to be a coroutine
response = await func(request)
if is_coroutine:
response = await func(request)
else:
response = await run_in_threadpool(func, request)
await response(scope, receive, send)
return app
fastapi_routing.request_response = request_response
def websocket_session(func: Callable) -> ASGIApp:
"""
Takes a coroutine `func(session)`, and returns an ASGI application.
"""
# assert asyncio.iscoroutinefunction(func), "WebSocket endpoints must be async"
async def app(scope: Scope, receive: Receive, send: Send) -> None:
session = WebSocket(scope, receive=receive, send=send)
await func(session)
return app
fastapi_routing.websocket_session = websocket_session
I am trying to turn some Julia code into a very basic local microservice, which accepts a POST request with some options supplied via JSON, runs a program, and returns a 200 response. My microservice code is here:
const ROUTERS = Dict()
function __init__()
"""Initialisation function to populate the global variables."""
ROUTERS["ROUTER"] = HTTP.Router()
HTTP.register!(ROUTERS["ROUTER"], "POST", "/run", run_program)
end
function run_program(req)
"""Takes a run request and passes it to the main program, before sending a 200 reponse to say no errors have occured."""
setup = JSON3.read(req.body, Dict{String, Any})
run_program(setup)
return HTTP.Response(200, JSON3.write("Success."))
end
function requestHandler(req)
"""Recieves incoming requests and passes them to the router. Returns the response object."""
local resp
resp = HTTP.handle(ROUTERS["ROUTER"], req)
return resp
end
function run_service(port=8080)
"""Runs the microservice at the specified port."""
HTTP.serve(requestHandler, "0.0.0.0", port)
end
__init__()
This code works with HTTP version 0.9.17, but I updated it to the new version 1.5.5. Now I receive this error whenever I make a request to the running service:
LogLevel(1999): handle_connection handler error
│ exception =
│ UndefVarError: handle not defined
What am I missing here? Have I defined my handler function incorrectly somehow?
There's no HTTP.handle function anymore in HTTP.jl version 1.x, as outlined by the documentation.
You'll probably want something like
"""
Receives incoming requests and passes them to the router. Returns the response object.
"""
function requestHandler(req)
return ROUTERS["ROUTER"](req)
end
instead. Also note that docstrings need to be inserted before the function for the docsystem to pick them up.
I have a callback in Flask+dash
server = Flask(__name__, static_folder='static')
app = dash.Dash(external_stylesheets=external_stylesheets, server=server)
thus:
#server.route("/Data/<symbol>")
def Data(symbol):
ib.qualifyContracts(symbol)
This gives a warning (in actuality it is an error):
RuntimeWarning:
coroutine 'IB.qualifyContractsAsync' was never awaited
However, if I put async in front of def soo I can await the function call (but not even inserting the await yet):
#server.route("/Data/<symbol>")
async def Data(symbol):
ib.qualifyContracts(symbol)
I get an exception
TypeError
TypeError: The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a coroutine.
How does one deal with dash callbacks that need to call other functions that need to be awaitable?
I want develop a web-socket watcher in python in such a way that when I send sth then it should wait until the response is received (sort of like blocking socket programming) I know it is weird, basically I want to make a command line python 3.6 tool that can communicate with the server WHILE KEEPING THE SAME CONNECTION LIVE for all the commands coming from user.
I can see that the below snippet is pretty typical using python 3.6.
import asyncio
import websockets
import json
import traceback
async def call_api(msg):
async with websockets.connect('wss://echo.websocket.org') as websocket:
await websocket.send(msg)
while websocket.open:
response = await websocket.recv()
return (response)
print(asyncio.get_event_loop().run_until_complete(call_api("test 1")))
print(asyncio.get_event_loop().run_until_complete(call_api("test 2")))
but this will creates a new ws connection for every command which defeats the purpose. One might say, you gotta use the async handler but I don't know how to synchronize the ws response with the user input from command prompt.
I am thinking if I could make the async coroutine (call_api) work like a generator where it has yield statement instead of return then I probably could do sth like beow:
async def call_api(msg):
async with websockets.connect('wss://echo.websocket.org') as websocket:
await websocket.send(msg)
while websocket.open:
response = await websocket.recv()
msg = yield (response)
generator = call_api("cmd1")
cmd = input(">>>")
while cmd != 'exit'
result = next(generator.send(cmd))
print(result)
cmd = input(">>>")
Please let me know your valuable comments.
Thank you
This can be achieved using an asynchronous generator (PEP 525).
Here is a working example:
import random
import asyncio
async def accumulate(x=0):
while True:
x += yield x
await asyncio.sleep(1)
async def main():
# Initialize
agen = accumulate()
await agen.asend(None)
# Accumulate random values
while True:
value = random.randrange(5)
print(await agen.asend(value))
asyncio.run(main())
I'm setting up a super simple http call to an endpoint on my server which returns a JSON response - an object with a success prop which is a boolean. Here is the relevant code:
getData : Model -> Cmd Msg
getData { userId, data } =
let
url =
"/get-data?userId=" ++ userId ++ "&data=" ++ data
request =
Http.get url decodeGetData
in
Http.send GetDataResult request
decodeGetData : Decode.Decoder Bool
decodeGetData =
Decode.at [ "success" ] Decode.bool
I'm getting the following error from the compiler:
Http.send GetDataResult request
^^^^^^^
Function `send` is expecting the 2nd argument to be:
Http.Request String
But it is:
Http.Request Bool
What's going wrong here? How do I set up Http.send to expect a Bool instead of a string? I know that the basic setup of my request is correct because my code compiles if I change the decodeGetData function to:
decodeGetData : Decode.Decoder String
decodeGetData =
Decode.at [ "success" ] Decode.string
In this case I can successfully make the http request, but then I get an error because the success prop on the response is a boolean instead of a string.
Any pointers? Cheers!
The code you pasted in all looks good, which leads me to think that the problem lies in a piece of code you don't have listed. Namely, the Msg constructor GetDataResult should have a single parameter of type Result Http.Error Bool. The compiler error you received would occur if the signature were instead Result Http.Error String.