aws lambda使用_如何使用AWS Lambda和S3构建无服务器URL缩短器

aws lambda使用

by Daniel Ireson

丹尼尔·埃里森(Daniel Ireson)

如何使用AWS Lambda和S3构建无服务器URL缩短器 (How to build a Serverless URL shortener using AWS Lambda and S3)

Throughout this post we’ll be building a serverless URL shortener using Amazon Web Services (AWS) Lambda and S3. Whilst you don’t require any previous experience with AWS, I’m assuming some familiarity with ES6 JavaScript and Node.js.

在整个本文中,我们将使用Amazon Web Services(AWS)Lambda和S3构建无服务器URL缩短器。 尽管您不需要任何过往的AWS经验,但我假设您对ES6 JavaScript和Node.js有所了解。

Ironically, the URLs that will be generated from our URL shortener will often be longer than the URLs that they redirect to - this is because we’re using the default S3 bucket website address. Towards the end of the post I’ll discuss how you can add a custom domain to get around this limitation.

具有讽刺意味的是,从我们的URL缩短器生成的URL通常会比重定向到的URL长,这是因为我们使用的是默认的S3存储桶网站地址。 在文章结尾处,我将讨论如何添加自定义域来解决此限制。

观看演示 (View the demo)
在Github上查看代码 (See the code on Github)

It’s relatively easy to get started with AWS and yet there’s definitely a perceived complexity. The number of available services can be daunting to pick from as many of them overlap in functionality. The slow and unintuitive AWS Management Console doesn’t help, nor does the text-heavy online documentation. But throughout this post, I hope to demonstrate that the best way to adopt AWS services is to use an incremental approach and you can get started by using only a handful of services.

开始使用AWS相对容易,但是肯定存在复杂性。 从功能重叠的众多服务中选择可用的服务数量可能令人生畏。 缓慢而笨拙的AWS管理控制台无济于事,繁琐的在线文档也无济于事。 但是,在整个这篇文章中,我希望证明采用AWS服务的最佳方法是使用增量方法,并且只需使用少数几个服务就可以入门。

We’ll be using the Serverless Framework to interact with AWS and so there’ll be no need to login to the AWS Management Console. The Serverless Framework provides an abstraction over AWS and helps provide project structure and sensible configuration defaults. If you want to learn more before we get started you should have a read of their docs.

我们将使用无服务器框架与AWS进行交互,因此无需登录到AWS管理控制台。 无服务器框架提供了对AWS的抽象,并有助于提供项目结构和合理的配置默认值。 如果您想在开始之前了解更多信息,则应该阅读他们的文档

建筑 (Architecture)

Before jumping into any development let’s first look at the AWS services we’ll be using to build our URL shortener.

在进行任何开发之前,我们首先来看一下将用于构建URL缩短器的AWS服务。

To host our website we’ll use the Amazon S3 file storage service. We’ll configure our S3 bucket, which can be thought of as a top-level folder, to serve a static website. The website will consist of static content and client-side scripts. There’s no capability to execute server-side code (like PHP, Ruby or Java for example), but that’s fine for our use case.

要托管我们的网站,我们将使用Amazon S3文件存储服务。 我们将配置S3存储桶(可以将其视为顶级文件夹)来为静态网站提供服务。 该网站将包含静态内容和客户端脚本。 没有执行服务器端代码的功能(例如,PHP,Ruby或Java),但这对我们的用例来说很好。

We’ll also be using a little known feature of S3 that allows you to setup forwarding for objects inside S3 buckets simply by adding a Website-Redirect-Location value to the metadata of the object. Setting this to a URL will have browsers redirected through a HTTP 301 response and the location header.

我们还将使用S3的一个鲜为人知的功能 ,该功能使您只需在对象的元数据中添加一个Website-Redirect-Location值即可为S3存储桶中的对象设置转发。 将此设置为URL将使浏览器通过HTTP 301响应和location标头重定向。

The URL of an S3 object is composed of the S3 bucket address followed by the object’s name.

S3对象的URL由S3存储桶地址及其后的对象名称组成。

http://[bucket-name].s3-website-eu-west-1.amazonaws.com/[object-name]

The following is an example of the format of an S3 bucket object for the eu-west-1 region.

以下是eu-west-1区域的S3存储桶对象格式的示例。

http://serverless-url-shortener.s3-website-eu-west-1.amazonaws.com/6GpLcdl

This object name “6GpLcdl” at the end of the URL in the example above becomes the shortcode for our shortened URLs. Using this functionality we get native URL redirection as well as storage capabilities. We don’t require a database to store the details of which shortcode points to which URL as this information will instead be stored with the object itself.

