使用 F# 探索 Amazon S3。






4.86/5 (11投票s)
本文演示了如何使用 F# 探索 Amazon S3 API,并在短短 45 分钟内取得丰硕成果。
引言
传统的命令式语言教会开发者,程序代码需要仪式。你不能只写一行代码就期望它能做什么。当编程书籍作者解释如何在他们喜欢的语言中显示“Hello world”字符串时,他们实际上会先解释每一条调用都应该包装在一个方法中,并且该方法通常需要一些参数等等。当然,他们还必须让初学者知道如何编译生成的程序以及如何执行它。没有所有这些步骤,世界就无法收到问候。
所有这些步骤都有合理的理由,但结果是,基于主要命令式语言的开发环境并不真正鼓励探索。它最适合大规模开发,但如果开发人员想快速了解新事物,并且在不创建新项目的情况下无法进行,那么他通常会推迟这项工作,直到他有足够的时间或足够多的理由继续进行。
幸运的是,总有一些替代 C# 或 Java 等语言的方案,它们在快速原型设计或技术探索方面效果更好。F# 是其中一个较新的选择,当我最近开始接触 Amazon Simple Storage Service 时,我意识到如果我想快速了解其编程功能,使用 F# 会比用 C# 编写测试程序更有效。
本文的目的是展示使用 F# 快速深入了解未知技术的底层细节有多么容易,以及需要编写多少代码。因此,我将文章内容保持简短,只关注必需的步骤。如果您需要有关 F# 或 Amazon S3 的更多信息,市面上有很多书籍和文章可供选择。只需在 Google 上搜索即可。我不会对我的步骤进行编号,而是提供时间信息:我于 7 月 15 日欧洲中部时间下午 5:00 左右开始练习。我花了大约 45 分钟显示一张从我的 S3 存储桶中提取的图片。所有代码都用 F# 编写。
17:00。获取 Amazon Web Services SDK
Amazon Web Services SDK,也称为 AWS SDK,可在 http://aws.amazon.com/sdkfornet 获取。下载后,其安装程序会复制二进制文件和示例,并选择性地将 AWSSDK.dll 注册到 GAC。
17:10。使用 Reflector 万无一失
在启动 Visual Studio F# 会话之前,我检查了已安装的二进制文件的内容,并将找到的唯一 .NET 程序集 AWSSDK.dll 加载到 Red Gate .NET Reflector 中。也许没有它我也能应付,但会花费更长的时间。创建 S3 客户端会话后,我主要依赖 Visual Studio 中 F# 的 IntelliSense 支持,但花几分钟看看命名空间和类为我提示了入口点。例如,我假设我需要创建一个 AmazonS3Client
实例,并可能使用 Amazon.S3.Model
中的类。
17:15。打开 F# Interactive 会话
我启动了 Visual Studio,并立即体验到了 F# 环境的比较优势。我不需要创建项目——实际上,如果我需要,我不知道应该创建哪种类型的项目:是控制台应用程序?还是 Windows Forms 程序?当时我还不清楚会遇到什么,我从 IDE 中想要的是一个记事本,我可以在其中编写代码行并逐行执行它们。所以 F# 非常符合我的意图:我创建了一个新的 F# 脚本文件,并开始将单个行发送到 FSI 窗口。第一行引用了 AWSSDK 并导入了我认为会有用的 Amazon.S3
命名空间(感谢 Reflector)。
#r "AWSSDK"
open Amazon.S3
之后我导入了另一个命名空间(Amazon.S3.Model
),但上述两条线足以连接到 Amazon S3 存储。
17:20。连接到 Amazon S3 并访问存储桶
有趣的环节开始了。我的猜测是,既然 AmazonS3Client
类有一个带有两个字符串参数的构造函数,那么我就应该使用它,并将参数分配给 AWS 密钥 ID 和密钥。F# IntelliSense 证实了这一点。所以,这是我用 F# 第一次调用 Amazon S3(我已删除实际的访问密钥值)。
let s3Client = new AmazonS3Client("KEY-ID", "SECRET-KEY")
几秒钟后,F# Interactive 给出了响应。
val s3Client : AmazonS3Client
现在我拥有了一个强大的客户端,其中包含许多有趣的待尝试的方法。下一步将是列出可用的存储桶。我只有一个,所以只能检索列表中的第一个元素。
let response = s3Client.ListBuckets()
response.Buckets
这是 FSI 的输出。
val it : System.Collections.Generic.List<model.s3bucket> =
seq
[Amazon.S3.Model.S3Bucket {BucketName = "abilov.com";
CreationDate = "to, 15 jul 2010 10:17:18 GMT";}]
太棒了!这看起来像回事。受到快速成功的鼓舞,我想开始检索存储桶的内容。
17:25。检索存储桶对象的尝试失败
存储桶集合被包装在一个序列中,所以我只需要获取它的头部就可以得到我在 S3 中的唯一存储桶。
let bucket = Seq.head response.Buckets
我曾以为要检索存储桶对象,我需要将获得的存储桶发送给某个方法,但我错了。实际上,我根本不需要获得存储桶——因为我知道存储桶的名称,所以我可以根据名称构造一个 ListObjectRequest
并将其发送给 ListObjects
。但我 5 分钟前并不知道这一点。无论如何,以下是检索存储桶对象的新调用——来自我们家庭照片库的数据文件。
let request = new ListObjectsRequest(BucketName = bucket.BucketName)
let objects = s3Client.ListObjects(request)
砰!!!异常!
System.Net.WebException: The underlying connection was closed: Could not establish
trust relationship for the SSL/TLS secure channel. --->
System.Security.Authentication.AuthenticationException:
The remote certificate is invalid according to the validation procedure.
at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message,
AsyncProtocolRequest asyncRequest, Exception exception)
at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(
ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.StartSendBlob(Byte[] incoming,
Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer,
Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslState.StartReadFrame(Byte[] buffer,
Int32 readBytes, AsyncProtocolRequest asyncRequest)
(The rest of the call stack is skipped)
现在我写这篇文章时,我实际上很高兴我遇到了这个问题。如果一切顺利,你可能会难以置信地看着我的时间信息:这家伙可能知道他在做什么。不,我不知道!所以我不得不四处查看。既然错误是在一些基本步骤之后出现的,我希望我不是唯一遇到它的人,幸运的是,我不是。许多人在 Amazon 论坛上抱怨过,如果他们愿意使用 HTTP 而不是 HTTPS,他们被推荐了一个简单的解决方法。而且,为了探索的目的,这完全没问题。
17:30。重新连接到 Amazon S3
这里是更新后的连接代码版本,现在需要两行而不是一行。
let s3Config = new AmazonS3Config(CommunicationProtocol = Protocol.HTTP)
let s3Client = new AmazonS3Client("KEY-ID", "SECRET-KEY", s3Config)
然后我重新运行了另外两行请求存储桶对象。现在,我成功收到了结果。
{AmazonId2 = "//VkK6Mb7Xe26pNPG7nt40GkYcp4GDyNV45I1E1mfZVCL67aak9/0Txmrk8X+JAE";
CommonPrefix = seq [];
CommonPrefixes = seq [];
Delimiter = null;
Headers = seq
["x-amz-id-2"; "x-amz-request-id"; "Transfer-Encoding";
"Content-Type"; ...];
IsTruncated = true;
MaxKeys = "1000";
Metadata = seq [];
Name = "abilov.com";
NextMarker = "private/photo/2005/2005-06-10-12 Elverum/IMG_6803.JPG";
Prefix = "";
RequestId = "3A27D1A4495A747A";
ResponseStream = null;
S3Objects = seq {…}
(The rest of results is skipped)
17:35。检索对象数据
我检查了响应,找到了我需要的属性:S3Objects
。它是一个存储桶对象的集合,代表了我上传到 S3 的照片库文件。进一步查看可用的 AmazonS3Client
方法,我找到了一个应该用来检索单个对象的方法:GetObject
。它需要一个 GetObjectRequest
实例。我首先创建了一个没有分配 Key
属性的 GetObjectRequest
实例。但在收到一个关于缺少键的错误消息的异常后,我纠正了我的错误。这是奏效的代码。
let objectRequest = new GetObjectRequest(BucketName=bucket.BucketName,
Key="private/photo/2005/2005-01 Kolbotn/IMG_5645.JPG")
let obj = s3Client.GetObject(objectRequest)
“Key
”属性只是存储桶中的文件路径,所以很容易弄清楚。
我将“obj
”发送到 FSI 窗口,它打印了它的属性。
{AmazonId2 = "zUH8IaRglPSPQuC+8m5pzAR59paJTi/L5M1DnxNympp9HcU5RigrntS7NGvpxQQf";
ContentLength = 1138630L;
ContentType = "image/jpeg";
ETag = ""ea02192ee842d3b1987a8de4ac879595"";
Headers = ?;
Metadata = seq ["x-amz-meta-cb-modifiedtime"];
RequestId = "A93CF70A966E4A56";
ResponseStream = System.Net.ConnectStream;
ResponseXml = null;
VersionId = null;}
其中一个属性叫做 ReponseStream
。这听起来非常有希望——也许我可以尝试从这个流创建一个图像并在 Windows Form 中显示它?
17:40。显示图像
如果你想在 C# 的 Windows Form 中显示图像,你应该怎么做?你必须从创建一个使用 Windows Forms Application 模板的新项目开始。然后,你可以在 Form 设计器中编辑 Form
类并向其添加一个 Image
控件。如何在 F# 中做到这一点?你可以直接从脚本创建一个带有图像的窗体。这是代码。
#r "System.Drawing"
#r "System.Windows.Forms"
open System.Windows.Forms
open System.Drawing
let img = Image.FromStream(obj.ResponseStream)
let frm = new Form(ClientSize = img.Size)
frm.Paint.Add(fun e -> e.Graphics.DrawImage(img, new Point(0,0)))
frm.Show()
请注意,前四行只是为了引用所需的 DLL 和导入命名空间。其余代码同样简短,并完成了任务。看看 Image.FromStream
调用:我不太确定我能否做到这一点,但属性名“ResponseStream
”太诱人了,不容错过。突然,我看到了这个窗口。
这是我们的家猫菲加罗(不幸的是,它已经不在我们身边了)。不,我没有特意为我的测试选择这张照片。这是一个随机的选择,或许暗示了一些家庭倾向于拍摄什么样的照片。
17:45。完成!
是的,我们做到了!也许只是为了展示我的环保主义态度,我将进行最后一次调用。
s3Client.Dispose()
结论
所以,这是四分之三的探索时间。一个全新的 API,加上我对 Amazon S3 相对不熟悉的经验,但一张从 S3 存储桶检索并显示在 Windows Form 中的家庭照片库图片证明我们完成了整个操作流程。所有这一切都用 F# 完成。这个实验并没有展示语言的其他(更重要的)优点,但我特意想关注另一个话题:语言在技术探索和快速原型设计方面的效率。作为一种 .NET 语言,F# 可以访问任何 .NET 领域或功能,并且其 REPL 支持确保开发人员的时间得到最有效的利用。