AWS API Gateway HTTP API how to pass string query params? - go

So I am making an app and need AWS API Gateway. I want to use HTTP API instead of REST API. My code looks like this
package main
import (
"database/sql"
"fmt"
"strings"
"github.com/aws/aws-lambda-go/lambda"
_ "github.com/lib/pq"
)
here I make a connection to the database
func fetch(inte string, input string) string {
if err != nil {
panic(err)
}
switch inte {
case "00":
{
res = append(res, response)
}
switch len(res) {
case 0:
return "401"
}
case "01":
}
switch len(res) {
case 0:
return "402"
}
}
return "404"
}
type LambdaEvent struct {
Req string `json:"req"`
Num string `json:"num"`
}
type LambdaResponse struct {
Res string `json:"res"`
}
func LambdaHandler(event LambdaEvent) (LambdaResponse, error) {
res := fetch(event.Num, event.Req)
return LambdaResponse{
Res: res,
}, nil
}
func main() {
lambda.Start(LambdaHandler)
}
So as you see this is not the full code. I make a connection to the database and and work with the requests string query. so I tried the same with http api but it just gives me the 404 meaning the http api doesn't pass the query string to the lambda so how do I make my api pass the data to the lambda. Rest api works HTTP doesn't.
Thanks for any help.

I'm not familiar with Serverless Frameworks for APIGW, but manipulating QueryString parameters is built into the APIGW Console. Just login to AWS and search for APIGateway. Edit your HTTP API and select Integrations from the menu on the left. Select the integration that maps to your Lambda function and Edit the Parameter Mappings on the right

If you are deploying your lambdas and api-gateway with serverless framework you can do something like this:
hello:
handler: src/hello.handler
name: hello
events:
- http:
path: car/{id}/color/{color}
method: get

Assuming you are planning to use Lambda Proxy Integration in API Gateway, here are the changes that needs to be done to access the query parameters.
import github.com/aws/aws-lambda-go/events (This has all the relevant structs)
Change the lambda handler to func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
Now you can access the query parameters as a Map at request.QueryStringParameters and execute your selection logic
When you return a response for API Gateway, ensure you follow the events.APIGatewayProxyResponse struct i.e. at least return a status code along with optional body, headers etc.
No changes/config required at the API Gateway to pass the query parameters with Lambda proxy integration
You can use your own structs for request and response, but they need to use the appropriate keys as defined in the events.APIGatewayProxyRequest and events.APIGatewayProxyResponse.
e.g. add the following in LambdaEvent struct to access the query string parameters.
QueryStringParameters map[string]string `json:"queryStringParameters"`
If you are getting started with AWS Lambda, have a look at AWS SAM to keep things simple.

Related

Passing nested JSON as variable in Machinebox GraphQL mutation using golang

Hi there Golang experts,
I am using the Machinebox "github.com/machinebox/graphql" library in golang as client for my GraphQL server.
Mutations with single layer JSON variables work just fine
I am, however, at a loss as to how to pass a nested JSON as a variable
With a single layer JSON I simply create a map[string]string type and pass into the Var method. This in turn populates my graphql $data variable
The machinebox (graphql.Request).Var method takes an empty interface{} as value so the map[string]string works fine. But embedded json simply throws an error.
code:
func Mutate(data map[string]string, mutation string) interface{} {
client := GQLClient()
graphqlRequest := graphql.NewRequest(mutation)
graphqlRequest.Var("data", data)
var graphqlResponse interface{}
if err := client.Run(context.Background(), graphqlRequest, &graphqlResponse); err != nil {
panic(err)
}
return graphqlResponse
}
Mutation:
mutation createWfLog($data: WfLogCreateInput)
{
createWfLog (data: $data){
taskGUID {
id
status
notes
}
event
log
createdBy
}
}
data variable shape:
{
"data": {
"event": "Task Create",
"taskGUID": {
"connect": {"id": "606f46cdbbe767001a3b4707"}
},
"log": "my log and information",
"createdBy": "calvin cheng"
}
}
As mentioned, the embedded json (value of taskGUID) presents the problem. If value was simple string type, it's not an issue.
Have tried using a struct to define every nesting, passed in struct.
Have tried unmarshaling a struct to json. Same error.
Any help appreciated
Calvin
I have figured it out... and it is a case of my noobness with Golang.
I didn't need to do all this conversion of data or any such crazy type conversions. For some reason I got in my head everything HAD to be a map for the machinebox Var(key, value) to work
thanks to xarantolus's referenced site I was able to construct a proper strut. I populated the strut with my variable data (which was a nested json) and the mutation ran perfectly!
thanks!

