POST to external url with FastAPI - python

I've been trying to figure out how to properly do a POST with FastAPI.
I'm currently doing a POST with python's "requests module" and passing some json data as shown below:
import requests
from fastapi import FastAPI
json_data = {"user" : MrMinty, "pass" : "password"} #json data
endpoint = "https://www.testsite.com/api/account_name/?access_token=1234567890" #endpoint
print(requests.post(endpoint, json=json_data). content)
I don't understand how to do the same POST using just FastAPI's functions, and reading the response.

The module request is not a FastAPI function, but is everything you need,
first you need to have your FastAPI server running, either in your computer or in a external server that you have access.
Then you need to know the IP or domain of your server, and then you make the request:
import requests
some_info = {'info':'some_info'}
head = 'http://192.168.0.8:8000' #IP and port of your server
# maybe in your case the ip is the localhost
requests.post(f'{head}/send_some_info', data=json.dumps(tablea))
# "send_some_info" is the address of your function in fast api
Your FastApI script would look like this, and should be running while you make your request:
from fastapi import FastAPI
app = FastAPI()
#app.post("/send_some_info")
async def test_function(dict: dict[str, str]):
# do something
return 'Success'

Related

How to get user's IP address using Amazon API Gateway and FastAPI?

I am using Amazon API Gateway that forwards requests to a FastAPI server (I am not using nginx). I am trying to get the user's IP address in a FastAPI endpoint, but it does not seem to be working (x_forwarded_for is empty).
Here is a snippet of my FastAPI backend:
#app.post("/upload")
async def runUpload(file: UploadFile,request: Request, x_forwarded_for: str = Header(None)):
userIp = x_forwarded_for.split(',')[0].strip() if x_forwarded_for else request.client.host
This is how I start my server:
if __name__ == '__main__':
uvicorn.run(app,port=PORT,host='0.0.0.0',proxy_headers=True,forwarded_allow_ips="*")
when i print the request.headers i see (of course with correct numbers), so the data is here...
'forwarded': 'by=1.2.3.4;for=5.6.7.8;host=555.execute-api.us-east-1.amazonaws.com;proto=https'

How to retrieve query parameter from URL after RedirectResponse in FastAPI?

I'm implementing an oauth autorization code flow.
What I want is to retrieve the code that shows in the url after redirection. I've made researches but haven't found something really helpful. I think that if I can get the current url in the browser after the RedirectResponse, I can then extract the code parameter of it with python module like urllib.parse. Or, does FastApi have a way to help me get that url after the RedirectResponse? I saw on their documentation the Background Tasks but I don't know if that can actually help me retrieve the url after the redirection. I tried using selenium library after having seen this but it opens up a new window and when I try to apply the driver.get('put_your_site_name') suggested in the comments, it just takes too long.
Here's the code excerpt which is redirecting me to the url in the browser with the code as a parameter :
from uuid import uuid4
from oauthlib.oauth2 import WebApplicationClient
from fastapi import APIRouter, Request, Response
from fastapi.responses import RedirectResponse
router = APIRouter()
#router.get("/install/")
async def install(request: Request) -> Response:
"""Trigger the client identification process."""
client_id = "xxx"
client = WebApplicationClient(client_id)
state = str(uuid4())
authorization_url = f"https://api-url.com/auth/authorize?client_id={client_id}"
url = client.prepare_request_uri(
authorization_url,
redirect_uri="http://127.0.0.1:8000/callback/",
scope=["read:user"],
state=state,
)
return RedirectResponse(url=url)
With the above, I'm redirected to the callback url with the authorization code as parameter : http://127.0.0.1:8000/callback/?code=random-string-xyz.
I found also this which is quite close to what I'm looking for, except I'm trying to get the current path only after the redirection.
I've also checked FastApi query parameters part and tried with the following :
import typing
from uuid import uuid4
from oauthlib.oauth2 import WebApplicationClient
from fastapi import APIRouter, Request, Response
from fastapi.responses import RedirectResponse
router = APIRouter()
#router.get("/install/")
async def install(request: Request, code : typing.Optional[str] = None) -> Response:
"""Trigger the client identification process."""
client_id = "xxx"
client = WebApplicationClient(client_id)
state = str(uuid4())
authorization_url = f"https://api-url.com/auth/authorize?client_id={client_id}"
url = client.prepare_request_uri(
authorization_url,
redirect_uri="http://127.0.0.1:8000/callback/",
scope=["read:user"],
state=state,
)
print("\n code : ", code, "\n")
return RedirectResponse(url=url)
Output : code : None, as the code is returned after the redirection I guess?
How do I get that url programmatically to retrieve then the code? Or maybe do you have any other way to get it ..?
You should instead retrieve the value of the code parameter inside the /callback, not /install, endpoint, since that is the endpoint to which you are being redirected—according to the link provided in your question:
http://127.0.0.1:8000/callback/?code=random-string-xyz
^^^^^^^^^
In FastAPI, you can get query parameters by declaring the parameters in your endpoint. As per the documentation :
When you declare other function parameters that are not part of the
path parameters, they are automatically interpreted as "query"
parameters.
Example:
#router.get("/callback")
async def install(code : str = None):
# ...
Alternatively, you can use Starlette's Request object directly (see Starlette's documentation as well), as described in this answer, as well as here and here.

