Appsync as Proxy to another Graphql server - amazon-web-services

I have a existing graphql server provied by 3rd party. I also have my own backend running on EC2 to provide APIs.
I'm trying to build the appsync with aws-cdk for connecting to both 3rd party graphql and my backend instance also.
With the graphql server, appsync will act as proxy to forward queries only. My questions are:
Do we have anyway to load remote schema and populate it in appsync along with its schema?
How can we forward the requests to another graphql server using aws-cdk? I'm trying something like this:
private get _requestMappingTemplate(): string {
return `
{
"version": "2018-05-29",
"method": "GET",
"resourcePath": $util.toJson("/graphql"),
"params": {
"headers": {
"Authorization": "Bearer $ctx.request.headers.Authorization"
},
"body": {
"query": "$util.escapeJavaScript($ctx.info.getSelectionSetGraphQL())"
}
}
}`;
}
But from the aws doc, getSelectionSetGraphQL returns string representation of the selection set, formatted as GraphQL schema definition language (SDL). Although fragments aren't merged into the selection set
Is that possible to setup AppSync for forwarding request to another GraphQL servers? Any best practice to follow?

It's quite a bit more complicated. I'm still working on it, and the best I got so far is given below. It still drops query arguments, so has limited use.
#* TODO: Add some more interesting info to the operation name, e.g. a timestamp *#
#set($operationName = $context.info.parentTypeName)
#set($payloadBody = {
"query": "$util.str.toLower($context.info.parentTypeName) $operationName { $context.info.fieldName $context.info.selectionSetGraphQL }",
"operationName": $operationName,
"variables": $context.info.variables
})
{
"version" : "2018-05-29",
"operation": "Invoke",
"payload":{
"path": "/graphql",
"httpMethod": "POST",
"headers": $util.toJson($ctx.request.headers),
"requestContext": {
"authorizer": {
"claims": $context.identity.claims
}
},
"body": "$util.escapeJavaScript($util.toJson($payloadBody))",
"isBase64Encoded": false
},
}

Related

Spring cloud function routing api gateway null pointer exception