上例中URL末尾的对象名称“ 6GpLcdl”成为我们缩短的URL的简码。 使用此功能,我们可以获得本机URL重定向以及存储功能。 我们不需要数据库来存储哪些短代码指向哪个URL的详细信息,因为此信息将与对象本身一起存储。

We’ll create a Lambda function for saving these S3 objects with the appropriate metadata to our S3 bucket.

我们将创建一个Lambda函数,以将带有适当元数据的这些S3对象保存到我们的S3存储桶中。

You could alternatively use the AWS SDK client-side in the browser to save the objects. But it’s better to extract this functionality into a separate service. It provides the advantage of not having to worry about exposing security credentials and is more extendable in the future. We’ll map the Lambda function to an endpoint on API Gateway so it’s accessible through an API call.

您也可以在浏览器中使用AWS开发工具包客户端来保存对象。 但是最好将此功能提取到单独的服务中。 它具有不必担心公开安全证书的优点,并且将来可以扩展。 我们会将Lambda函数映射到API网关上的终结点,以便可以通过API调用对其进行访问。

入门 (Getting started)

Head on over to the Serverless Framework docs and run through their quick-start guide. As part of the setup process, you’ll have to install the AWS CLI and configure your AWS credentials.

转至无服务器框架文档,并运行其快速入门指南。 在设置过程中,您必须安装AWS CLI并配置AWS凭证。

Start by creating a package.json file at the root of the project.

首先在项目的根目录下创建一个package.json文件。

{  "name": "serverless-url-shortener",  "scripts": {},  "dependencies": {}}

We know we’ll need to use the AWS SDK, so go ahead and install it from NPM now by entering the following command.

我们知道我们需要使用AWS开发工具包 ,因此请继续并通过输入以下命令从NPM安装它。

npm install aws-sdk --save

npm install aws-sdk --save

Now create a config.json file also at the project root. We’ll use this to store customisable user options in JSON format.

现在也在项目根目录下创建一个config.json文件。 我们将使用它以JSON格式存储可自定义的用户选项。

Add the following keys with values appropriate to your setup.

添加以下键,并为其设置合适的值。

  • BUCKET - the name you want to use for your S3 bucket. This will become part of the short URL if you choose not to add a custom domain. It has to be unique to the region you’re deploying to so don’t pick something too generic. But don’t worry, if your chosen bucket name is already in use you’ll be warned through the Serverless CLI at deployment.

    -你想用你的S3存储的名称。 如果您选择不添加自定义域,则它将成为短URL的一部分。 它对于您要部署的区域必须是唯一的,因此不要选择太通用的东西。 但请放心,如果您选择的存储桶名称已在使用中,则在部署时会通过无服务器CLI发出警告。

  • REGION - the AWS region you wish to deploy to. It’s best to pick the region closest to your users for performance reasons. If you’re just following along with the tutorial I’ll be using eu-west-1.

    区域 -您希望部署到的AWS区域 。 出于性能方面的考虑,最好选择最接近用户的区域。 如果您只是跟随本教程,那么我将使用eu-west-1

  • STAGE - the stage to deploy to. Typically you’d have a staging environment that replicates the same configuration as your production environment. This allows you to test software releases in a non-destructive manner. As this is a tutorial, I’ll be deploying to the dev stage.

    STAGE-部署到的阶段。 通常,您将拥有一个临时环境,该临时环境复制与生产环境相同的配置。 这使您可以以非破坏性的方式测试软件版本。 由于这是一个教程,因此我将部署到dev阶段。

Your config.json file should look similar to the following once complete.

完成后,您的config.json文件应类似于以下内容。

{  "BUCKET": "your-bucket-name",  "REGION": "eu-west-1",  "STAGE": "dev",}

Next, create another file at the project root, serverless.yml. This will hold our Serverless Framework configuration formatted in the YAML markup language.

接下来,在项目根目录serverless.yml创建另一个文件。 这将保留以YAML标记语言格式的无服务器框架配置。

Inside this file we’ll start by defining our environment. Notice how we can reference variables stored earlier in config.json.

在此文件中,我们将从定义环境开始。 注意我们如何引用之前存储在config.json变量。

service: serverless-url-shortenerprovider:  name: aws  runtime: nodejs6.10  stage: ${file(config.json):STAGE}  region: ${file(config.json):REGION}  iamRoleStatements:    - Effect: Allow      Action:        - s3:PutObject      Resource: "arn:aws:s3:::${file(config.json):BUCKET}/*"

The iamRoleStatements section refers to Identity and Access Management which is used to set up Lambda permissions. Here we give the Lambda write-access to our S3 bucket.

iamRoleStatements部分引用身份和访问管理 ,该身份和访问管理用于设置Lambda权限。 在这里,我们为Lambda提供了对S3存储桶的写访问权限。