Programmatically get Account Id from lambda context arn

I have access to
com.amazonaws.services.lambda.runtime.Context;
object and by extension the invoked function Arn. The arn contains the account Id where the lambda resides.
My question is simple, I want the cleanest way to extract the account Id from that.
I was taking a look
com.amazon.arn.ARN;
It has a whole bunch of stuff, but no account ID (which i presume is due to the fact that not all arns have account ids ?)
I want to cleanly extract the account Id, without resorting to parsing the string.
If your lambda is being used as an API Gateway proxy lambda, then you have access to event.requestContext.accountId (where event is the first parameter to your handler function).
Otherwise, you will have to split the ARN up.
From the AWS documentation about ARN formats, here are the valid Lambda ARN formats:
arn:aws:lambda:region:account-id:function:function-name
arn:aws:lambda:region:account-id:function:function-name:alias-name
arn:aws:lambda:region:account-id:function:function-name:version
arn:aws:lambda:region:account-id:event-source-mappings:event-source-mapping-id
In all cases, account-id is the 5th item in the ARN (treating : as a separator). Therefore, you can just do this:
String accountId = arn.split(":")[4];
You no longer need to parse the arn anymore, sts library has introduced get_caller_identity for this purpose.
Its an overkill, but works!.
Excerpts from aws docs.
python
import boto3
client = boto3.client('sts')
response = client.get_caller_identity()['Account']
js
/* This example shows a request and response made with the credentials for a user named Alice in the AWS account 123456789012. */
var params = {
};
sts.getCallerIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
/*
data = {
Account: "123456789012",
Arn: "arn:aws:iam::123456789012:user/Alice",
UserId: "AKIAI44QH8DHBEXAMPLE"
}
*/
});
More details here & here
I use this:
ACCID: { "Fn::Join" : ["", [{ "Ref" : "AWS::AccountId" }, "" ]] }
golang
import (
"github.com/aws/aws-lambda-go/lambdacontext"
)
func Handler(ctx context.Context) error {
lc, ok := lambdacontext.FromContext(ctx)
if !ok {
return errors.Errorf("could not get lambda context")
}
AwsAccountId := strings.Split(lc.InvokedFunctionArn, ":")[4]

Endpoints Google ID JWT, and Golang oauth2: have id_token, need access_token?

I have an app engine app behind endpoints and I'm having trouble following the documentation around adding authentication. My preference is to allow service accounts within the project through, then perform more granular app-side authorization.
In my case, most clients will be outside GCP and will be automated programs not people, so I'm using JSON key files thinking that is the way to go (correct me if I'm wrong please). I also don't want to have to redeploy the app to change user configs, so I follow the "GOOGLE ID JWT" information in the documentation from here:
https://cloud.google.com/endpoints/docs/openapi/service-to-service-auth
This is the security sections of my swagger JSON:
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "key",
"in": "query"
},
"google_id_token": {
"authorizationUrl": "",
"flow": "implicit",
"type": "oauth2",
"x-google-issuer": "https://accounts.google.com"
}
},
"security": [
{
"api_key": [],
"google_id_token": []
}
]
This deploys OK, but I am stumped on what to do from the client side to make use of the service account JSON.
In Go, I use the following, based on my understanding of the oauth2/google documentation:
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"os"
"golang.org/x/oauth2/google"
)
func main() {
ctx := context.Background()
apiKey := os.Getenv("API_KEY")
basePath := "https://SERVICE_NAME-dot-PROJECT_NAME.appspot.com" // changed for privacy
creds, _ := google.FindDefaultCredentials(ctx)
jwt, _ := google.JWTConfigFromJSON(creds.JSON, basePath)
client := jwt.Client(ctx)
res, _ := client.Get(fmt.Sprintf("%s/REQUEST_PATH?key=%s", basePath, apiKey)) // changed for privacy
body, _ := ioutil.ReadAll(res.Body)
log.Printf("### http status: %d %s", res.StatusCode, res.Status)
log.Printf("### http body: %s", body)
}
I've removed error-checking for this code paste for brevity, there are no errors when I run this. The result of this being:
2017/10/16 07:32:38 ### http status: 401 401 Unauthorized
2017/10/16 07:32:38 ### http body: {
"code": 16,
"message": "JWT validation failed: Missing or invalid credentials",
"details": [
{
"#type": "type.googleapis.com/google.rpc.DebugInfo",
"stackEntries": [],
"detail": "auth"
}
]
}
Upon inspection of the internals of what's happening, such as calling jwt.TokenSource(ctx).Token(), I see that Google is returning an id_token, but no access_token (spotted by adding some debug logs to the jwt package):
2017/10/16 07:38:47 ### oauth2/jwt -> tokenURL: https://accounts.google.com/o/oauth2/token, form: url.Values{"grant_type":[]string{"urn:ietf:params:oauth:grant-type:jwt-bearer"}, "assertion":[]string{"...cut..."}}
2017/10/16 07:38:47 ### oauth2/jwt <- response: {
"id_token" : "...cut..."
}
2017/10/16 07:38:47 ### oauth2/jwt <- token: &oauth2.Token{AccessToken:"", TokenType:"", RefreshToken:"", Expiry:time.Time{wall:0x0, ext:63643740079, loc:(*time.Location)(0x1424160)}, raw:map[string]interface {}{"id_token":"...cut..."}}
So when the oauth2 HTTP transport tries to add the auth header -- which is tied to the AccessToken field above -- it results in a string like Authorization: Bearer, thus the result fails.
I feel like I'm missing a step here which isn't obvious to me in the documentation.
Thanks for your time.
It would be expected for the oauth client to return id_token instead of access_token. You're using JWTConfigFromJSON, not ConfigFromJSON, which is correct. I think the second argument to JWTConfigFromJSON is incorrect, and you should be specifying scopes instead. In this case, try "email" instead of basePath.

