I was reading this blog post from Serverless about the different patterns that exist for Serverless architecture.
I'm interested in the services pattern and thought I'd try it out.
With this configuration in my serverless.yml file.
functions:
apps:
handler: handler.apps
events:
- http: post apps
cors: true
- http: patch users
- http: get users
cors: true
- http: delete users
The following output comes from running serverless deploy.
POST - https://x7lpwa04.execute-api.us-west-2.amazonaws.com/staging/users
PATCH - https://x7lpwa04.execute-api.us-west-2.amazonaws.com/staging/users
GET - https://x7lpwa04.execute-api.us-west-2.amazonaws.com/staging/users
DELETE - https://x7lpwa04.execute-api.us-west-2.amazonaws.com/staging/users
Now in a CRUD service if I wanted to get a single resource, I'd probably have something like this for a get endpoint, /staging/users/{id}. With the above pattern, is it up to the user to pass in a query string parameter like this /staging/users?id={id} instead of having a path parameter like this /staging/users/{id}? Is it possible to get the endpoint to have a path parameter?
It doesn't seem like the path can be overwritten this way.
You already can use path parameters, e.g.:
{
"myFunction": {
"handler": "src/myFunction/index.index",
"description": "Does awesome things",
"events": [
{
"http": {
"path": "apiPath/{parameterOne}",
"method": "GET",
"integration": "lambda",
"request": {
"parameters": {
"parameterOne": true
},
"template": {
"application/json": "{ \"parameterOne\": \"$input.params(\"parameterOne\")\" }"
}
},
"response": {
"statusCodes": {
"200": {
"pattern": ""
},
"400": {
"pattern": "[\\s\\S]*\\[400\\][\\s\\S]*",
"template": "$input.path('$.errorMessage')"
},
"500": {
"pattern": "[\\s\\S]*(Process\\s?exited\\s?before\\s?completing\\s?request|\\[500\\])[\\s\\S]*",
"template": "$input.path('$.errorMessage')"
}
},
"headers": {
"Cache-Control": "'no-cache, no-store'",
"Pragma": "'no-cache'",
"Expires": "'0'",
"Strict-Transport-Security": "'max-age=31536000; includeSubdomains; preload'"
}
},
"cors": {
"origin": "*",
"headers": [
"Content-Type",
"Pragma",
"Cache-Control",
"X-Amz-Date",
"Authorization",
"X-Api-Key",
"X-Amz-Security-Token",
"X-Amz-User-Agent"
],
"allowCredentials": false
}
}
}
]
}
}
The parameter parameterOne will be mapped into the Lambda event.
I might be interpreting this wrong but, at least in the case of the aws you are able to add several resources into a lambda. You will be responsible to handle the correct behavior, which you can do by parsing the event which will have filled the path parameters in case they exist.
as mentioned on the blog post
You can inspect the incoming HTTP request’s path and method by parsing the event body in your code, and then perform the correct operation in response. It’s like having a small router in the beginning of your Lambda code.
you can specific a more complete event with
- http:
path: users/{id}
method: delete
Related
I have a test helper function createUser that makes an HTTP request to the backend API to create a user. The backend API calls a method on Auth0 client to create a user. The responses I have are set for Auth0 client URL path /api/v2/users.
I am testing a service that has two types of users - an owner of the account and the invitee user. I am calling a helper twice before all the tests:
const owner = await createUser('owner#email.com');
const invitee = await createUser('invitee#email.com');
And I expect owner to have owner#email.com as an email and an invitee#email.com email for invitee .
In order to return two different responses I used Wiremock scenarios. And currently I have the responses like this:
User #1
{
"scenarioName": "user-scenario",
"requiredScenarioState": "Started",
"newScenarioState": "owner-user",
"request": {
"method": "POST",
"urlPattern": "/api/v2/users"
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"email": "owner#email.com"
}
}
}
User #2
{
"scenarioName": "user-scenario",
"requiredScenarioState": "owner-user",
"request": {
"method": "POST",
"urlPattern": "/api/v2/users"
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"email": "invitee#email.com"
}
}
}
When I run the tests Wiremock returns the second response for both createUser calls. The second response was created after the first and Wiremock prioritizes the most recently created response. I decided to set the priority: 1 on the first response to force Wiremock to return it and therefore change the scenario state. That worked only on the first test run but not for the subsequent ones.
Sometimes if I delete the Wiremock Docker image and start the container from scratch it returns the responses as expected but then continues returning the second response for both calls.
What am I doing wrong and if it's not how I set up the scenarios what could be the possible reasons for such inconsistency?
Multiple ways to skin this cat - third (response templating) is probably best.
Understanding Scenarios
The first request moves the scenario into state owner-user, and after that all requests will return user #2 for the lifetime of the WireMock instance - in your case, the docker container - unless it is reset.
You can reset it as so: PUT /__admin/scenarios/user-scenario/Started.
The state of a scenario is held in memory, so restarting the container should also reset the state to Started.
See WireMock | Stateful Behaviour | Resetting a single scenario
Using better matching to avoid scenarios
You may not need to use scenarios at all. You can use the request body of the POST to decide which stub to call, based on the email address you are sending.
Assuming your request payload looks like this:
{
"email": "owner#email.com"
}
You can match on the request body as so:
User #1
{
"request": {
"method": "POST",
"urlPattern": "/api/v2/users",
"bodyPatterns": [
{
"matchesJsonPath": {
"expression": "$.email",
"equalTo": "owner#email.com",
}
}
]
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"email": "owner#email.com"
}
}
}
User #2
{
"request": {
"method": "POST",
"urlPattern": "/api/v2/users",
"bodyPatterns": [
{
"matchesJsonPath": {
"expression": "$.email",
"equalTo": "invitee#email.com",
}
}
]
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"jsonBody": {
"email": "invitee#email.com"
}
}
}
See WireMock | Request Matching | JSON Path for details.
Using response templating to reduce number of stubs
You can use values passed to WireMock in the request in the response using response templating as so:
{
"request": {
"method": "POST",
"urlPattern": "/api/v2/users"
},
"response": {
"status": 201,
"headers": {
"Content-Type": "application/json"
},
"transformers": ["response-template"],
"jsonBody": {
"email": "{{jsonPath request.body '$.email'}}"
}
}
}
See WireMock | Response Templating | JSONPath helper for details.
There are also a variety of helpers for generating random data in various formats should you need each request to return some different value (e.g. a UUID) - see WireMock | Response Templating | Random value helper for details.
I'm attempting to create an API Gateway that will take in ANY method in AWS. Once the API has been called, the lambda function will then parse out the message that was sent, and decide what to do from there. So, given an API Gateway method of:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref myRestApi
ResourceId: !Ref myResource
HttpMethod: ANY
AuthorizationType: NONE
Integration:
Type: AWS_PROXY
IntegrationHttpMethod: POST
Uri:
Fn::Join:
- ''
- - 'arn:aws:apigateway:'
- Ref: AWS::Region
- :lambda:path/2015-04-30/functions/
- Fn::GetAtt:
- myLambdaFunction
- Arn
- /invocations
And it will successfully call myLambdaFunction, how do I then have the lambda function in node get which HttpMethod was actually sent?
For example:
exports.handler = (event, context, callback) => {
const response = {
statusCode: 200,
headers: {
"x-custom-header" : "This exists for reasons."
}
};
// I know that event doesn't actually have any httpmethod, but I'm not sure what it does have, or how to use it.
switch(event.httpmethod) {
case "POST":
console.log("POST!!!");
create(event, context, callback);
break;
case "GET":
console.log("GET!!!");
read(event, context, callback);
break;
case "PUT":
console.log("PUT!!!");
update(event, context, callback);
break;
}
The lambda above, should be able to console.log whichever method it got, but I'm not sure what should go in place of the event.httpmethod which is something I just made up.
You are looking for the event.httpMethod (note CAPITAL M) property.
If you are not sure what data your Lambda event has, you can always log the result by using
console.log(event);
and the result will be visible in the CloudWatch log associated with the Lambda function.
For proxy integration between API Gateway and Lambda, you can find specific details about those events in the AWS API Gateway developer guide:
{
"resource": "Resource path",
"path": "Path parameter",
"httpMethod": "Incoming request's method name"
"headers": {String containing incoming request headers}
"multiValueHeaders": {List of strings containing incoming request headers}
"queryStringParameters": {query string parameters }
"multiValueQueryStringParameters": {List of query string parameters}
"pathParameters": {path parameters}
"stageVariables": {Applicable stage variables}
"requestContext": {Request context, including authorizer-returned key-value pairs}
"body": "A JSON string of the request payload."
"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
}
Or in the AWS Lambda Developer Guide:
{
"path": "/test/hello",
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, lzma, sdch, br",
"Accept-Language": "en-US,en;q=0.8",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48",
"Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==",
"X-Forwarded-For": "192.168.100.1, 192.168.1.1",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"pathParameters": {
"proxy": "hello"
},
"requestContext": {
"accountId": "123456789012",
"resourceId": "us4z18",
"stage": "test",
"requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9",
"identity": {
"cognitoIdentityPoolId": "",
"accountId": "",
"cognitoIdentityId": "",
"caller": "",
"apiKey": "",
"sourceIp": "192.168.100.1",
"cognitoAuthenticationType": "",
"cognitoAuthenticationProvider": "",
"userArn": "",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48",
"user": ""
},
"resourcePath": "/{proxy+}",
"httpMethod": "GET",
"apiId": "wt6mne2s9k"
},
"resource": "/{proxy+}",
"httpMethod": "GET",
"queryStringParameters": {
"name": "me"
},
"stageVariables": {
"stageVarName": "stageVarValue"
}
}
The event variable is a request given json that your lambda gets.
For your code to work you need to pass to the lambda a the following json
{
httpmethod : "value"
}
where the value will be POST,GET or PUT.
If you go to the console on the right of the button actions you can creat a test with an event json input.
I have found the httpMethod value using this -
if (event.requestContext.http.method === 'GET') {
// code goes here...
}
The below code can use to find the method.
if (event.httpMethod === "GET") {
// Get method code goes here
} else if(event.httpMethod === "POST") {
// Post method code goes here
}
Example event.json API Gateway proxy event (REST API)
In an AWS lambda written in Node.js, I want to extract the following part of a URL when I do a GET call through the API gateway:
/devices/{id} --> {id} will be replaced by a value, and that is the value I want!
I know that to get QueryStringParameters you just use
event.queryStringParameters.[parameter name]
But how will I do this for path parameters, like for {id} above.
Also is there a good place where I can comprehensively learn about writing lambdas for APIs in Node.js?
Short answer:
const { id } = event.pathParameters;
I recently released a short training video that demonstrates in detail how to create API Gateway REST APIs and integrate them with AWS Lambda (NodeJS). Please check it out here:
Serverless Architecture: AWS API Gateway & Lambda
I'm assuming you are using lambda proxy here i'm pasting the event object sample for lambda proxy.
{
"message": "Good day, John of Seattle. Happy Friday!",
"input": {
"resource": "/{proxy+}",
"path": "/Seattle",
"httpMethod": "POST",
"headers": {
"day": "Friday"
},
"queryStringParameters": {
"time": "morning"
},
"pathParameters": {
"proxy": "Seattle"
},
"stageVariables": null,
"requestContext": {
"path": "/{proxy+}",
"accountId": "123456789012",
"resourceId": "nl9h80",
"stage": "test-invoke-stage",
"requestId": "test-invoke-request",
"identity": {
"cognitoIdentityPoolId": null,
"accountId": "123456789012",
"cognitoIdentityId": null,
"caller": "AIDXXX...XXVJZG",
"apiKey": "test-invoke-api-key",
"sourceIp": "test-invoke-source-ip",
"accessKey": "ASIXXX...XXDQ5A",
"cognitoAuthenticationType": null,
"cognitoAuthenticationProvider": null,
"userArn": "arn:aws:iam::123456789012:user/kdeding",
"userAgent": "Apache-HttpClient/4.5.x (Java/1.8.0_131)",
"user": "AIDXXX...XXVJZG"
},
"resourcePath": "/{proxy+}",
"httpMethod": "POST",
"apiId": "r275xc9bmd"
},
"body": "{ \"callerName\": \"John\" }",
"isBase64Encoded": false
}
}
the path can be extracted from "path" key in event object, it can be accessed from event.path and after that you can use string manipulation function to further manipulate it.
I hope it helps !
Use brackets in the resource path, as image above. Then in node.js user the code below:
exports.handler = async function(event) {
let serviceId = event.pathParameters.id;
}
The solution is very similar to what you mentioned in the first place. Just use event.pathParameters instead of event.queryStringParameters.
Here I'm using /api/test/{id} as my resource path and using Lambda Proxy integration. I'm getting the following event when I hit https://www.dummyapi.com/dev/api/test/id-123456
This has taken me awhile to figure out on my own so hoping this helps someone. After defining the Path Parameter on the Resource in API Gateway.
AWS Guide including PathParameter Steps: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-step-by-step.html
If using CloudFormation: https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-proxy-path-parameter-error/
My path /user/{userId}
My Path Parameter: userId
Then in my lambda function you can access the path via event.path which will have your parameters in object form: path: { userId: 9812 } }. Lots of documentation has it labeled as pathParameters but for whatever reason with this setup it doesn't come through that way.
export const getUser = async (event) => {
console.log(event);
const { userId } = event.path;
}
serverless.yaml
events:
- http:
path: user/{userId}
method: get
request:
parameters:
paths:
userId: true
integration: lambda
Can I resolve the issue in JIRA?
I made some trials with REST API as;
var url = "https://hibernate.atlassian.net/rest/api/2/issue/WEBSITE-1/transitions";
var message = [{
"update": {
"comment": [
{
"add": {
"body": "some text for body"
}
}
]
},
"fields": {
"assignee": {
"name": "name1"
},
"resolution": {
"name": "Fix"
}
},
"transition": {
"id": "1"
}
}];
request({
url: url,
method: "POST",
json: true,
body: message,
}, function (error){});
Url(https://hibernate.atlassian.net/rest/api/2/issue/WEBSITE-1/transitions) gives me;
{"expand":"transitions","transitions":[]}
How can I resolve an issue in JIRA? Am I doing wrong?
You've got the right approach, but you need to authenticate your requests with a user that has permission to execute the transition.
Because you execute your requests anonymously, JIRA gives you a response that does not contain any transitions you can execute and will not allow you to perform a transition.
Take a look at the documentation for the request module or another example.
To get the full list of transitions, append the string ?expand=transitions.fields to your existing url. So in this case it would look like
var url = "https://hibernate.atlassian.net/rest/api/2/issue/WEBSITE-1/transitions?expand=transitions.fields";
my case,I have create rest api which it should call external api and should display the response to the client using rest connector, i have added the below code in datasources.js
{"aci":{
"name": "aci",
"connector": "rest",
"operations": [{
"template": {
"method": "POST",
"url": "http://192.168.220.1:7800/esb/esb_cbs_service/AccountInfo",
"headers": {
"accepts": "application/json",
"content-type": "application/json"
},
"form": {
"ACCTINFOTRNRQ": {
"TRNUID": "12345",
"ACCTINFORQ": {
"DTACCTUP": "20050101"
}
}
},
"responsePath": "$.results[0].AccountInfoResponse.Item"
},
"functions": {
"acif": [
"customerId",
"customerName"
]
}
}]
}}
in model.js
module.exports = function(Acim) {
//remote method
Acim.extcall = function(Acim, cb){
cb(null, res);
}
};
Acim.remoteMethod('extcall',{
accepts: [{arg: 'customerId type" 'string''}]
returns: {arg: 'customerId', type: 'type'}
http: {path:'/getAci', verb: 'post'}
})
and when I reload the project I am facing this error. Please I am stuck here, what am I doing wrong?
C:\Users\user\acinfo>apic loopback:refresh
Updating swagger and product definitions
ERROR Cannot load the LoopBack application: Cannot load the LoopBack application: Cannot create data source "aci": Cannot initialize connector "RequestBuilder": Cannot read property 'root' of undefined
Please fix the problem and run apic loopback:refresh
Please fix the problem and run apic loopback:refresh