FastAPI: Error by sending Request Body trough GET - python

I'm updating some APIs that I have coded using Python and FastAPI. In the actual version I'm sending the info using Query Paramenters, but now I want to try to send the data by using Request Body, but the code doesn't seem to work.
Below you can see a sample of the code on the Server Side where FastAPI should return the text I'm sending (for this example only the "string_to_show" info, but in the real project there would be more fields). I know that is a base code, but this is just a sample.
from fastapi import FastAPI, Path, Query
from pydantic import BaseModel
import uvicorn
app = FastAPI()
class req_body(BaseModel):
string_to_show:str
#app.get("/test/")
def scraper_shield(payload:req_body):
request_feedback = str(payload.string_to_show)
return request_feedback
if __name__ == "__main__":
uvicorn.run(app)
On the Client Side I'm using this code, that simply sends the payload to the server.
import requests
payload = {'string_to_show':'Python is great!'}
r = requests.get('http://127.0.0.1:8000/test/', params=payload)
print(r.text)
When sending the request, I should get the string "Python is Great!" but instead I'm getting some errors, below the Client and Server messages:
CLIENT: {"detail":[{"loc":["body"],"msg":"field required","type":"value_error.missing"}]}
SERVER: "GET /test/?string_to_show=Python+is+great%21 HTTP/1.1" 422 Unprocessable Entity

GET methods are not suppose to have body.
https://dropbox.tech/developers/limitations-of-the-get-method-in-http
POST method is meant for that.
If you really need some robust parametrization with GET (but I really would reconsider that), think about putting them into custom header(s).

You are sending the parameters as query strings when using
r = requests.get('http://127.0.0.1:8000/test/', params=payload). You have to instead use
r = requests.get('http://127.0.0.1:8000/test/', json=payload) which will create a request body and send it correctly. This what you also see from the errors, which basically tell you that the required body is missing.

Related

Can't access or print any request data with FastAPI

I have a simple FastAPI endpoint, where I want to receive a string value. In this case, I tried it with a JSON body, but basically it doesn't need to be JSON. I really need only a simple string to separate the requests from each other. Unfortunately, I can't access any of the request parameters with a GET method. I also tried POST method instead, but I get an error:
request:
url = "http://127.0.0.1:5000/ping/"
payload=json.dumps({"key":"test"})
headers = {
"Content-Type": "application/json"
}
response = requests.request("POST", url, headers=headers, json=payload)
print(response.text)
api:
#app.get("/ping/{key}")
async def get_trigger(key: Request):
key = key.json()
test = json.loads(key)
print(test)
test2 = await key.json()
print(key)
print(test2)
return
I can't print anything with post or put:
#app.post("/ping/{key}")
async def get_trigger(key: Request):
...
or
#app.put("/ping/{key}")
async def get_trigger(key: Request):
I get a 405 Method not allowed error.
How can I get this fixed?
The 405 Method Not Allowed status code indicates that "the server knows the request method, but the target resource doesn't support this method". You get this error when you attempt, for instance, to send a POST request to a GET route (as shown in your first example). This, however, is not the only issue with your code (on both client and server sides). Below is given an example on how to achieve what you described in the question using Path parameters. The same could be achieved using Query parameters, as well as Request Body. Please have a look at Python requests documentation on how to specify the parameters/body for each case. I would also highly suggest to take the FastAPI tutorial online—you'll find most of the answers you are looking for there.
app.py
from fastapi import FastAPI
app = FastAPI()
#app.get("/ping/{ping_id}")
async def get_trigger(ping_id: str):
return {"ping_id": ping_id}
test.py
import requests
url = 'http://127.0.0.1:8000/ping/test1'
resp = requests.get(url=url)
print(resp.json())

Modify json body with mitmproxy

I am trying to intercept and modify a graphql response's body. Here is my addon code:
from mitmproxy import ctx
from mitmproxy import http
import json
def response(flow: http.HTTPFlow) -> None:
if flow.request.pretty_url == "https://my.graphql/endpoint":
request_data = json.loads(flow.request.get_text())
if request_data["operationName"] == "MyOperationName":
data = json.loads(flow.response.get_text())
data["data"]["product"]["name"] = "New Name"
flow.response.text = json.dumps(data)
I can see the modified response in mitmproxy console. But the iOS simulator I am using is still getting the original response. Does anyone know how can I pass the modified response to the device?
From the documentation
def response(self, flow: mitmproxy.http.HTTPFlow):
"""
The full HTTP response has been read.
Note: If response streaming is active, this event fires after the entire body has been streamed.
HTTP trailers, if present, have not been transmitted to the client yet and can still be modified.
"""
ctx.log(f"response: {flow=}")
It appears that you might be streaming the response body which would mean that modifications would be ignored.
Consider using def request event hook instead

How to make a request inside a simple mitmproxy script?