Determine if POST data value matches struct field type

Using the gin framework I am trying to determine if POST'ed data does not match the struct field type and inform API user of their error.
type CreateApp struct {
LearnMoreImage string `db:"learn_more_image" json:"learn_more_image,omitempty" valid:"string,omitempty"`
ApiVersion int64 `db:"api_version" json:"api_version" valid:"int,omitempty"`
}
...
func CreateApps(c *gin.Context) {
var json models.CreateApp
c.Bind(&json)
So when I POST
curl -H "Content-Type: application/json" -d '{"learn_more_image":"someimage.jpg","api_version":"somestring"}' "http://127.0.0.1:8080/v1.0/apps"
I would like to determine whether the POST'ed data for field 'api_version' (passed as string) does not match the struct field it is being binded to (int). If the data doesnt match I'd like to send a message back to the user. Its for this reason I was hoping I could loop through the gin contexts data and check it.
The gin function 'c.Bind()' seems to omit invalid data and all subsequent data fields with it.
Gin has a built-in validation engine: https://github.com/bluesuncorp/validator/blob/v5/baked_in.go
but you can use your own or disable it completely.
The validator does not validate the wire data (json string), instead it validates the binded struct:
LearnMoreImage string `db:"learn_more_image" json:"learn_more_image,omitempty" binding:"required"`
ApiVersion int64 `db:"api_version" json:"api_version" binding:"required,min=1"`
Notice this: binding:"required,min=1"
Then:
err := c.Bind(&json)
or use a middleware and read c.Errors.
UPDATED:
Three workarounds:
Validate the json string your own (it can not be done with enconding/json)
Validate if integer is > 0 binding:"min=1"
Use a map[string]interface{} instead of a Struct, then validate the type.
func endpoint(c *gin.Context) {
var json map[string]interface{}
c.Bind(&json)
struct, ok := validateCreateApp(json)
if ok { /** DO SOMETHING */ }
}
func validateCreateApp(json map[string]interface{}) (CreateApp, bool) {
learn_more_image, ok := json["learn_more_image"].(string)
if !ok {
return CreateApp{}, false
}
api_version, ok = json["api_version"].(int)
if !ok {
return CreateApp{}, false
}
return CreateApp{
learn_more_image, api_version,
}
}

Golang: implements http server health checking. gocraft/health

I want to check the health of my service,having the metrics of each endPoint.
My service calls some other services and recieves a Json code, I make templates with it, and then I send it to a http.ResponseWriter.
I searched and I found this package "gocraft/health" but I didn't really understand how it works.
Is there any other way or package to generate metrics or should I just use "gocraft/health.
Thank you in advance
Finally, I choose "gocraft/health", a great library.
Example of usage:
package main
import (
"log"
"net/http"
"os"
"time"
"github.com/gocraft/health"
)
//should be global Var
var stream = health.NewStream()
func main() {
// Log to stdout!
stream.AddSink(&health.WriterSink{os.Stdout})
// Make sink and add it to stream
sink := health.NewJsonPollingSink(time.Minute*5, time.Minute*20)
stream.AddSink(sink)
// Start the HTTP server! This will expose metrics via a JSON API.
adr := "127.0.0.1:5001"
sink.StartServer(adr)
http.HandleFunc("/api/getVastPlayer", vastPlayer)
log.Println("Listening...")
panic(http.ListenAndServe(":2001", nil))
}
Per the initialization options above, your metrics are aggregated in 5-minute chunks. We'll keep 20 minutes worth of data in memory. Nothing is ever persisted to disk.
You can create as many jobs as you want
func vastPlayer(w http.ResponseWriter, r *http.Request) {
job_1 := stream.NewJob("/api/getVastPlayer")
...
...
if bol {
job_1.Complete(health.Success)
} else {
job_1.Complete(health.Error)
}
}
Once you start your app, this will expose metrics via a JSON API. You can browse the /health endpoint (eg, 127.0.0.1:5001/health) to see the metrics. You will get something like that:
{
"instance_id": "sd-69536.29342",
"interval_duration": 86400000000000,
"aggregations": [
{
"interval_start": "2015-06-11T02:00:00+02:00",
"serial_number": 1340,
"jobs": {
"/api/getVastPlayer": {
"timers": {},
"events": {},
"event_errs": {},
"count": 1328,
"nanos_sum": 140160794784,
"nanos_sum_squares": 9.033775178022173E+19,
"nanos_min": 34507863,
"nanos_max": 2736850494,
"count_success": 62,
"count_validation_error": 1266,
"count_panic": 0,
"count_error": 0,
"count_junk": 0
},
"timers": {},
"events": {},
"event_errs": {}
}
}
]
}
For more information and functionality check this link:
https://github.com/gocraft/health
In case you came across this question because you were looking for exposing a /health endpoint, there is an upcoming RFC for health checks: https://github.com/inadarei/rfc-healthcheck
And there's a Go library health-go for exposing health endpoints compliant with that RFC: https://github.com/nelkinda/health-go
Example:
package main
import (
"github.com/nelkinda/health-go"
"net/http"
)
func main() {
// 1. Create the health Handler.
h := health.New(health.Health{Version: "1", ReleaseID: "1.0.0-SNAPSHOT"})
// 2. Add the handler to your mux/server.
http.HandleFunc("/health", h.Handler)
// 3. Start your server.
http.ListenAndServe(":80", nil)
}
It is extensible and supports a number of built-in checks such as uptime and sysinfo.
Disclaimer: I'm the author of health-go.

Resources