To save objects we need permission to execute the s3:PutObject action. Other permissions can be added here if they are required by your project. Refer to the S3 docs for other available actions.

为了保存对象,我们需要权限来执行s3:PutObject操作。 如果您的项目需要其他权限,可以在此处添加其他权限。 有关其他可用操作,请参考S3文档

The Resource value is set to the S3 bucket’s Amazon Resource Name, which is used to uniquely identify a particular AWS resource. The format of this identifier depends upon the AWS service that is being referred to, but generally they have the following format.

Resource值设置为S3存储桶的Amazon Resource Name ,用于唯一标识特定的AWS资源。 该标识符的格式取决于所引用的AWS服务,但通常它们具有以下格式。

arn:partition:service:region:account-id:resource

Underneath provider append our functions configuration.

provider下面附加我们的functions配置。

functions:  store:    handler: api.handle    events:      - http:          path: /          method: post          cors: true

Here we define the API configuration and map our Lambda to an HTTP POST event at the API’s base URL. A handler with the value api.handle refers to a function named handle that is exported from api.js (we don’t need the js file extension because earlier in serverless.yml we set the runtime to nodejs6.10).

在这里,我们定义API配置,并将Lambda映射到API基本URL上的HTTP POST事件。 一个handler与价值api.handle指函数命名的handle是从出口api.js (我们不需要js文件扩展名,因为先前在serverless.yml我们设置运行时nodejs6.10 )。

Lambda is event based and so functions only get executed based on predefined triggers. Here we’ve defined a HTTP event but this could have also have been an event trigged by a DynamoDB table or an SQS queue.

Lambda是基于事件的,因此仅基于预定义的触发器才能执行功能。 在这里,我们定义了一个HTTP事件,但这也可能是由DynamoDB表或SQS队列触发的事件。

Next in serverless.yml we define the AWS resources to be instantiated for us on deployment using CloudFormation. It’s worth mentioning that you don’t necessarily have to setup resources this way, you could also create them using the AWS Management Console. Providing the correct access permissions are in place it doesn’t matter how the resources are created. But in defining the required services in serverless.yml you’re defining your ‘infrastructure as code’ and obtain a number of benefits in doing so.

接下来,在serverless.yml我们定义要使用CloudFormation在部署时实例化的AWS资源。 值得一提的是,您不一定必须以这种方式设置资源,也可以使用AWS管理控制台创建它们。 只要提供了正确的访问权限,无论如何创建资源都没有关系。 但是,在serverless.yml中定义所需的服务时,您将“基础结构定义为代码”,并从中获得许多好处。

“Infrastructure as code is the approach to defining computing and network infrastructure through source code that can then be treated just like any software system. Such code can be kept in source control to allow auditability and ReproducibleBuilds, subject to testing practices, and the full discipline of ContinuousDelivery.”
“基础架构即代码是通过源代码定义计算和网络基础结构的方法,然后可以像对待任何软件系统一样对待它们。 这样的代码可以保留在源代码控制中,以允许可审核性和ReproducibleBuilds(受测试实践的约束)和ContinuousDelivery的完整规范。”
- Martin Fowler
-马丁·福勒

Go ahead and add the resources configuration.

继续并添加resources配置。