Propagate top-level span ID's in OpenTelemetry

I'm trying to get OpenTelemetry tracing working with FastAPI and Requests. Currently, my setup looks like this:
import requests
from opentelemetry.baggage.propagation import W3CBaggagePropagator
from opentelemetry.propagators.composite import CompositePropagator
from fastapi import FastAPI
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.b3 import B3MultiFormat
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
set_global_textmap(CompositePropagator([B3MultiFormat(), TraceContextTextMapPropagator(), W3CBaggagePropagator()]))
app = FastAPI()
FastAPIInstrumentor.instrument_app(app)
RequestsInstrumentor().instrument()
#app.get("/")
async def get_things():
r = requests.get("http://localhost:8081")
return {
"Hello": "world",
"result": r.json()
}
The / endpoint just does a GET to another service that looks basically like this one, just with some middleware to log the incoming headers.
If I send a request like this (httpie format),
http :8000 'x-b3-traceid: f8c83f4b5806299983da51de66d9a242' 'x-b3-spanid: ba24f165998dfd8f' 'x-b3-sampled: 1'
I expect that the downstream service, i.e. the one being requested by requests.get("http://localhost:8081"), to receive headers that look something like
{
"x-b3-traceid": "f8c83f4b5806299983da51de66d9a242",
"x-b3-spanid": "xxxxxxx", # some generated value from the upstream service
"x-b3-parentspanid": "ba24f165998dfd8f",
"x-b3-sampled": "1"
}
But what I'm getting is basically exactly what I sent to the upstream service:
{
"x-b3-traceid": "f8c83f4b5806299983da51de66d9a242",
"x-b3-spanid": "ba24f165998dfd8f",
"x-b3-sampled": "1"
}
I must be missing something obvious, but can't seem to figure out exactly what.
Sending a W3C traceparent header results in the same exact situation (just with traceparent in the headers that are received downstream). Any pointers would be appreciated.
EDIT - I'm not using any exporters, as in our environment, Istio is configured to export the traces. So we just care about the HTTP traces for now.
The B3MultiFormat propagator doesn't consider the parent span id field while serialising the context into HTTP headers since X-B3-ParentSpanId is an optional header https://github.com/openzipkin/b3-propagation#multiple-headers. You can expect the X-B3-TraceId and X-B3-SpanId to be always present but not the remaining ones.
Edit:
Are you setting the concrete tracer provider? It doesn't look like from the shared snippet but I don't know if you are actual application code. It's all no-op if you do not set the sdk tracer provider i.e no recording spans are created in FastAPI service. Please do the following.
...
from opentelemetry.trace import set_tracer_provider
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource
set_tracer_provider(TracerProvider(
resource=Resource.create({"serice.name": "my-service"})
))
...
Another edit:
OpenTelemetry does not store the parent span ID in the context https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#spancontext. The context propagation client libraries from OTEL are limited to serialise and pass on this info only. I don't think you can have the parentSpanId propagated.

