Return binary HTTP response from OpenWhisk (IBM Cloud Function) action - openwhisk

I want to return a binary file which resides in (IBM Cloud) ObjectStorage via HTTP using OpenWhisk in a IBM Cloud Function.
Is this possible? To me it seems OpenWhisk only supports JSON as result to an action.
This is the code I'm using (get_object_storage_file returns binary data):
import sys
from io import StringIO
import requests
import json
def get_object_storage_file(container, filename):
"""This functions returns a StringIO object containing
the file content from Bluemix Object Storage."""
url1 = ''.join(['https://identity.open.softlayer.com', '/v3/auth/tokens'])
data = {'auth': {'identity': {'methods': ['password'],
'password': {'user': {'name': 'member_1feaf9dc308e9d57b5fce8a2424e51cd3f04af17','domain': {'id': '4619da2fa8524beda11c89d2d1969c5b'},
'password': 'nviJ.XXXXXXX.aexT'}}}}}
headers1 = {'Content-Type': 'application/json'}
resp1 = requests.post(url=url1, data=json.dumps(data), headers=headers1)
resp1_body = resp1.json()
for e1 in resp1_body['token']['catalog']:
if(e1['type']=='object-store'):
for e2 in e1['endpoints']:
if(e2['interface']=='public'and e2['region']=='dallas'):
url2 = ''.join([e2['url'],'/', container, '/', filename])
s_subject_token = resp1.headers['x-subject-token']
headers2 = {'X-Auth-Token': s_subject_token, 'accept': 'application/json'}
resp2 = requests.get(url=url2, headers=headers2)
return StringIO(resp2.text)
def main(dict):
get_object_storage_file('data', 'raw.bin')
return {'greeting':'test'}

Is this about a web-action or a "normal" action?
In general, you can always return a Base64 encoded representation of your binary data, encoded in a JSON object. It is true, that a CloudFunctions action always needs to return a JSON object.
In your specific example, the following could work:
import base64
def main(dict):
binary = get_object_storage_file('data', 'raw.bin')
return {'data':base64.base64encode(binary)}
(untested pseudocode)

Related

Python AWS Lambda's event body returning string instead of my json object

Invocation code
import requests
import json
# Create a new resource
data_object = {'key1':'testing'}
response = requests.post('https://fakeurl.execute-api.us-east-1.amazonaws.com/default/My_Test_Event_Lambda', data=data_object)
print(response._content.decode())
Lambda Code
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps(event['body'])
}
The response I get from invocation is "key1=testing"
I don't care so much about the response but i want the lambda function to be able to handle my data passed as json not a string. Example: I want to be able to say event['body']['key1'] and have it return "testing"
Currently API gateways being used as lambda proxy.
The event['body'] you received is a string. You need to parse that from JSON into a dict with:
d = json.loads(event['body'])
Then you can return that dict as the body in your response, if you want, via:
return {
'statusCode': 200,
'body': json.dumps(d)
}
As it stands, you're just managing strings.

django rest framework running on apache2 not returning full response to client