Good day,
I am currently trying to figure out a way to make non blocking requests inside a simple script of mitmproxy, but the documentation doesn't seem to be clear for me for the first look.
I think it's probably the easiest if I show my current code and describe my issue below:
from copy import copy
from mitmproxy import http
def request(flow: http.HTTPFlow):
headers = copy(flow.request.headers)
headers.update({"Authorization": "<removed>", "Requested-URI": flow.request.pretty_url})
req = http.HTTPRequest(
first_line_format="origin_form",
scheme=flow.request.scheme,
port=443,
path="/",
http_version=flow.request.http_version,
content=flow.request.content,
host="my.api.xyz",
headers=headers,
method=flow.request.method
)
print(req.get_text())
flow.response = http.HTTPResponse.make(
200, req.content,
)
Basically I would like to intercept any HTTP(S) request done and make a non blocking request to an API endpoint at https://my.api.xyz/ which should take all original headers and return a png screenshot of the originally requested URL.
However the code above produces an empty content and the print returns nothing either.
My issue seems to be related to: mtmproxy http get request in script and Resubmitting a request from a response in mitmproxy but I still couldn't figure out a proper way of sending requests inside mitmproxy.
The following piece of code probably does what you are looking for:
from copy import copy
from mitmproxy import http
from mitmproxy import ctx
from mitmproxy.addons import clientplayback
def request(flow: http.HTTPFlow):
ctx.log.info("Inside request")
if hasattr(flow.request, 'is_custom'):
return
headers = copy(flow.request.headers)
headers.update({"Authorization": "<removed>", "Requested-URI": flow.request.pretty_url})
req = http.HTTPRequest(
first_line_format="origin_form",
scheme='http',
port=8000,
path="/",
http_version=flow.request.http_version,
content=flow.request.content,
host="localhost",
headers=headers,
method=flow.request.method
)
req.is_custom = True
playback = ctx.master.addons.get('clientplayback')
f = flow.copy()
f.request = req
playback.start_replay([f])
It uses the clientplayback addon in order to send out the request. When this new request is sent, that will generate another request event which will then be an infinite loop. That is the reason for the is_custom attribute I added to the request there. If the request that generated this event is the one that we have created, then we don't want to create a new request from it.

Responding to an http request with JSON in Python

I have several Python scripts that are used from the CLI. Now I have been asked if I could provide a web API to perform some of the work normally done from the CLI. I would like to respond with JSON formatted data. I know little about JSON or API's.
How do I retrieve the query data from the http request?
How to a create the HTTP headers for the response from my script?
Given the following URL, how can I respond with a JSON reply of the name "Steve"?
http://myserver.com/api.py?query=who
I already have a functioning Apache web server running, and can serve up HTML and CGI scripts. It's simply coding the Python script that I need some help with. I would prefer not to use a framework, like Flask.
A simple code example would be appreciated.
The following is the Python code that I've come up with, but I know it's not the correct way to do this, and it doesn't use JSON:
#!/usr/local/bin/python3.7
import cgi # CGI module
# Set page headers and start page
print("Content-type: text/plain")
print("X-Content-Type-Options: nosniff\x0d\x0a\x0d\x0a")
# Form defaults
query_arg = None
# Get form values, if any
form = cgi.FieldStorage()
# Get the rest of the values if the form was submitted
if "query" in form.keys():
query_arg = form["query"].value
if query_arg == "who":
print("Steve", end="", flush=True)
You are trying to build a request handler with core python code. which is able to handle http request, In my opinion its not good idea as there are multiple securty scenarios attached with it, also in cross server request its bit difficult to handle all request and request scenarios . I will suggest to use Flask which is very lightweight and this will give an pre-setup of routing mechanism to handle all kind of request in very less code below is the snippet to generate http json response hope it helps
import sys
import flask
import random, string
from flask import jsonify
class Utils:
def make_response(self):
response = jsonify({
'message': 'success',
})
response.status_code = 200
return response

bottle request.json getting a 405 on post

I'm trying to get bottle to receive json in an xmlhttprequest and I'm getting a 405 error
Part of my bottle script:
#app.route('/myroute/')
def myroute():
print request.json
Part of my other script to test the xhr out:
jdata = json.dumps({"foo":"bar"})
urllib2.urlopen("http://location/app/myroute/", jdata)
Why am I getting a 405?
bottlepy error: 127.0.0.1 - - [2012-09-23 23:09:34] "POST /myroute/ HTTP/1.0" 405 911 0.005458
urllib2 error: urllib2.HTTPError: HTTP Error 405: Method Not Allowed
I also tried variations of:
#app.route('/myroute/json:json#[1-9]+#')
def myroute(json):
request.content_type = 'application/json'
print request.json, json
Returning json does not seem to be an issue
I think the problem is the server does not allow POST requests. You can probably try sending it in a GET request instead:
urllib2.urlopen("http://location/app/myroute/?" + jdata)
UPDATE:
I just realized, after looking at your question again, that you are actually trying to send JSON data via GET request. You should in general avoid sending JSONs with GET requests, but use POST requests instead[Reference].
To send a POST request to Bottle, you also need to set the headers to application/json:
headers = {}
headers['Content-Type'] = 'application/json'
jdata = json.dumps({"foo":"bar"})
urllib2.urlopen("http://location/app/myroute/", jdata, headers)
Then, with the help of #Anton's answer, you can access the JSON data in your view like this:
#app.post('/myroute/')
def myroute():
print request.json
Also, as a bonus, to send a normal GET request and access it:
# send GET request
urllib2.urlopen("http://location/app/myroute/?myvar=" + "test")
# access it
#app.route('/myroute/')
def myroute():
print request.GET['myvar'] # should print "test"
By default, the route decorator makes the decorated function handle only GET requests. You need to add a method argument to tell Bottle to handle POST requests instead. To do that, you need to change:
#app.route('/myroute/')
to:
#app.route('/myroute/', method='POST')
or a shorter version:
#app.post('/myroute/')

Categories

Resources