HTTP endpoint that causes string to write to a file

the api should include one function called "write text to file" and inputs a string parameter
as for the function to write to the disk I have no problem and I implemented the code my problem is how to set the rest API using python.
EDIT:
this is my code:
from flask import (
Flask,
render_template
)
import SocketServer
import SimpleHTTPServer
import re
app = Flask(__name__, template_folder="templates")
#app.route('/index', methods=['GET'])
def index():
return 'Welcome'
#app.route('/write_text_to_file', methods=['POST'])
def write_text_to_file():
f = open("str.txt", "w+")
f.write("hello world")
f.close()
if __name__ == '__main__':
app.run(debug=True)
anyhow when I try to test my rest api:
http://127.0.0.1:5000/write_text_to_file
I am getting the following error:
Now I'm trying to test my rest-api , however how can I make my code to start the server and to the test the post request api, this is my test_class:
import requests
import unittest
API_ENDPOINT="http://127.0.0.1:5000/write_text_to_file"
class test_my_rest_api(unittest.TestCase):
def test_post_request(self):
"""start the server"""
r = requests.post(API_ENDPOINT)
res = r.text
print(res)
also when runnning my request using postman I am getting internal_server_error:
You're doing a GET request for this url, but you've specified that this endpoint can only accept POST:
#app.route('/write_text_to_file', methods=['POST'])
Also, the SocketServer and SimpleHTTPServer imports are not needed with Flask.
The method is not allowed because Chrome (or any browser) makes GET requests.
Whereas, you defined it as POST
#app.route('/write_text_to_file', methods=['POST'])
Either change it to a GET method, or use a tool such as POSTMan to perform other HTTP call types

accessing client's x509 certificate from within twisted web WSGI app

I have set up a twisted + flask https server that also does certificate-based client authentication by following the documentation at the Twisted site here. So far, so good.
In addition to authenticating the client using a certificate, the application code within the flask app needs the user name (which is present in the client x509 certificate) in order to do its job. I couldn't find an easy way to access this information. The information (based on the documentation) seems to be in the pyopenssl X509Name object at the time it does authentication, and I need the identity at the flask layer every time I process a request from that client.
The request object flask is getting did not seem to have this information (unless I read it wrong), so I assume I need to modify some options at the Twisted level to send them through to flask. I also need to somehow get them out of the OpenSSL layer.
How would you do this?
Updated: using HTTPChannel.allHeadersReceived instead of Protocol.dataReceived for support of chunked requests.
You can use HTTP headers to store connection information: set them up in HTTPChannel.allHeadersReceived method and retrieve from flask.request.headers, e.g.:
from twisted.application import internet, service
from twisted.internet import reactor
from twisted.web.http import HTTPChannel
from twisted.web.server import Site
from twisted.web.wsgi import WSGIResource
from flask import Flask, request
app = Flask('app')
#app.route('/')
def index():
return 'User ID: %s' % request.headers['X-User-Id']
class MyHTTPChannel(HTTPChannel):
def allHeadersReceived(self):
user_id = 'my_user_id'
req = self.requests[-1]
req.requestHeaders.addRawHeader('X-User-Id', user_id)
HTTPChannel.allHeadersReceived(self)
class MySite(Site):
protocol = MyHTTPChannel
application = service.Application('myapplication')
service = service.IServiceCollection(application)
http_resource = WSGIResource(reactor, reactor.getThreadPool(), app)
http_site = MySite(http_resource)
internet.TCPServer(8008, http_site).setServiceParent(service)
I'm not familiar with using client certificates in twisted. I assume you can retrieve its information in Protocol.transport.

Categories

Resources