I'm running an api on apache/2.4.29 built using django rest framework 3.10.3 on top of django 2.2.5 running on wsgi, whenever I try to return a json object for a POST request from a client, the POST method inside apiviews.py that describes success does not send the full data. I have tried on using curl, postman and insomina and the response is the same. On the log it shows that the returning Reponse object is fully sent.
This is the apiviews.py code:
from rest_framework import generics, status, viewsets, permissions
from rest_framework.response import Response
from rest_framework.views import APIView
from django.shortcuts import get_object_or_404, get_list_or_404
from django.contrib.auth import authenticate
import threading
import datetime
import json
from commons.models import SMSMessages
from commons.serializers import SMSMessagesSerializer
from notification.sender import sender
class SMSView(APIView):
def post(self, request):
sms_messages_serializer = SMSMessagesSerializer(
data={
"sms_number_to": request.data.get("sms_number_to"),
"sms_content": request.data.get("sms_content"),
"sending_user": request.auth.user_id,
}
)
permission_classes = (permissions.IsAuthenticated)
if sms_messages_serializer.is_valid():
data_to_send = {
"number": sms_messages_serializer.validated_data[
"sms_number_to"
],
"msg_text": sms_messages_serializer.validated_data[
"sms_content"
]
}
sms_object = sms_messages_serializer.save()
else:
print("invalid data - {0}\t{1}".format(sms_messages_serializer.errors, datetime.datetime.now()))
data_to_send = None
max_retry = 0
resp = Response()
while max_retry < 3:
max_retry += 1
status_flag, status_response = sender(data_to_send)
if not status_flag:
resp = Response(
data={
"status": "sms not sent"
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
content_type="application/json"
)
else:
# the update method defined in the SMSMessagesSerializer class
# needs an instance to work with.
sms_messages_serializer.update(
sms_object,
{
"delivery_status": True
}
)
resp = Response(
data={
"status": "sms successfully sent"
},
headers=status_response.headers,
status=status_response.status_code,
content_type="application/x-www-form-urlencoded"
)
print(resp.data)
return resp
else:
resp = Response(
data={
"error": "unable to send sms"
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
content_type="application/json"
)
print(resp.data)
return resp
when sms is successfully sent, on the log file it prints out:
[Wed Dec 23 14:19:29.789772 2020] [wsgi:error] [pid 27330] [remote xxx.xxx.xxx.xxx:xxxx] {'status': 'sms successfully sent.'}
but this is not what is delivered to the client, the client application receives:
{"status":"sms successfull
This is the sender module for more clarity -- it uses the python request library:
import requests
import time
import json
from rest_framework.response import Response
from rest_framework import status
base_url = "http://xxx.xxx.xxx.xxx:xxxx/"
def sender(sms_data):
"""
The actual function that accesses the server and sends the sms.
"""
sending_url = base_url + "api/sendsms/"
sending_headers = {"content-type": "application/x-www-form-urlencoded"}
response = requests.Response()
try:
response = requests.post(
sending_url,
data=sms_data,
headers=sending_headers,
timeout=(7, 14),
)
response.raise_for_status()
except Exception as e:
print("{}".format(e))
return False, response
else:
return True, response
with all the headers intact and this only occurs on the success response, it does not appear on the rest.
How do I make it send the full response to the client?
thank you
The headers set on successful response inform the wrong Content-Length and Content-Type.
Your response is currently built like so:
resp = Response(
data={
"status": "success"
},
headers=status_response.headers,
status=status_response.status_code,
content_type="application/form-data"
)
There you're forwarding headers of the response from the SMS service such as Content-Length, whereas the transferred content is {"status": "success"}.
When Content-Length is not provided in the built response, the framework computes it based on the transferred content.
One more thing to note is that the transferred content is JSON data and not form-data.
I recommend explicitly selecting which headers to send back to the requestor if you care about any specific headers in the SMS service response like so:
resp = Response(
data={
"status": "success"
},
headers={
"specific_header_name_passed_from_sms_service": status_response.headers["specific_header_name_passed_from_sms_service"],
#...
},
status=status_response.status_code,
content_type="application/json"
)
Otherwise if you don't care for any of the headers in the response from the SMS service, you can ignore passing headers option in the built response.

how to send JSON-encoded http-body in http-get method by http-request

Basically, I am using http-request-plugin to send http-request in jenkins-pipeline.
In this post, it is possible to send JSON-encoded http-body in http-get method. However, the http-body is empty in the server-side when running the below jenkins-pipeline script. Is it allowed to send JSON-data in http-body when using http-get method?
import groovy.json.JsonOutput
def reqBody = [
'key01': 'val01',
'key02': 'val02',
]
def resp = httpRequest(
url: '127.0.0.1:8000/api/service01',
httpMode: 'GET',
contentType: 'APPLICATION_JSON',
requestBody: JsonOutput.toJson(reqBody),
)
One possible solution is to refactor the script in the server-side to read parameters in http-post. After this, the http-body has the json-data.
import groovy.json.JsonOutput
def reqBody = [
'key01': 'val01',
'key02': 'val02',
]
def resp = httpRequest(
url: '127.0.0.1:8000/api/service01',
httpMode: 'POST',
contentType: 'APPLICATION_JSON',
requestBody: JsonOutput.toJson(reqBody),
)

Bing spell check api doesn't work - error code 404

I've followed through the guide written here
https://learn.microsoft.com/en-us/azure/cognitive-services/bing-spell-check/quickstarts/python, but I'm getting a 404 error code. This is the code from the guide:
import requests
import json
api_key = myke
example_text = "Hollo, wrld" # the text to be spell-checked
endpoint = "https://api.cognitive.microsoft.com/bing/v7.0/SpellCheck"
data = {'text': example_text}
params = {
'mkt':'en-us',
'mode':'spell'
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Ocp-Apim-Subscription-Key': api_key,
}
response = requests.post(endpoint, headers=headers, params=params, data=data)
json_response = response.json()
print(json.dumps(json_response, indent=4))
But when I create a resource, the endpoint I get is either https://api.bing.microsoft.com/ or https://spellcheck3.cognitiveservices.azure.com/ depending on the guide.
How do I correctly run this code?
The code as written in the guide doesn't seem to work; here's a solution I found.
search_url = "https://api.bing.microsoft.com/v7.0/spellcheck"
search_term = "wrld helath"
params = {
'mkt':'en-us',
'mode':'spell',
'text' : search_term
}
headers = {"Ocp-Apim-Subscription-Key": subscription_key}
response = requests.get(search_url, headers=headers, params=params)
response.raise_for_status()
search_results = response.json()

Malformed request from aiohttp.ClientSession().post() with multiple image files

I'm still relatively new to Python and my first time to use aiohttp so I'm hoping someone can help spot where my problem is.
I have a function that does the following:
retrieves from the JSON payload two base64 strings - base64Front and base64Back
decode them, save to "images" folder
send the Front.jpg and Back.jpg to an external API
this external API expects a multipart/form-data
imgDataF = base64.b64decode(base64FrontStr)
frontFilename = 'images/Front.jpg'
with open(frontFilename, 'wb') as frontImgFile:
frontImgFile.write(imgDataF)
imgDataB = base64.b64decode(base64BackStr)
backFilename = 'images/Back.jpg'
with open(backFilename, 'wb') as backImgFile:
backImgFile.write(imgDataB)
headers = {
'Content-Type': 'multipart/form-data',
'AccountAccessKey': 'some-access-key',
'SecretToken': 'some-secret-token'
}
url = 'https://external-api/2.0/AuthenticateDoc'
files = [('file', open('./images/Front.jpg', 'rb')),
('file', open('./images/Back.jpg', 'rb'))]
async with aiohttp.ClientSession() as session:
async with session.post(url, data=files, headers=headers) as resp:
print(resp.status)
print(await resp .json())
The response I'm getting is status code 400 with:
{'ErrorCode': 1040, 'ErrorMessage': 'Malformed/Invalid Request detected'}
If I call the url via Postman and send the two jpg files, I get status code 200.
Hope someone can help here.
Thanks in advance.
Try using FormData to construct your request. Remove the content type from header and use it in FormData field as below:
data = FormData()
data.add_field('file',
open('Front.jpg', 'rb'),
filename='Front.jpg',
content_type='multipart/form-data')
await session.post(url, data=data)
Reference: https://docs.aiohttp.org/en/stable/client_quickstart.html#post-a-multipart-encoded-file

Resources