In a serverless.yml file, how does one customize the default http response template? - aws-lambda

Looking at the documentation here https://serverless.com/framework/docs/providers/aws/events/apigateway/#custom-response-templates, there doesn't seem to be much detail at all about setting up these templates.
I'm currently looking to remove the default application/json content-type, which is generated during creation of the handler's integration response (pictured below), and replace it with text/html. Is there defined syntax for how to do this hidden somewhere? Or is this level of customization not possible within the current scope of the framework?
Here is my endpoint as defined in serverless.yml
events:
- http:
method: any
path: /
integration: lambda
request:
region: ${env:AwsRegion}
response:
headers:
Content-Type: "'text/html'"
template: $input.path('body')
- http:
method: any
path: /{proxy+}
Which produces the following configuration on AWS Api Gateway:
I did try modifying the specification like this, as a guess, but it threw a syntax error:
template:
text\html: $input.path('body')

Make It Work
It looks like this isn't really supported by the framework, but it can be hacked together by (ab)using statusCodes in your serverless template.
Moving the response template to under a status code, and providing a pattern for that status code, accomplishes what I think you are after. The syntax:
- http:
method: any
path: /
integration: lambda
request:
region: us-east-1
response:
headers:
Content-Type: "'text/html'"
statusCodes:
200:
pattern: ""
template:
text/html: $input.path('body')
Note: Both the pattern and the template must be present.
Is It Really That Bad?
That's up to you, ultimately. I'm calling it a hack because:
It would be much nicer to be able to supply this at the response.template level, rather than at response.statusCodes.200.template.
Specifying one or more statusCodes removes the set of default lambda error regexes that you get when you don't specify any.
It feels like working around the fact that response.template will only accept a string, whereas it could (should?) accept a string or an object (like it does under statusCodes).
Fix It?
The offending code, from /lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js:
if (http.response.template) {
_.merge(integrationResponse.ResponseTemplates, {
'application/json': http.response.template,
});
}
if (config.template) {
const template =
typeof config.template === 'string'
? { 'application/json': config.template }
: config.template;
_.merge(integrationResponse.ResponseTemplates, template);
}
I think for this to work under response.template, the code in the first if() would need to behave more like the code in the second if().

Related

How do I create a HttpOrigin for Cloudfront to use a Lambda function url?