resources:  Resources:    ServerlessRedirectS3Bucket:      Type: AWS::S3::Bucket      Properties:        BucketName: ${file(config.json):BUCKET}        AccessControl: PublicRead        WebsiteConfiguration:          IndexDocument: index.html    ServerlessRedirectS3BucketPolicy:      Type: AWS::S3::BucketPolicy      Properties:        Bucket: ${file(config.json):BUCKET}        PolicyDocument:          Statement:          - Action:            - s3:GetObject            Effect: Allow            Resource:            - arn:aws:s3:::${file(config.json):BUCKET}/*            Principal: "*"

We ask for an S3 bucket resource configured to use static site hosting with index.html as the root document. S3 buckets for good reason are private by default and so we need to create an S3 bucket policy which allows public access to it. Without this policy website visitors would instead by shown an unauthenticated error message.

我们要求提供一个S3存储桶资源,该资源配置为使用以index.html作为根文档的静态站点托管。 出于充分原因,S3存储桶默认情况下是私有的,因此我们需要创建一个S3存储桶策略,以允许对其进行公共访问。 如果没有此政策,网站访问者将显示未经身份验证的错误消息。

构建API (Building the API)

Our Lambda function is responsible for four tasks.

我们的Lambda函数负责四个任务。

  1. Grabbing the URL to shorten from the user’s form submission.

    抓取URL以缩短用户提交表单的时间。
  2. Generating a unique shortcode for the URL.

    为URL生成唯一的简码。
  3. Saving the appropriate redirect object to S3.

    将适当的重定向对象保存到S3。
  4. Returning the object’s path to the client.

    返回对象的路径到客户端。
创建处理程序 (Create the handler)

Create a new file called api.js and export an arrow function named handle which takes three arguments: event, context and callback. These will be provided by AWS when the handler is invoked. This file is a Node.js script and in order to export the arrow function you need to append it to module.exports.

创建一个名为api.js的新文件,并导出一个名为handle的箭头函数,该函数需要三个参数: event contextcallback 这些将由AWS在调用处理程序时提供。 该文件是Node.js脚本,为了导出箭头功能,您需要将其附加到module.exports

module.exports.handle = (event, context, callback) => {
}

This handler will get invoked when a HTTP POST request is made to our endpoint. To return an API response we need to use the supplied callback function provided as the third arrow function argument. It’s an error-first callback which takes two arguments. If the request completed successfully null should be passed in as the first argument. The response object passed in as the second argument determines the type of response to be returned to the user. Generating a response is as simple as providing a statusCode and body as is shown in the example below.

向端点发出HTTP POST请求时,将调用此处理程序。 要返回API响应,我们需要使用提供的回调函数作为第三个箭头函数参数。 这是一个错误优先的回调 ,需要两个参数。 如果请求成功完成,则应将null用作第一个参数。 作为第二个参数传入的响应对象确定要返回给用户的响应的类型。 生成响应就像提供一个statusCodebody一样简单,如下面的示例所示。

const response = {  statusCode: 201,  body: JSON.stringify({ "shortUrl": "http://example.com" })}
callback(null, response)

The context object passed in as the second argument to the handler contains run-time information which for this tutorial we don’t need access to. We do however need to make use of the event passed in as the first argument as this contains the form submission with the URL to shorten.

作为第二个参数传递给处理程序的context对象包含运行时信息,对于本教程,我们不需要访问这些信息。 但是,我们确实需要利用作为第一个参数传入的event ,因为它包含带有缩短URL的表单提交。

解析请求 (Parse the request)

Below is an example of an API Gateway event that will be passed to our handler when a user makes a form submission. As we’re building our URL shortener as a single page application we’ll be submitting the form using JavaScript and hence the content type will be application/json rather than application/x-www-form-urlencoded.

以下是一个API网关事件的示例,该事件将在用户提交表单时传递给我们的处理程序。 当我们将URL缩短器构建为单页应用程序时,我们将使用JavaScript提交表单,因此内容类型将为application/json而不是application/x-www-form-urlencoded

{     resource:'/',   path:'/',   httpMethod:'POST',   headers: {      Accept:'*/*',      'Accept-Encoding':'gzip, deflate',      'cache-control':'no-cache',      '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':'GB',      'content-type':'application/json',      Host:'',      'User-Agent':'',      'X-Amz-Cf-Id':'',      'X-Amzn-Trace-Id':'',      'X-Forwarded-For':'',      'X-Forwarded-Port':'443',      'X-Forwarded-Proto':'https'   },   queryStringParameters:null,   pathParameters:{},   stageVariables:null,   requestContext: {        path:'/dev',      accountId:'',      resourceId:'',      stage:'dev',      requestId:'',      identity:{           cognitoIdentityPoolId:null,         accountId:null,         cognitoIdentityId:null,         caller:null,         apiKey:'',         sourceIp:'',         accessKey:null,         cognitoAuthenticationType:null,         cognitoAuthenticationProvider:null,         userArn:null,         userAgent:'',         user:null      },      resourcePath:'/',      httpMethod:'POST',      apiId:''   },   body:'{"url":"http://example.com"}',   isBase64Encoded:false}

We only need the form submission from the event, which we can get by looking at the request body. The request body is stored as a stringified JavaScript object which we can grab inside of our handler using JSON.parse(). Taking advantage of JavaScript short-circuit evaluation we can set a default value of an empty string for cases where a URL hasn’t been sent as part of the form submission. This allows us to treat instances where the URL is missing and where the URL is an empty string equally.

我们只需要从事件中提交表单,就可以通过查看请求body来获得。 请求主体存储为一个字符串化JavaScript对象,我们可以使用JSON.parse()其捕获到处理程序中。 利用JavaScript短路评估,我们可以为未作为表单提交的一部分发送URL的情况设置一个空字符串的默认值。 这使我们可以处理URL丢失和URL为空字符串的情况。

module.exports.handle = (event, context, callback) => {  let longUrl = JSON.parse(event.body).url || ''}
验证URL (Validate the URL)