I have a problem with routing using API Gateway headers. I am using org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest as a handler request. I have two functions, they work locally. They work if I set environment variable.
If I use API Gateway headers (spring.cloud.function.definition:lowercase), I get:
{
"errorMessage": "java.lang.NullPointerException",
"errorType": "java.lang.NullPointerException",
"stackTrace": [
"org.springframework.cloud.function.adapter.aws.AWSLambdaUtils.generateMessage(AWSLambdaUtils.java:123)",
"org.springframework.cloud.function.adapter.aws.FunctionInvoker.handleRequest(FunctionInvoker.java:105)",
"java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
"java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)",
"java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)",
"java.base/java.lang.reflect.Method.invoke(Unknown Source)"
]
}
Example code reproducing the issue is here: https://github.com/cygi/cloudexample
POM is based on samples from Spring Cloud Function codebase (example code (https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-aws-routing). Spring Cloud Function version is 3.2.1 (sample had a SNAPSHOT version, that uses JAVA 11, which is not available in AWS Lambda, at least without docker).
Reverting to Spring Cloud Function 3.1.6 has resolved the problem.
Test event for AWS Lambda:
{
"body": "foo",
"httpMethod": "POST",
"isBase64Encoded": false,
"headers": {
"spring.cloud.function.definition": "uppercase"
}
}
Result on 3.2.2
{
"statusCode": 417,
"headers": null,
"body": "Failed to establish route, since neither were provided: 'spring.cloud.function.definition' as Message header or as application property or 'spring.cloud.function.routing-expression' as application property."
}
Result on 3.1.6
{
"isBase64Encoded": false,
"headers": {
"id": "758c1873-9377-25af-5ca2-84f55710ff2a",
"contentType": "application/json",
"timestamp": "1644500775689"
},
"body": "\"bbbb\"",
"statusCode": 200
}

Can the "queryStringParameter" in the eventbody of a GET request be moved to the "body" of the event for an AWS API Gateway HTTP API?

I'm trying to get my "queryStringParameters" in the event into the "body" key of the event so I can parse them in the same way that I parse post requests. Is this possible with a HTTP api? I tried using parameter mappings in the integration settings but this only allows me to append the body to the queryString.
The way that you get the values from a POST request using body and with query parameters are different. So both ca not be in the body property as you want.
The queryStringParameters is passed when you add a URL Query String Parameter in your Api gateway resource.
When you send a request to the event in your API the following event will be send to the Lamba function
{
"resource": "/{proxy+}",
"path": "/path/to/resource",
"httpMethod": "POST",
"isBase64Encoded": true,
"queryStringParameters": {
"foo": "bar"
},
"multiValueQueryStringParameters": {
"foo": [
"bar"
]
},
"pathParameters": {
"proxy": "/path/to/resource"
},
"stageVariables": {
"baz": "qux"
},
"body":"{ \"time\": \"evening\" }",
"headers": {
...
},
...
}
You be able to get the query parameters of event in your Lambda using the properties:
event.queryStringParameters && event.queryStringParameters.foo
And the body we need to get with the body property:
if (event.body) {
let body = JSON.parse(event.body)
if (body.time)
time = body.time;
}
In this way in your Lambda you need to parse them in different ways.
For more information of how to work with API Gateway and Lambda take a look here: Tutorial: Build a Hello World REST API with Lambda proxy integration

How can I make apigateway forward root path to integrated http endpoint?

I created a REST api gateway in AWS and configure it to pass through all requests to a http endpoint. The configuration I have is
After deploy to a stage (dev) it gives me an invoke URL, like https://xxxx.execute-api.ap-southeast-2.amazonaws.com/dev,
it works fine if I invoke the url by appending a sub path like: https://xxxx.execute-api.ap-southeast-2.amazonaws.com/dev/xxxxx`, I can see it forward the request to downstream http endpoint. However it doesn't forward any request if I invoke the base url: https://xxxx.execute-api.ap-southeast-2.amazonaws.com/dev. How can I make it work with the base invoke url without any subpath?
I tired to add an additional / path resource in API gateway but it doesn't allow me to add it.
The application must be able to receive requests at any path, including the root path: /. An API Gateway resource with a path of /{proxy+} captures every path except the root path. Making a request for the root path results in a 403 response from API Gateway with the message Missing Authentication Token.
To fix this omission, add an additional resource to the API with the path set to / and link that new resource to the same http endpoint as used in the existing /{proxy+} resource.
The updated OpenAPI document now looks like the following code example:
{
"openapi": "3.0",
"info": {
"title": "All-capturing example",
"version": "1.0"
},
"paths": {
"/": {
"x-amazon-apigateway-any-method": {
"responses": {},
"x-amazon-apigateway-integration": {
"httpMethod": "POST",
"type": "aws_proxy",
"uri": ""
}
}
},
"/{proxy+}": {
"x-amazon-apigateway-any-method": {
"responses": {},
"x-amazon-apigateway-integration": {
"httpMethod": "POST",
"type": "aws_proxy",
"uri": ""
}
}
}
}
}

AppSync check if DynamoDB record exists

I am trying to write a resolver for AppSync that derives the value for a Boolean field based on the existence of a record in DynamoDB.
I currently have the following request mapping template:
{
"version": "2017-02-28",
"operation": "GetItem",
"key": {
"field1": $util.dynamodb.toDynamoDBJson($ctx.args.field1),
"field2": $util.dynamodb.toDynamoDBJson($ctx.args.field2)
}
}
And the following response mapping template:
#if($util.isNull($ctx.result))
#set($exists = false)
#else
#set($exists = true)
#end
$util.toJson({
"field1": $ctx.args.field1,
"field2": $ctx.args.field2,
"exists": $exists
})
This works correctly if the record exists but if it does not then AppSync simply returns "null" for the entire API call and does not seem to evaluate the response mapping template at all. Is there any way I can instruct it not to do this?
Another option would be to perform a query and look at the length of the response but I have no idea how to check length in these templates.
This is an expected behavior for the 2017 version of the Request template. If you would like the $ctx.result to be evaluated, switch to the 2018 version as below:
{
"version": "2018-05-29",
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.id),
},
}
Refer to this change log for additional details.

HIVE_INVALID_METADATA in Amazon Athena