Trying to setup a Cloudfront behaviour to use a Lambda function url with code like this:
this.distribution = new Distribution(this, id + "Distro", {
comment: id + "Distro",
defaultBehavior: {
origin: new S3Origin(s3Site),
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
additionalBehaviors: {
[`api-prd-v2/*`]: {
compress: true,
originRequestPolicy: originRequestPolicy,
origin: new HttpOrigin(functionUrl.url, {
protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
originSslProtocols: [OriginSslPolicy.TLS_V1_2],
}),
allowedMethods: AllowedMethods.ALLOW_ALL,
viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY,
cachePolicy: apiCachePolicy,
},
The functionUrl object is created in a different stack and passed in to the cloudformation stack, the definition looks like:
this.functionUrl = new FunctionUrl(this, 'LambdaApiUrl', {
function: this.lambdaFunction,
authType: FunctionUrlAuthType.NONE,
cors: {
allowedOrigins: ["*"],
allowedMethods: [HttpMethod.GET, HttpMethod.POST],
allowCredentials: true,
maxAge: Duration.minutes(1)
}
});
The above code fails because "The parameter origin name cannot contain a colon".
Presumably, this is because functionUrl.url evaluates to something like https://xxx.lambda-url.ap-southeast-2.on.aws/ (note the https://) whereas the HttpOrigin parameter should just be the domain name like xxx.lambda-url.ap-southeast-2.on.aws.
I can't just write code to hack the url up though (i.e. functionUrl.url.replace("https://", "")) because when my code executes, the value or the url property is a token like ${Token[TOKEN.350]}.
The function url is working properly: if I hard-code the HttpOrigin to the function url's value (i.e. like xxx.lambda-url.ap-southeast-2.on.aws) - it works fine.
How do I use CDK code to setup the reference from Cloudfront to the function url?
I'm using aws-cdk version 2.21.1.
There is an open issue to add support: https://github.com/aws/aws-cdk/issues/20090
Use CloudFormation Intrinsic Functions to parse the url string:
cdk.Fn.select(2, cdk.Fn.split('/', functionUrl.url));
// -> 7w3ryzihloepxxxxxxxapzpagi0ojzwo.lambda-url.us-east-1.on.aws

Swagger Codegen Java RequestBody Name

I am using the swagger-codegen Spring generator to generate the API classes using a YAML API definition. I am describing post request with multiple parameters in the body like this:
paths:
/api/test:
post:
...
requestBody:
content:
application/json:
schema:
properties:
firstName:
type: string
lastName:
type: string
This generates a method in the ApiDelegate with the signature ResponseEntity test(Body body). However, when describing multiple post methods like this, the subsequent generated signatures have parameters of type Body1, Body2, ... . With Swagger 2.0, it was possible to name the RequestBodies. Can I do something similar with V3?
Thanks for your help!
Kind regards

Azure Form Recognizer training not finding data

I'm trying to train a Form Recognizer using the browser API console (https://eastus.dev.cognitive.microsoft.com/docs/services/form-recognizer-api/operations/TrainCustomModel/console). I've uploaded traning images to a container and created an SAS. The browser API console generate following HTTP request:
POST https://eastus.api.cognitive.microsoft.com/formrecognizer/v1.0-preview/custom/train?source=https://pythonimages.blob.core.windows.net/?sv=2019-02-02&ss=bfqt&srt=sco&sp=rl&se=2020-01-22T00:23:33Z&st=2020-01-21T16:23:33Z&spr=https&sig=••••••••••••••••••••••••••••••••&prefix=images HTTP/1.1
Host: eastus.api.cognitive.microsoft.com
Content-Type: application/json
Ocp-Apim-Subscription-Key: ••••••••••••••••••••••••••••••••
{
"source": "string",
"sourceFilter": {
"prefix": "string",
"includeSubFolders": true
}
}
However, the answer I get back is
Transfer-Encoding: chunked
x-envoy-upstream-service-time: 4
apim-request-id: 5ad37aa2-e251-4b61-98ae-023930b47d27
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
x-content-type-options: nosniff
Date: Tue, 21 Jan 2020 16:25:03 GMT
Content-Type: application/json; charset=utf-8
{
"error": {
"code": "1004",
"message": "Dataset path must be relative to local input mount path '/input' if local data is referenced."
}
}
I don't understand why it seems to be looking for data locally. I've experimented with the SAS, e.g. including the container name (images) in the blob http address rather than as a query parameter, but no success so far.
I've also tried the Python/REST path (described here: https://learn.microsoft.com/en-gb/azure/cognitive-services/form-recognizer/quickstarts/python-train-extract-v1), which results in a different error:
Response status code: 408
Response body: {'error': {'code': '1011', 'innerError': {'requestId': 'e7f9ef9f-97bc-4b6a-86f3-0b29c9591c87'}, 'message': 'The operation exceeded allowed time limit and was canceled. The common reasons are that the data source is too large or contains unsupported content. Please check that your request conforms to service limits and retry with redacted data source.'}}
For completeness, the code I use is as follows (key/signature *ed out:)
########### Python Form Recognizer Train #############
from requests import post as http_post
# Endpoint URL
base_url = r"https://markusformsrecognizer.cognitiveservices.azure.com/" + "/formrecognizer/v1.0-preview/custom"
source = r"https://pythonimages.blob.core.windows.net/images?sv=2019-02-02&ss=bfqt&srt=sco&sp=rl&se=2020-01-22T15:37:26Z&st=2020-01-22T07:37:26Z&spr=https&sig=*********************************"
headers = {
# Request headers
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': '*********************************'
}
url = base_url + "/train"
body = {"source": source}
try:
resp = http_post(url = url, json = body, headers = headers)
print("Response status code: %d" % resp.status_code)
print("Response body: %s" % resp.json())
except Exception as e:
print(str(e))
For error code 1004 Please follow the below to get the Source path containing the training documents and pass as value to the source key.
{
"source": "string",
"sourceFilter": {
"prefix": "string",
"includeSubFolders": true
}
}
Replace with the Azure Blob storage container's shared access signature (SAS) URL. To retrieve the SAS URL, open the Microsoft Azure Storage Explorer, right-click your container, and select Get shared access signature.
Make sure the Read and List permissions are checked, and click Create.
Then copy the value in the URL section. It should have the form:
https://.blob.core.windows.net/container name?SAS value.
Please use the new Form Recognizer v2.0 release it is an async API and enables training on large data sets and analyzing large documents. https://aka.ms/form-recognizer/api
quick start - https://learn.microsoft.com/en-us/azure/cognitive-services/form-recognizer/quickstarts/python-train-extract
To get started with Form Recognizer please login to the Azure Portal using this link to create a Form Recognizer resource (for v2.0 (preview) please use West US 2 or West Europe regions).
try removing the string value from prefix property.
{
"source": "string",
"sourceFilter": {
"prefix": "",
"includeSubFolders": true
}
}
The Python Quick Start code for version 2.0 seems to be working, at least I don’t get any errors anymore. I’m now feeling slightly silly that I didn’t try this earlier. The API (web-browser) console, linked from the Quick Start page of the Form Recognizer seems automatically assume I want to use version 1.0 and there’s no way to change that (or perhaps I’ve just overseen something). Hence I assumed I’d been allocated a v1.0 trial and therefore that’s what I used when I tried the Python Quick Start the first time around.
Instead of using just the SAS URI in the "source" of Request parameter on the API POST call, use the complete string of the container followed by the SAS URI token.
For ex:
https://.blob.core.windows.net//

Passing a path parameter to Google's Endpoint for Cloud Function

I am following Google's tutorial on setting up Google Cloud endpoint (not AWS API Gateway) in front of my Cloud Function. I am triggering my Cloud Function to trigger an AWS lambda function, AND I am trying to pass a path parameter from my Endpoint as defined by OpenAPI spec.
Path parameters are variable parts of a URL path. They are typically used to point to a specific resource within a collection, such as a user identified by ID. A URL can have several path parameters, each denoted with curly braces { }.
paths: /users/{id}:
get:
parameters:
- in: path
name: id # Note the name is the same as in the path
required: true
schema:
type: integer
GET /users/{id}
My openapi.yaml
swagger: '2.0'
info:
title: Cloud Endpoints + GCF
description: Sample API on Cloud Endpoints with a Google Cloud Functions backend
version: 1.0.0
host: HOST
x-google-endpoints:
- name: "HOST"
allowCors: "true
schemes:
- https
produces:
- application/json
paths:
/function1/{pathParameters}:
get:
operationId: function1
parameters:
- in: path
name: pathParameters
required: true
type: string
x-google-backend:
address: https://REGION-FUNCTIONS_PROJECT_ID.cloudfunctions.net/function1
responses:
'200':
description: A successful response
schema:
type: string
The error I get when I use Endpoint URL https://REGION-FUNCTIONS_PROJECT_ID.cloudfunctions.net/function1/conversations is a TypeError from my AWS lambda function
StatusCode:200, FunctionError: "Unhandled", ExecutedVersion: "$LATEST". Payload: "errorType":"TypeError", errorMessage:"Cannot read property 'startsWith' of undefined..."
It is saying that on line
var path = event.pathParameters;
...
...
if (path.startsWith('conversations/'){...};
my path var is undefined.
I initially thought my Google Function was not correctly passing pathParameters but when I tested my Google function using triggering event {"pathParameters":"conversations"}, my Lambda returns the payload successfully.
My Google Cloud Function:
let AWS = require('aws-sdk');
AWS.config.update({
accessKeyId: 'key',
secretAccessKey: 'secret',
region: 'region'
})
let lambda = new AWS.Lambda();
exports.helloWorld = async(req,res) => {
let params = {
FunctionName:'lambdafunction',
InvocationType: "RequestRespone",
Payload: JSON.stringify(req.body)
};
res.status(200).send(await lambda.invoke(params, function(err,data){
if(err){throw err}
else{
return data.Payload
}
}).promise());
}
EDIT 1:
Seeing this Google Group post, I tried adding to my openapi.yaml file path_translation: APPEND_PATH_TO_ADDRESS, yet still I'm having no luck.
...
paths:
/{pathParameters}:
get:
...
x-google-backend:
address: https://tomy.cloudfunctions.net/function-Name
path_translation: APPEND_PATH_TO_ADDRESS
#Arunmainthan Kamalanathan mentioned in the comments that testing in AWS and Google Cloud directly with trigger event {"pathParameters":"conversations"} is not equivalent to passing req.body from my Google function to AWS lambda. I think this is where my error is occurring -- I'm not correctly passing my path parameter in the payload.
EDIT 2:
There is this Stackoverflow post concerning passing route parameters to Cloud Functions using req.path. When I console.log(req.path) I get / and console.log(req.params) I get {'0': '' }, so for some reason my path parameter is not getting passed correctly from Cloud Endpoint URL to my Google function.
I was running into the same issue when specifying multiple paths/routes within my openapi.yaml. It all depends on where you place the x-google-backend (top-level versus operation-level). This has implications on the behaviour of the path_translation. You could also overwrite path_translation yourself, as the documentation clearly describes:
path_translation: [ APPEND_PATH_TO_ADDRESS | CONSTANT_ADDRESS ]
Optional. Sets the path translation strategy used by ESP when making
target backend requests.
Note: When x-google-backend is used at the top level of the OpenAPI
specification, path_translation defaults to APPEND_PATH_TO_ADDRESS,
and when x-google-backend is used at the operation level of the
OpenAPI specification, path_translation defaults to CONSTANT_ADDRESS.
For more details on path translation, please see the Understanding
path translation section.
This means that if you want the path to be appended as a path parameter instead of a query parameter in your backend, you should adhere to the following scenario's:
Scenario 1: Do you have one cloud function in the x-google-backend.address that handles all of your paths in the openapi specification? Put x-google-backend at the top-level.
Scenario 2: Do you have multiple cloud functions corresponding to different paths? Put x-google-backend at the operation-level and set x-google-backend.path_translation to APPEND_PATH_TO_ADDRESS.
When your invocation type is RequestRespone, you can access the payload directly from the event parameter of lambda.
Look at the `Payload' parameter of the Google function:
let params = {
FunctionName:'lambdafunction',
InvocationType: "RequestRespone",
Payload: JSON.stringify({ name: 'Arun'})
};
res.status(200).send(await lambda.invoke(params)...)
Also the Lambda part:
exports.handler = function(event, context) {
context.succeed('Hello ' + event.name);
};
I hope this helps.

savon-multipart - How to obtain attachments

I am using savon-multipart https://github.com/savonrb/savon-multipart to request a SOAP multipart response with an attachment (PDF). So far, this is my code:
require "savon-multipart"
client = Savon.client(
wsdl: "http://something.de?wsdl",
wsse_auth: [username: "uu", password: "??"]
)
reponse = client.call(:get_report, message: {
pdfId: 1
})
response.attachments
Authentication works fine. I can also fetch the XML-reponse. What I can't do is extract the attachment. There does not seem to exist a method for it.
According to savon-multipart's documentation
response.attachments
should contain the attachment(s). Unfortunately ruby tells me that this method is not defined.
I could't find an example implementation of savon-multipart so I'm coming to you guys :) Hope you can help me.
We had this same problem in some code. I hope this saves someone else some time in finding the solution.
When using savon-multipart, we had to add multipart: true to the parameters in call. When that parameter was added the response returned was of type Savon::Multipart::Response which has the attachments and parts methods.
reponse = client.call(:get_report, message: {
pdfId: 1
}, multipart: true)
Without that parameter, or with it set to false, the returned response is a Savon::Response object which does not have those methods.

Resources