Let’s add some basic validation to check that the provided URL looks legitimate. There are multiple approaches that could be taken to achieve this. But for the purpose of this tutorial we’ll keep it simple and use the built-in Node.js URL module. We’ll build our validation to return a resolved promise on a valid URL and return a rejected promise on an invalid URL. Promises in JavaScript can be sequentially chained so that the resolution of one promise gets passed to the success handler of the next. We’ll be using this attribute of promises to structure our handler. Let’s write the validate function using promises.

让我们添加一些基本的验证来检查所提供的URL是否合法。 可以采用多种方法来实现这一目标。 但出于本教程的目的,我们将使其保持简单并使用内置的Node.js URL 模块 。 我们将建立验证以在有效的URL上返回已解决的承诺,并在无效的URL上返回被拒绝的承诺。 可以按顺序链接JavaScript中的Promise,以便将一个Promise的解决方案传递给下一个Promise的成功处理程序。 我们将使用promises的这个属性来构造我们的处理程序。 让我们使用promise编写validate函数。

const url = require('url')
function validate (longUrl) {  if (longUrl === '') {    return Promise.reject({      statusCode: 400,      message: 'URL is required'    })  }
let parsedUrl = url.parse(longUrl)  if (parsedUrl.protocol === null || parsedUrl.host === null) {    return Promise.reject({      statusCode: 400,      message: 'URL is invalid'    })  }
return Promise.resolve(longUrl)}

In our validate function we first check that the URL isn’t set to an empty string. If it is we return a rejected promise. Notice how the rejected value is an object containing a status code and message. We’ll use this later to build an appropriate API response. Calling parse on the Node.js url module returns a URL object with information that could be extracted from the URL that was passed in as a string argument. As part of our basic URL validation we simply check to see whether a protocol (For example, ‘http’) and a host (like ‘example.com’) could be extracted. If either of these values is null on the returned URL object, we assume that the URL is invalid. If the URL is valid we return it as part of a resolved promise.

在我们的validate函数中,我们首先检查URL是否未设置为空字符串。 如果是这样,我们将返回被拒绝的承诺。 请注意,拒绝值是一个包含状态代码和消息的对象。 稍后我们将使用它来构建适当的API响应。 在Node.js url模块上调用parse返回一个URL对象,该对象包含可以从作为字符串参数传入的URL中提取的信息。 作为基本URL验证的一部分,我们仅检查是否可以提取协议(例如,“ http”)和主机(例如,“ example.com”)。 如果在返回的URL对象上这些值中的任何一个为null ,则我们假定URL无效。 如果网址有效,我们会将其作为已解决的承诺的一部分返回。

返回回应 (Returning a response)

After grabbing the URL from the request we call validate and for each additional handler step that’s required we’ll return a new promise in the previous promise’s success handler. The final success handler is responsible for returning an API response through the handle’s callback argument. It will be invoked for both error API responses generated from rejected promises as well as successful API responses.

从请求中获取URL后,我们称为validate ,对于所需的每个其他处理程序步骤,我们将在先前的promise的成功处理程序中返回一个新的promise。 最终成功处理程序负责通过句柄的回调参数返回API响应。 它会针对拒绝承诺产生的错误API响应以及成功的API响应进行调用。

module.exports.handle = (event, context, callback) => {  let longUrl = JSON.parse(event.body).url || ''  validate(longUrl)    .then(function(path) {      let response = buildResponse(200, 'success', path)      return Promise.resolve(response)    })    .catch(function(err) {      let response = buildResponse(err.statusCode, err.message)      return Promise.resolve(response)    })    .then(function(response) {      callback(null, response)    })}
function buildResponse (statusCode, message, path = false) {  let body = { message }  if (path) body['path'] = path    return {    headers: {      'Access-Control-Allow-Origin': '*'    },    statusCode: statusCode,    body: JSON.stringify(body)  }}
生成URL简码 (Generate a URL shortcode)

The API needs to be able to generate unique URL shortcodes, which are represented as filenames in the S3 bucket. As a shortcode is just a filename there’s a great degree of flexibility in how it’s composed. For our shortcode we’ll use a 7 digit alphanumeric string consisting of both uppercase and lowercase characters, this translates to 62 possible combinations for each character. We’ll use recursion to build up the shortcode by selecting one character at a time until seven have been selected.

该API需要能够生成唯一的URL短代码,这些短代码在S3存储桶中以文件名表示。 由于简码只是文件名,因此其组成方式具有很大的灵活性。 对于我们的简码,我们将使用由7个数字组成的字母数字字符串,包括大写和小写字符,这将转换为每个字符62种可能的组合。 通过一次选择一个字符直到选择了七个字符,我们将使用递归来构建短代码。

function generatePath (path = '') {  let characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'  let position = Math.floor(Math.random() * characters.length)  let character = characters.charAt(position)
if (path.length === 7) {  return path}
return generatePath(path + character)}