How can I work around the following error in Amazon Athena?
HIVE_INVALID_METADATA: com.facebook.presto.hive.DataCatalogException: Error: : expected at the position 8 of 'struct<x-amz-request-id:string,action:string,label:string,category:string,when:string>' but '-' is found. (Service: null; Status Code: 0; Error Code: null; Request ID: null)
When looking at position 8 in the database table connected to Athena generated by AWS Glue, I can see that it has a column named attributes with a corresponding struct data type:
struct <
x-amz-request-id:string,
action:string,
label:string,
category:string,
when:string
>
My guess is that the error occurs because the attributes field is not always populated (c.f. the _session.start event below) and does not always contain all fields (e.g. the DocumentHandling event below does not contain the attributes.x-amz-request-id field). What is the appropriate way to address this problem? Can I make a column optional in Glue? Can (should?) Glue fill the struct with empty strings? Other options?
Background: I have the following backend structure:
Amazon PinPoint Analytics collects metrics from my application.
The PinPoint event stream has been configured to forward the events to an Amazon Kinesis Firehose delivery stream.
Kinesis Firehose writes data to S3
Use AWS Glue to crawl S3
Use Athena to write queries based on the databases and tables generated by AWS Glue
I can see PinPoint events successfully being added to json files in S3, e.g.
First event in a file:
{
"event_type": "_session.start",
"event_timestamp": 1524835188519,
"arrival_timestamp": 1524835192884,
"event_version": "3.1",
"application": {
"app_id": "[an app id]",
"cognito_identity_pool_id": "[a pool id]",
"sdk": {
"name": "Mozilla",
"version": "5.0"
}
},
"client": {
"client_id": "[a client id]",
"cognito_id": "[a cognito id]"
},
"device": {
"locale": {
"code": "en_GB",
"country": "GB",
"language": "en"
},
"make": "generic web browser",
"model": "Unknown",
"platform": {
"name": "macos",
"version": "10.12.6"
}
},
"session": {
"session_id": "[a session id]",
"start_timestamp": 1524835188519
},
"attributes": {},
"client_context": {
"custom": {
"legacy_identifier": "50ebf77917c74f9590c0c0abbe5522d2"
}
},
"awsAccountId": "672057540201"
}
Second event in the same file:
{
"event_type": "DocumentHandling",
"event_timestamp": 1524835194932,
"arrival_timestamp": 1524835200692,
"event_version": "3.1",
"application": {
"app_id": "[an app id]",
"cognito_identity_pool_id": "[a pool id]",
"sdk": {
"name": "Mozilla",
"version": "5.0"
}
},
"client": {
"client_id": "[a client id]",
"cognito_id": "[a cognito id]"
},
"device": {
"locale": {
"code": "en_GB",
"country": "GB",
"language": "en"
},
"make": "generic web browser",
"model": "Unknown",
"platform": {
"name": "macos",
"version": "10.12.6"
}
},
"session": {},
"attributes": {
"action": "Button-click",
"label": "FavoriteStar",
"category": "Navigation"
},
"metrics": {
"details": 40.0
},
"client_context": {
"custom": {
"legacy_identifier": "50ebf77917c74f9590c0c0abbe5522d2"
}
},
"awsAccountId": "[aws account id]"
}
Next, AWS Glue has generated a database and a table. Specifically, I see that there is a column named attributes that has the value of
struct <
x-amz-request-id:string,
action:string,
label:string,
category:string,
when:string
>
However, when I attempt to Preview table from Athena, i.e. execute the query
SELECT * FROM "pinpoint-test"."pinpoint_testfirehose" limit 10;
I get the error message described earlier.
Side note, I have tried to remove the attributes field (by editing the database table from Glue), but that results in Internal error when executing the SQL query from Athena.
This is a known limitation. Athena table and database names allow only underscore special characters#
Athena table and database names cannot contain special characters, other than underscore (_).
Source: http://docs.aws.amazon.com/athena/latest/ug/known-limitations.html
Use tick (`) when table name has - in the name
Example:
SELECT * FROM `pinpoint-test`.`pinpoint_testfirehose` limit 10;
Make sure you select "default" database on the left pane.
I believe the problem is your struct element name: x-amz-request-id
The "-" in the name.
I'm currently dealing with a similar issue since my elements in my struct have "::" in the name.
Sample data:
some_key: {
"system::date": date,
"system::nps_rating": 0
}
Glue derived struct Schema (it tried to escape them with ):
struct <
system\:\:date:String
system\:\:nps_rating:Int
>
But that still gives me an error in Athena.
I don't have a good solution for this other than changing Struct to STRING and trying to process the data that way.