├── .gitignore ├── LICENSE ├── README.md ├── basic_lambda_function.py ├── create_lambda_package.sh ├── lambda_function.py └── sam ├── swagger.yaml └── template.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | sam/template.out.yaml 2 | lambda_function.zip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Lambda + API Gateway Example 2 | 3 | This example uses [Twilio](https://www.twilio.com/) to save an image from your mobile phone to the AWS cloud. A user sends an image using MMS to a Twilio phone number which sends a request to an Amazon API Gateway endpoint that triggers a Lambda function. The app then returns a publicly accessible link to the image in AWS S3. This app uses AWS Lambda, API Gateway, DynamoDB & S3. It is also 100% serverless! 4 | 5 | NOTE: The project has been updated to use AWS Serverless Application Model (AWS SAM) 6 | 7 | ##Architecture 8 | 9 | ![Architecture](https://s3.amazonaws.com/smallya-useast-1/twilio-apig/architecture.png) 10 | 11 | # Building the App 12 | 13 | Step-by-step on how to configure, develop & deploy this app on AWS. 14 | 15 | ### Pre-Requisites 16 | 1. Sign-in to AWS or [Create an Account](https://us-west-2.console.aws.amazon.com). 17 | 2. Pick a region in the console and be consistent throughout this app. Use either `us-east-1`, `us-west-2` & `eu-west-1`. 18 | 3. Create a table in DynamoDB with a single Hash named `fromNumber` for primary key of type String. We don't need any additional indexes and you can keep the read/write capacity at 1 for this example. 19 | 4. Create an S3 bucket to ingest MMS images. 20 | 5. Create/login to a Twilio account & create a phone number with MMS capability. 21 | 22 | ### *NEW* Serverless Application Model (SAM) Deployment 23 | 24 | In the AWS Region you plan to deploy, make sure you have an existing Amazon S3 bucket in which SAM can create the deployment artifacts. 25 | 26 | Else create a new bucket using the following AWS CLI command: 27 | 28 | ``` 29 | aws s3 mb s3:// 30 | ``` 31 | 32 | Before deploying the project to SAM for the first time, you'll need to update some variables in `lambda_function.py` and `template.yaml`/`swagger.yaml` (found in `sam/` folder). 33 | 34 | ``` 35 | # swagger.yaml 36 | # <> : AWS region set in Pre-Requisites, referenced twice in swagger.yaml 37 | # <> : your global AWS account ID (found in MyAccount) 38 | uri: arn:aws:apigateway:<>:lambda:path/2015-03-31/functions/arn:aws:lambda:<>:<>:function:${stageVariables.LambdaFunctionName}/invocations 39 | 40 | # template.yaml 41 | CodeUri: s3:///lambda_function.py.zip # name of S3 bucket created in Pre-Requiisites 42 | DefinitionUri: s3:///swagger.yaml # name of S3 bucket created in Pre-Requisites 43 | ``` 44 | 45 | #### Generating the Lambda code 46 | 47 | Connect to a 64-bit Amazon Linux instance via SSH. 48 | 49 | ``` 50 | ssh -i key.pem ec2-user@public-ip-address 51 | ``` 52 | 53 | Ensure basic build requirements are installed. 54 | 55 | ``` 56 | sudo yum install python27-devel python27-pip gcc 57 | ``` 58 | 59 | Install native dependencies required by Pillow. 60 | 61 | ``` 62 | sudo yum install libjpeg-devel zlib-devel 63 | ``` 64 | 65 | Create and activate a virtual environment. 66 | 67 | ``` 68 | virtualenv ~/lambda-apigateway-twilio-tutorial 69 | 70 | source ~/lambda-apigateway-twilio-tutorial/bin/activate 71 | ``` 72 | 73 | Install libraries in the virtual environment. 74 | 75 | ``` 76 | pip install Pillow 77 | 78 | pip install boto3 79 | 80 | pip install twilio 81 | ``` 82 | 83 | Install git. 84 | 85 | ``` 86 | sudo yum install git-all 87 | ``` 88 | 89 | Clone this repo and update `lambda_function.py`. 90 | 91 | ``` 92 | # lambda_function.py 93 | account_sid = "account_sid" # Twilio account SID 94 | auth_token = "auth_token" # Twilio auth token 95 | phone_number = "phone_number" # Twilio phone number 96 | dynamodb = boto3.resource('dynamodb', '_region') # AWS region set in Pre-Requisites 97 | table_users = dynamodb.Table('table_name') # name of DyanmoDB created in Pre-Requisites 98 | ``` 99 | 100 | Run `bash ./create_lambda_package.sh` which will create the `lambda_function.zip`. 101 | 102 | Download the `.zip` file using a command like `scp -i key.pem ec2-user@public_ip_address:/path/to/file .` where `/path/to/file` can be determined by `readlink -f lambda_function.zip`. 103 | 104 | #### Deploying via SAM 105 | 106 | Upload both `swagger.yaml` and `lambda_function.zip` into the S3 bucket. 107 | 108 | To deploy the project for the first time with SAM, and for each subsequent code update, run both of 109 | the following AWS CLI commands in order. 110 | 111 | You can use the basic_lambda_function.py as the reference for a simple backend to test the end to 112 | end flow 113 | 114 | ``` 115 | aws cloudformation package \ 116 | --template-file template.yaml \ 117 | --output-template-file template-out.yaml \ 118 | --s3-bucket 119 | 120 | aws cloudformation deploy \ 121 | --template-file \ 123 | --capabilities CAPABILITY_IAM 124 | ``` 125 | 126 | After executing the cloudformation deployment, you'll need to navigate to IAM > Roles > STACK_NAME-LambdaFunctionRole-UNIQUE_STRING and attach the `AmazonS3FullAccess` and `AmazonDynamoDBFullAccess` policies to enable the Lambda function with sufficient permissions. 127 | 128 | ### (Blog reference) Manually creating the API Gateway and Lambda deployment 129 | This section has been retained for users who want to refer to the blog post or want to manually 130 | create the API Gateway and Lambda resources 131 | 132 | #### Lambda 133 | 1. Create a new Lambda function. I've provided the function, so we can skip a blueprint. 134 | 2. Give it a name and description. Use Python 2.7 for runtime. 135 | 3. Use the given Lambda function, `lambda_function.py`. Read through the module and provide a few variables: Twilio credentials, DynamoDB table name & region and S3 ingest bucket. We will upload as a .zip because our function requires a few external libraries, such as Twilio Python SDK. Compress httplib2, pytz, twilio & lambda_function.py and upload as a .zip file. 136 | 4. The Lambda function handler tells Lambda what .py module to use and corresponding method. Ex. `main_function.lambda_handler` would call the method `def lambda_handler()` inside `main_function.py`. Let's call it `lambda_function.lambda_handler` to match `lambda_function.py`. 137 | 5. Select the role we created earlier in the housekeeping steps. 138 | 6. In advanced settings, I recommend changing Timeout to 10 seconds (httplib2 is a bit needy). Currently, max timeout is 60 seconds. 139 | 7. Review & create function. 140 | 141 | #### API Gateway 142 | 1. Create a new API. Give it a name and description. This will be our RESTful endpoint. 143 | 2. Create a resource. The path should be `/addphoto` , for example. 144 | 3. We need to add a method to this resource. Create a GET method with Lambda integration and select the function we created earlier. API Gateway isn't able to process POST requests that are URL encoded, so we are using GET as a workaround. 145 | 4. Now let's setup the Integration Request. Twilio's GET request will be of type `application-x-www-form-urlencoded`. This Integration step will map this type to a JSON object, which Lambda requires. In the Integration Requests page create a mapping template. Content-type is `application/json` and template: 146 | ``` 147 | { 148 | "body" : "$input.params('Body')", 149 | "fromNumber" : "$input.params('From')", 150 | "image" : "$input.params('MediaUrl0')", 151 | "numMedia" : "$input.params('NumMedia')" 152 | } 153 | ``` 154 | 155 | More on [Intergration Requests](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html). 156 | `$input.params()` parse the request object for the corresponding variable and allows the mapping template to build a JSON object. 157 | [Screenshot](https://s3.amazonaws.com/smallya-useast-1/twilio-apig/integration_request.png) 158 | 5. Let's ensure the response is correct. Twilio requires valid XML. Change the response model for 200 to Content-type: `application/xml`. Leave models empty. 159 | 6. Lambda cannot return proper XML, so API Gateway needs to build this. This is done in Integration response as another mapping template. This time we want to create Content-type: application/xml and template: 160 | 161 | ``` 162 | #set($inputRoot = $input.path('$')) 163 | 164 | 165 | 166 | 167 | $inputRoot 168 | 169 | 170 | 171 | ``` 172 | Our Lambda function solely returns a string of the SMS body. Here we build the XML object and use `$inputRoot` as the string. 173 | 7. Now let's deploy this API, so we can test it! Click the Deploy API button. 174 | 175 | ## Connecting the dots & Testing 176 | 177 | 1. We should now have a publically accessible GET endpoint. Ex: `https://xxxx.execute-api.us-west-2.amazonaws.com/prod/addphoto` 178 | 2. Point your Twilio number to this endpoint. [Screenshot](https://s3-us-west-2.amazonaws.com/mauerbac-hosting/twilio.png) Recommend creating a Progammable SMS > Messaging Service (Inbound Settings > Request URL) and assigning it your Phone Number > Messaging > Messaging Service > `MESSAGING_SERVICE_NAME`. 179 | 3. The app should now be connected. Let's review: Twilio sends a GET request with MMS image, fromNumber and body to API Gateway. API Gateway transforms the GET request into a JSON object, which is passed to a Lambda function. Lambda processes the object and writes the user to DynamoDB and writes the image to S3. Lambda returns a string which API Gateway uses to create an XML object for Twilio's response to the user. 180 | 4. First, let's test the Lambda function. Click the Actions dropdown and Configure test event. We need to simulate the JSON object passed by API Gateway. Example: 181 | ``` 182 | { 183 | "body" : "hello", 184 | "fromNumber" : "+19145554224" , 185 | "image" : "https://api.twilio.com/2010-04-01/Accounts/AC361180d5a1fc4530bdeefb7fbba22338/Messages/MM7ab00379ec67dd1391a2b13388dfd2c0/Media/ME7a70cb396964e377bab09ef6c09eda2a", 186 | "numMedia" : "1" 187 | } 188 | ``` 189 | Click Test. At the bottom of the page you view Execution result and the log output in Cloudwatch logs. This is very helpful for debugging. 190 | 5. Testing API Gateway requires a client that sends requests to the endpoint. I personally like the Chrome Extension [Advanced Rest Client](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=en-US) Send the endpoint a GET request and view its response. Ensure the S3 link works. You can also test by sending an MMS to phone number and checking the Twilio logs. 191 | 192 | ##Troubleshooting 193 | 194 | 1. Ensure your Lambda function is using the correct IAM role. The role must have the ability to write/read to DynamoDB and S3. 195 | 2. All Lambda interactions are logged in Cloudwatch logs. View the logs for debugging. 196 | 3. Lambda/API Gateway Forums 197 | 198 | **Please Note:** Twilio is a 3rd party service that has terms of use that the user is solely responsible for complying with (https://www.twilio.com/legal/tos) 199 | -------------------------------------------------------------------------------- /basic_lambda_function.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Basic Twilio handler function 3 | ''' 4 | 5 | import boto3 6 | import random 7 | import StringIO 8 | import urllib2 9 | 10 | from boto3.dynamodb.conditions import Key 11 | from boto3.session import Session 12 | 13 | # create an S3 & Dynamo session 14 | s3 = boto3.resource('s3') 15 | session = Session() 16 | 17 | def lambda_handler(event, context): 18 | 19 | message = event['body'] 20 | from_number = event['fromNumber'] 21 | pic_url = event['image'] 22 | num_media = event['numMedia'] 23 | 24 | if num_media != '0': 25 | twilio_resp = "Hi I got an image @ location %s" % pic_url 26 | else: 27 | twilio_resp = 'No image found' 28 | 29 | return twilio_resp 30 | -------------------------------------------------------------------------------- /create_lambda_package.sh: -------------------------------------------------------------------------------- 1 | zip -9 lambda_function.zip lambda_function.py 2 | 3 | # Log in to the Virtual Environment 4 | cd ./lib/python2.7/site-packages 5 | 6 | zip -r9 ../../../lambda_function.zip * 7 | 8 | cd ../../../lib64/python2.7/site-packages 9 | 10 | zip -r9 ../../../lambda_function.zip * 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /lambda_function.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Twilio Ingest Lambda handler code 3 | 4 | Recieve an image URL from Twilio 5 | Apply a filter to the image and store in S3 6 | Return filtered image URL to the user 7 | 8 | ''' 9 | 10 | import boto3 11 | import random 12 | import StringIO 13 | import urllib2 14 | 15 | from boto3.dynamodb.conditions import Key 16 | from boto3.session import Session 17 | from PIL import Image, ImageOps 18 | from twilio.rest import TwilioRestClient 19 | 20 | 21 | # create Twilio session 22 | # Add Twilio Keys 23 | account_sid = "account_sid" 24 | auth_token = "auth_token" 25 | client = TwilioRestClient(account_sid, auth_token) 26 | 27 | # create an S3 & Dynamo session 28 | s3 = boto3.resource('s3') 29 | session = Session() 30 | # Add Dynamo Region and Table 31 | dynamodb = boto3.resource('dynamodb', '_region_') 32 | table_users = dynamodb.Table('table_name') 33 | 34 | def sample_filter(im): 35 | ''' 36 | A simple filter to be applied to the image 37 | ''' 38 | black = "#000099" 39 | white= "#99CCFF" 40 | filter_image = ImageOps.colorize(ImageOps.grayscale(im), black, white) 41 | return filter_image 42 | 43 | def lambda_handler(event, context): 44 | 45 | message = event['body'] 46 | from_number = event['fromNumber'] 47 | pic_url = event['image'] 48 | num_media = event['numMedia'] 49 | 50 | # check if we have their number 51 | response_dynamo = table_users.query(KeyConditionExpression=Key('fromNumber').eq(from_number)) 52 | 53 | # a new user 54 | if response_dynamo['Count'] == 0: 55 | if len(message) == 0: 56 | return "Please send us an SMS with your name first!" 57 | else: 58 | name = message.split(" ") 59 | table_users.put_item(Item={'fromNumber': from_number, 'name': name[0]}) 60 | return "We've added {0} to the system! Now send us a selfie! ".format(name[0]) 61 | else: 62 | name = response_dynamo['Items'][0]['name'] 63 | 64 | if num_media != '0': 65 | # get photo from s3 66 | twilio_pic = urllib2.Request(pic_url, headers={'User-Agent': "Magic Browser"}) 67 | image = urllib2.urlopen(twilio_pic) 68 | 69 | # Apply an Image filter 70 | im_buffer = image.read() 71 | im = Image.open(StringIO.StringIO(im_buffer)) 72 | im = sample_filter(im) 73 | 74 | # Add to S3 Bucket 75 | bucket = "s3_bucket_name" 76 | key = "ingest-images/" + str(from_number.replace('+', '')) + "/" + str(random.getrandbits(50)) + ".png" 77 | resp_url = "https://s3.amazonaws.com/{0}/{1}".format(bucket, str(key)) 78 | twilio_resp = "Hi {0}, your S3 link: ".format(name) + resp_url 79 | 80 | # build meta data 81 | m_data = {'fromNumber': from_number, 'url': resp_url, 'name': name} 82 | output = StringIO.StringIO() 83 | im.save(output, format="PNG") 84 | im_data = output.getvalue() 85 | output.close() 86 | 87 | s3.Bucket(bucket).put_object(Key=key, Body=im_data, ACL='public-read', ContentType='image/png', Metadata=m_data) 88 | else: 89 | 90 | twilio_resp = "No image found, try sending a selfie!" 91 | 92 | return twilio_resp 93 | -------------------------------------------------------------------------------- /sam/swagger.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: "2.0" 3 | info: 4 | version: "2016-12-20T18:27:47Z" 5 | title: "twilio-apigateway" 6 | basePath: "/Prod" 7 | schemes: 8 | - "https" 9 | paths: 10 | /addphoto: 11 | get: 12 | consumes: 13 | - "application/json" 14 | produces: 15 | - "application/xml" 16 | responses: 17 | 200: 18 | description: "200 response" 19 | headers: 20 | Content-Type: 21 | type: "string" 22 | x-amazon-apigateway-integration: 23 | responses: 24 | default: 25 | statusCode: "200" 26 | responseTemplates: 27 | application/xml: "#set($inputRoot = $input.path('$'))\n\n\n \n \n\ 29 | \ $inputRoot\n \n \n " 30 | responseParameters: 31 | method.response.header.Content-Type: "'application/xml'" 32 | requestTemplates: 33 | application/json: "{\n \"body\" : \"$input.params('Body')\",\n \"\ 34 | fromNumber\" : \"$input.params('From')\",\n \"image\" : \"$input.params('MediaUrl0')\"\ 35 | ,\n \"numMedia\" : \"$input.params('NumMedia')\"\n}" 36 | # NOTE: Replace <> and <> fields 37 | uri: arn:aws:apigateway:<>:lambda:path/2015-03-31/functions/arn:aws:lambda:<>:<>:function:${stageVariables.LambdaFunctionName}/invocations 38 | passthroughBehavior: "when_no_templates" 39 | httpMethod: "POST" 40 | contentHandling: "CONVERT_TO_TEXT" 41 | type: "aws" 42 | -------------------------------------------------------------------------------- /sam/template.yaml: -------------------------------------------------------------------------------- 1 | # To deploy for the first time, and for each update, 2 | # run both of the following commands in order: 3 | # 4 | # aws cloudformation package \ 5 | # --template-file template.yaml \ 6 | # --output-template-file template-out.yaml \ 7 | # --s3-bucket 8 | # 9 | # aws cloudformation deploy \ 10 | # --template-file \ 12 | # --capabilities CAPABILITY_IAM 13 | 14 | AWSTemplateFormatVersion: '2010-09-09' 15 | Transform: 'AWS::Serverless-2016-10-31' 16 | Description: Lambda handler for API Gateway - Twilio integration 17 | Resources: 18 | LambdaFunction: 19 | Type: 'AWS::Serverless::Function' 20 | Properties: 21 | Handler: lambda_function.lambda_handler 22 | Runtime: python2.7 23 | CodeUri: s3:///lambda_function.zip #UPDATE 24 | Description: Lambda handler for API Gateway - Twilio integration 25 | MemorySize: 256 26 | Timeout: 60 27 | Events: 28 | AddPhotoApi: 29 | Type: Api 30 | Properties: 31 | RestApiId: !Ref ApiGatewayApi 32 | Path: /addphoto 33 | Method: GET 34 | 35 | ApiGatewayApi: 36 | Type: AWS::Serverless::Api 37 | Properties: 38 | DefinitionUri: s3:///swagger.yaml #UPDATE 39 | StageName: Prod 40 | Variables: 41 | # NOTE: Before using this template, replace the <> and <> fields 42 | # in Lambda integration URI in the swagger file to region and accountId 43 | # you are deploying to 44 | LambdaFunctionName: !Ref LambdaFunction 45 | 46 | Outputs: 47 | ApiUrl: 48 | Description: URL of your API endpoint 49 | Value: !Join 50 | - '' 51 | - - https:// 52 | - !Ref ApiGatewayApi 53 | - '.execute-api.' 54 | - !Ref 'AWS::Region' 55 | - '.amazonaws.com/Prod' 56 | --------------------------------------------------------------------------------