Whilst the chance of randomly generating the same shortcode is slim (there’s actually a 0.0000000000000000000000008063365516 chance that two shortcodes will be the same), we need to check whether the generated shortcode is already in use, which we can do using the AWS SDK. There’s a headObject method on the S3 service which loads an object’s metadata. We can use this to test whether an object with the same name already exists as when an object isn’t found a promise with the code NotFound is rejected. This rejected promise indicates that the shortcode is free and can be used. Calling headObject is more performant than testing whether the object exists through getObject, which loads the entire object.

尽管随机生成相同的简码的可能性很小(实际上,两个简码的可能性为0.000000000000000000000000808065565516),但我们需要检查生成的简码是否已在使用中,我们可以使用AWS开发工具包进行此操作。 有一个headObject S3服务上的方法,该方法加载对象的元数据。 我们可以使用它来测试是否存在与未找到对象时具有相同名称的对象,但拒绝代码NotFound的承诺。 该拒绝的承诺表明该短代码是免费的并且可以使用。 与通过加载整个对象的getObject测试对象是否存在相比,调用headObject具有更高的性能。

const AWS = require('aws-sdk')const S3 = new AWS.S3()
function isPathFree (path) {  return S3.headObject(buildRedirect(path)).promise()    .then(() => Promise.resolve(false))    .catch(function (err) {      if (err.code == 'NotFound') {        return Promise.resolve(true)      } else {        return Promise.reject(err)      }    })}
function buildRedirect (path, longUrl = false) {  let redirect = {    'Bucket': config.BUCKET,    'Key': path  }
if (longUrl) {    redirect['WebsiteRedirectLocation'] = longUrl  }
return redirect}

We can use isPathFree to recursively find a unique object path.

我们可以使用isPathFree递归地找到唯一的对象路径。

function getPath () {  return new Promise(function (resolve, reject) {    let path = generatePath()    isPathFree(path)      .then(function (isFree) {        return isFree ? resolve(path) : resolve(getPath())      })  })}

Taking advantage of the ability to chain promises we return a new invocation of getPath if isPathFree returns false.

如果isPathFree返回false,则利用链接承诺的功能,我们将返回getPath的新调用。

To save an object after a unique shortcode has been found we just need to call the putObject method on the AWS SDK S3 service. Let’s wrap this up in a function that resolves the shortcode if the putObject method call was successful and returns an error object to build an API response if it didn’t.

要在找到唯一的短代码后保存对象,我们只需要在AWS开发工具包S3服务上调用putObject方法。 让我们将其包装在一个函数中,如果putObject方法调用成功,则该函数将解析短代码,如果未成功,则返回一个错误对象以构建API响应。

