You may think that to expose search results, we would need another Lambda function. On the other hand, in our API endpoint, we just need the document we saved to CloudSearch, and we can directly expose it to the client. Then, we can use the similar method we used for the S3 upload and use API Gateway as a proxy between CloudSearch's AWS API and our publicly facing API.
To create such an API, let's first create a resource in the /search path using the method we already know from the earlier steps. Let's add this block to the Resources block in our CloudFormation template:
"SearchResource": { "Type": "AWS::ApiGateway::Resource", "Properties": { "PathPart": "search", "RestApiId": { "Ref": "RestApi" }, "ParentId": { "Fn::GetAtt": [ "RestApi", "RootResourceId" ] } } }
To integrate CloudSearch with API Gateway, we will need to know the automatically generated subdomain for the CloudSearch domain. When you look at the CloudSearch dashboard, you will see this value under the Search Endpoint title. For example, this value can be shown as follows:
search-serverlessbook-uiyqpdvcdz7o4hxudtnqzpjdtu.us-east-
1.cloudsearch.amazonaws.com
We need only the first portion, so add search-serverlessbook-uiyqpdvcdz7o4hxudtnqzpjdtu as a parameter in our CloudFormation template. Let's create this parameter value to be used in the next part:
"CloudSearchDomain": { "Type": "String", "Description": "Endpoint Name for CloudSearch domain" }
Do not forget to set this parameter to your endpoint's value in build.gradle! You have to omit the search- prefix when you write the parameter to your build.gradle file. As you can see on the dashboard, CloudSearch provides two endpoints for different purposes: Search Endpoint and Document Endpoint. For example, for this configuration, the endpoints are:
Search Endpoint: search-serverlessbook-uiyqpdvcdz7o4hxudtnqzpjdtu.us-
east-1.cloudsearch.amazonaws.com Document Endpoint: doc-serverlessbook-uiyqpdvcdz7o4hxudtnqzpjdtu.us-
east-1.cloudsearch.amazonaws.com
You should write serverlessbook-uiyqpdvcdz7o4hxudtnqzpjdtu to your build.gradle file, so for different purposes, we will populate different endpoints in our code.
Now, we can create the most complicated part, the method itself. Let's first add the code and then analyze the important parts of it:
"SearchGetMethod": { "Type": "AWS::ApiGateway::Method", "Properties": { "HttpMethod": "GET", "RestApiId": { "Ref": "RestApi" }, "ResourceId": { "Ref": "SearchResource" }, "RequestParameters": { "method.request.querystring.q": "q" }, "AuthorizationType": "NONE", "Integration": { "Type": "AWS", "Uri": { "Fn::Sub":"arn:aws:apigateway:${AWS::Region}:search-
${CloudSearchDomain}.cloudsearch:path//2013-01-01/suggest" }, "IntegrationHttpMethod": "GET", "Credentials": { "Fn::GetAtt": [ "ApiGatewayProxyRole", "Arn" ] }, "RequestParameters": { "integration.request.querystring.suggester":
"'username_suggester'", "integration.request.querystring.q":
"method.request.querystring.q" }, "RequestTemplates": { }, "PassthroughBehavior": "WHEN_NO_TEMPLATES", "IntegrationResponses": [ { "SelectionPattern": ".*", "StatusCode": "200" } ] }, "MethodResponses": [ { "StatusCode": "200" } ] } }
This is pretty similar to S3 integration. Here, we do not force authentication, because we want our Search API to be public. You could, of course, change it easily according to your needs, just adding authorization Lambda to the configuration.
Maybe, the most important configuration is the Uri field, where we set which AWS API we should send our request to. Here, we will construct the full path using the AWS::Region and CloudSearchDomain parameters. Note that we have prepended search- to the subdomain to construct the URL you read in the CloudSearch dashboard. You may wonder what /2013-01-01/suggest means. This is the documented API endpoint for suggestion. If you check the documentation at http://docs.aws.amazon.com/cloudsearch/latest/developerguide/search-api.html#suggest, you can see this information and more endpoints.
As you might have noted, this CloudSearch API endpoint expects the suggester and q parameters. We get the second parameter from the client, because it is what they search on our application. On the other hand, we can add the suggester parameter at the API Gateway level and pass the request to CloudSearch API. That's why we pass 'username_suggester' (with quotes) to integration.request.querystring.suggester and integration.request.querystring.q to method.request.querystring.q.
As a last step, we have to add SearchGetMethod to the DependsOn collection of ApiDeployment to prevent our API from being deployed before the method's creation:
"ApiDeployment": { "DependsOn": [ "TestGetMethod", "SearchGetMethod" ],
Now, we can deploy the API and see the results under the /search?q=keyword query.