function saveRedirect (redirect) {  return S3.putObject(redirect).promise()    .then(() => Promise.resolve(redirect['Key']))    .catch(() => Promise.reject({      statusCode: 500,      message: 'Error saving redirect'  })}

Utilizing the above functions we can add two new promise success handlers to finalise our API endpoint. We need to return getPath from the first promise success handler which will resolve a unique URL shortcode. Returning saveRedirect with a redirect object built using this unique shortcode in the second success handler will save the object to the S3 bucket. This object’s path can then be returned to the client as part of an API response. Our handler should now be complete.

利用以上功能,我们可以添加两个新的Promise成功处理程序来最终确定我们的API端点。 我们需要从第一个Promise成功处理程序返回getPath ,该处理程序将解析唯一的URL短代码。 在第二个成功处理程序中返回带有使用此唯一的短代码构建的重定向对象的saveRedirect会将对象保存到S3存储桶。 然后可以将该对象的路径作为API响应的一部分返回给客户端。 现在,我们的处理程序应该已经完成​​。

module.exports.handle = (event, context, callback) => {  let longUrl = JSON.parse(event.body).url || ''  validate(longUrl)    .then(function () {      return getPath()    })    .then(function (path) {      let redirect = buildRedirect(path, longUrl)      return saveRedirect(redirect)    })    .then(function (path) {      let response = buildResponse(200, 'success', path)      return Promise.resolve(response)    })    .catch(function (err) {      let response = buildResponse(err.statusCode, err.message)      return Promise.resolve(response)    })    .then(function (response) {      callback(null, response)    })}
部署API (Deploy the API)

Run serverless deploy in your terminal to deploy the API to AWS. This will setup our S3 bucket and return the URL of the endpoint. Keep the URL of the endpoint handy as we’ll need it later on.

在终端中运行serverless deploy ,以将API部署到AWS。 这将设置我们的S3存储桶并返回端点的URL。 请将端点的URL放在手边,因为稍后我们将需要它。

Serverless: Packaging service...Serverless: Excluding development dependencies...Serverless: Uploading CloudFormation file to S3...Serverless: Uploading artifacts...Serverless: Uploading service .zip file to S3 (5.44 MB)...Serverless: Validating template...Serverless: Updating Stack...Serverless: Checking Stack update progress.................Serverless: Stack update finished...Service Informationservice: serverless-url-shortenerstage: devregion: eu-west-1stack: serverless-url-shortener-devapi keys:  Noneendpoints:  POST - https://t2fgbcl26h.execute-api.eu-west-1.amazonaws.com/dev/functions:  store: serverless-url-shortener-dev-storeServerless: Removing old service versions...

创建前端 (Creating the frontend)

To assist with frontend design we’ll be utilizing the PaperCSS framework. We’ll also be grabbing jQuery to simplify working with the DOM and making AJAX queries. It’s worth noting that for a production environment you’d probably want to pull in two lighter dependencies, but as this is just a tutorial I feel it’s acceptable.

为了协助前端设计,我们将使用PaperCSS框架。 我们还将使用jQuery来简化DOM的工作并进行AJAX查询。 值得注意的是,对于生产环境,您可能希望引入两个较轻的依赖关系,但是由于这只是一个教程,我认为这是可以接受的。

Create a static folder so we have somewhere to store our frontend code.

创建一个static文件夹,以便我们可以在某处存储前端代码。

下载依赖项 (Download the dependencies)

Save a copy of paper.min.css and jquery-3.2.1.min.js to our newly created static folder, these are minified versions of the PaperCSS framework and jQuery library respectively.

paper.min.cssjquery-3.2.1.min.js的副本保存到我们新创建的static文件夹中,它们分别是PaperCSS框架和jQuery库的缩小版本。

添加HTML (Add the HTML)

Create a new file called index.html inside the static folder and add the required HTML. We need a form with a URL input and a button to submit the form. We also need somewhere to put the result of any API calls, which for a successful API call would be the shortened URL and for an unsuccessful API call this would be the error message.

static文件夹内创建一个名为index.html的新文件,并添加所需HTML。 我们需要一个带有URL输入的表单和一个按钮来提交表单。 我们还需要在某个地方放置任何API调用的结果,对于成功的API调用,将是缩短的URL;对于失败的API调用,这将是错误消息。

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name=viewport content="width=device-width,initial-scale=1">  <title>Serverless url shortener</title>  <link href="paper.min.css" rel="stylesheet"></head><style>  * {    text-align: center;  }
#message {    display: none;  }</style><body>  <div class="row flex-center">    <div class="col-8 col">      <h2>Serverless url shortener</h2>      <form action="">        <div class="form-group">          <label for="url">Enter URL to shorten</label>          <input             class="input-block"             name="url"             type="url"             id="url"              autocomplete="off"             required>        </div>        <div id="message" class="alert alert-primary"></div>        <input           class="paper-btn"           type="submit"           value="Shorten link">      </form>      <p class="padding-top">        <a href="https://git.io/vbS8I">          View this project on Github        </a>      </p>    </div>  </div></body></html>

Although not shown in the code block above for brevity, be sure you set the form action to the API endpoint that was displayed when you ran serverless deploy. If you’ve no longer got access to your terminal output from that deployment, you can find out the endpoint URL through the serverless info command.

为了简洁起见,虽然上面的代码块中没有显示,但请确保将form操作设置为运行serverless deploy时显示的API端点。 如果您再也无法访问该部署的终端输出,则可以通过serverless info命令serverless info端点URL。

发出API请求 (Make API requests)

Before writing the JavaScript to make requests to our API, let’s first load jQuery by appending a script tag just before </body> and referencing the minified file we downloaded previously.

在编写向我们的API发出请求JavaScript之前,让我们首先通过在</bo dy>之前附加一个脚本标签并引用我们先前下载的缩小文件来加载jQuery。

<script src="jquery-3.2.1.min.js"></script>

Now add another pair of script tags underneath and inside let’s create a function that can be used to display a message to the user using the message div in our template that is set to display:none by default on page load. To show a message we can simply set the text inside of this div using text() and toggle the display using show().

现在,在下面和内部添加另一对脚本标签,让我们创建一个函数,该函数可使用模板中的message div向用户显示消息,该div在页面加载时默认设置为display:none 。 为了显示一条消息,我们可以简单地使用text()在该div内设置文本,并使用show()切换显示。

<script>  function addMessage (text) {    $('#message').text(text).show()  }</script>

Let’s write another function to go inside the same set of script tags that will use jQuery to make requests to our API.

让我们编写另一个函数,将其放入同一组脚本标签中,这些脚本标签将使用jQuery向我们的API发出请求。

function shortenLink (apiUrl, longUrl) {  $.ajax(apiUrl, {    type : 'POST',     data: JSON.stringify({url: longUrl})})    .done(function (responseJSON) {      var protocol = window.location.protocol + '//'      var host = window.location.host + '/'      var shortUrl = protocol + host + responseJSON.path      addMessage(shortUrl)    })    .fail(function (data) {      if (data.status === 400) {        addMessage(data.responseJSON.message)      } else {        addMessage('an unexpected error occurred')      }    })}

This function creates a POST request and sets the request body to a JSON object containing the URL to shorten. If the request completed successfully and a HTTP 2XX status code was returned, it grabs the shortcode from the path key on the response and builds up a fully qualified short URL to present to the user using the addMessage function created previously. If the request was unsuccessful then an error message is displayed.

此函数创建一个POST请求,并将请求主体设置为包含要缩短的URL的JSON对象。 如果请求成功完成,并且返回了HTTP 2XX状态代码,它将从响应上的path键中获取简码,并使用先前创建的addMessage函数构建完全合格的短URL,以呈现给用户。 如果请求失败,则会显示一条错误消息。

Finally we can hook this up to our form by adding an on submit handler. We get the API endpoint URL from the form action attribute and get the URL to shorten from the url form input.

最后,我们可以通过添加on Submit处理程序将其连接到表单。 我们从表单操作属性获取API端点URL,并从url表单输入获取要缩短的url

$('form').submit(function (event) {  event.preventDefault()  addMessage('...')  shortenLink(event.target.action, event.target.url.value)})
部署网站 (Deploy the website)

For website deployment we’ll use the AWS CLI sync command to upload the contents of the static folder to our S3 bucket. Run aws s3 sync static s3://[bucket] in your terminal, replacing [bucket] with your bucket name chosen in config.json. After this completes you should be able to head to your S3 bucket address in a browser to see the URL shortener in action. Public URLs for S3 buckets take the following form.

对于网站部署,我们将使用AWS CLI sync命令将静态文件夹的内容上传到我们的S3存储桶。 在终端中运行aws s3 sync static s3://[bucket] ,将[bucket]替换为在config.json选择的存储桶名称。 完成此操作后,您应该能够在浏览器中访问您的S3存储桶地址,以查看正在使用的URL缩短器。 S3存储桶的公共URL采用以下形式。

http://[bucket].s3-website-[region].amazonaws.com

So after adding your bucket name and region your URL shortener address should look similar to the below.

因此,在添加您的存储桶名称和区域后,您的URL缩短地址应该类似于以下内容。

http://serverless-url-shortener.s3-website-eu-west-1.amazonaws.com

To add a custom domain to your bucket you should follow one of the instructions in this AWS support article. For the easiest option you should set the bucket name to your domain’s www subdomain (for example www.example.com). If you then add a CNAME record in your DNS configuration for the www subdomain and set it to your S3 bucket address, the website should be accessible via your domain. Be sure to also remove any existing A records and bear in mind that this won’t set up a redirection from your root domain to the www subomain. There’s a couple of ways this could be solved which are described in the AWS article.

要将自定义域添加到存储桶中,您应遵循此AWS支持文章中的说明之一。 对于最简单的选项,您应该将存储桶名称设置为您域的www子域(例如www.example.com)。 如果随后在www子域的DNS配置中添加CNAME记录并将其设置为S3存储桶地址,则应该可以通过您的域访问该网站。 确保还删除所有现有的A记录,并记住这不会设置从您的根域到www子域名的重定向。 AWS文章中介绍了解决此问题的几种方法。

结语 (Wrap up)

I hope you found this tutorial useful. The reality is that AWS is incredibly flexible and in this article we went through just one way you can create a URL shortener using Lambda and S3. But there are a variety of other ways the same process could also have been accomplished.

希望本教程对您有所帮助。 实际上,AWS非常灵活,在本文中,我们仅介绍了一种使用Lambda和S3创建URL缩短器的方法。 但是,还有多种其他方式可以完成同一过程。

If you found this interesting you might enjoy one of my previous articles where I created a form forwarding service using AWS Lambda.

如果您发现这很有趣,那么您可能会喜欢我以前的文章之一,其中我使用AWS Lambda创建了表单转发服务。

Introducing Formplug v1, a form forwarding service for AWS LambdaIt’s estimated that approximately 269 billion emails are sent in a single day. Over 10 million were sent as you read…hackernoon.com

引入Formplug v1, 这是 适用于AWS Lambda的表单转发服务。 据估计,每天大约发送2690亿封电子邮件。 当您阅读时,发送了超过一千万的内容…… hackernoon.com

翻译自: https://www.freecodecamp.org/news/how-to-build-a-serverless-url-shortener-using-aws-lambda-and-s3-4fbdf70cbf5c/

aws lambda使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值